diff --git a/CPRNIMS.Domain/CPRNIMS.Domain.csproj b/CPRNIMS.Domain/CPRNIMS.Domain.csproj index 85e4f86..4549f27 100644 --- a/CPRNIMS.Domain/CPRNIMS.Domain.csproj +++ b/CPRNIMS.Domain/CPRNIMS.Domain.csproj @@ -10,6 +10,9 @@ + + + diff --git a/CPRNIMS.Domain/Contracts/Common/ITransactionFacade.cs b/CPRNIMS.Domain/Contracts/Common/ITransactionFacade.cs new file mode 100644 index 0000000..61c7759 --- /dev/null +++ b/CPRNIMS.Domain/Contracts/Common/ITransactionFacade.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Domain.Contracts.Common +{ + public interface ITransactionFacade + { + Task ExecuteAsync(Func> operation,CancellationToken ct); + Task ExecuteAsync(Func operation,CancellationToken ct); + } +} diff --git a/CPRNIMS.Domain/Contracts/Inventory/IInventoryReports.cs b/CPRNIMS.Domain/Contracts/Inventory/IInventoryReports.cs new file mode 100644 index 0000000..6e70503 --- /dev/null +++ b/CPRNIMS.Domain/Contracts/Inventory/IInventoryReports.cs @@ -0,0 +1,16 @@ +using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Domain.Contracts.Inventory +{ + public interface IInventoryReports + { + Task GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, int? departmentId = null, string? userName = ""); + Task GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, int? departmentId = null, string? userName = ""); + Task GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, int? departmentId = null, string? userName = ""); + } +} diff --git a/CPRNIMS.Domain/Contracts/Inventory/IMRS.cs b/CPRNIMS.Domain/Contracts/Inventory/IMRS.cs index e42df8e..9540c15 100644 --- a/CPRNIMS.Domain/Contracts/Inventory/IMRS.cs +++ b/CPRNIMS.Domain/Contracts/Inventory/IMRS.cs @@ -11,9 +11,10 @@ namespace CPRNIMS.Domain.Contracts.Inventory { public interface IMRS { - Task GetPagedAsync(MRSFilterDto filter); - Task GetByIdAsync(long mrsId); - Task CreateAsync(CreateMRSRequest dto, string createdBy); - Task ApproveAsync(long mrsId, string approvedBy); + Task GetPagedAsync(MRSFilterDto filter, CancellationToken ct,int? departmentId = null, string? userName = ""); + Task GetByIdAsync(long mrsId, CancellationToken ct); + Task CreateAsync(CreateMRSRequest dto, string createdBy, CancellationToken ct); + Task ApproveAsync(long mrsId, string approvedBy, CancellationToken ct); + Task CancelAsync(CancelMRSRequest request, string canceledBy, CancellationToken ct); } } diff --git a/CPRNIMS.Domain/Contracts/Inventory/IRIS.cs b/CPRNIMS.Domain/Contracts/Inventory/IRIS.cs index 9e2d2ca..3665e75 100644 --- a/CPRNIMS.Domain/Contracts/Inventory/IRIS.cs +++ b/CPRNIMS.Domain/Contracts/Inventory/IRIS.cs @@ -12,11 +12,11 @@ using System.Threading.Tasks; namespace CPRNIMS.Domain.Contracts.Inventory { public interface IRIS - { - Task GetPagedAsync(RISFilterDto filter, CancellationToken ct); + { + Task GetPagedAsync(RISFilterDto filter, CancellationToken ct, int? departmentId = null, string? userName = ""); Task GetByIdAsync(long risId, CancellationToken ct); Task CreateAsync(CreateRISRequest dto, string createdBy, CancellationToken ct); Task ApproveAsync(ApproveRISRequest request, string approvedBy, CancellationToken ct); - Task CancelAsync(CancelRISRequest request, CancellationToken ct); + Task CancelAsync(CancelRISRequest request,string canceledBy, CancellationToken ct); } } diff --git a/CPRNIMS.Domain/Contracts/Reports/IReportBuilder.cs b/CPRNIMS.Domain/Contracts/Reports/IReportBuilder.cs new file mode 100644 index 0000000..2b03fae --- /dev/null +++ b/CPRNIMS.Domain/Contracts/Reports/IReportBuilder.cs @@ -0,0 +1,16 @@ +using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using FastReport; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Domain.Contracts.Reports +{ + public interface IReportBuilder + { + Task RISBuildAsync(DateTime dateFrom, DateTime dateTo, string templatePath, CancellationToken ct); + Task MRSBuildAsync(DateTime dateFrom, DateTime dateTo, string templatePath, CancellationToken ct); + } +} diff --git a/CPRNIMS.Domain/Contracts/Reports/IReportDataService.cs b/CPRNIMS.Domain/Contracts/Reports/IReportDataService.cs new file mode 100644 index 0000000..0063489 --- /dev/null +++ b/CPRNIMS.Domain/Contracts/Reports/IReportDataService.cs @@ -0,0 +1,16 @@ +using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Domain.Contracts.Reports +{ + public interface IReportDataService + { + List GetMain(DateTime dateFrom, DateTime dateTo); + List GetDisciplines(DateTime dateFrom, DateTime dateTo); + List GetRecipients(DateTime dateFrom, DateTime dateTo); + } +} diff --git a/CPRNIMS.Domain/Services/Account/Account.cs b/CPRNIMS.Domain/Services/Account/Account.cs index 9f6145f..f6844f3 100644 --- a/CPRNIMS.Domain/Services/Account/Account.cs +++ b/CPRNIMS.Domain/Services/Account/Account.cs @@ -57,6 +57,7 @@ namespace CPRNIMS.Domain.Services.Account new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim("FullName", user.FullName ?? ""), new Claim("Company", user.Company ?? ""), + new Claim("DepartmentId", Convert.ToString(user.DepartmentId)), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), }; diff --git a/CPRNIMS.Domain/Services/Common/TransactionFacade.cs b/CPRNIMS.Domain/Services/Common/TransactionFacade.cs new file mode 100644 index 0000000..1b4808b --- /dev/null +++ b/CPRNIMS.Domain/Services/Common/TransactionFacade.cs @@ -0,0 +1,49 @@ +using CPRNIMS.Domain.Contracts.Common; +using CPRNIMS.Infrastructure.Database; +using Microsoft.EntityFrameworkCore; + +namespace CPRNIMS.Domain.Services.Common +{ + public class TransactionFacade : ITransactionFacade + { + private readonly NonInventoryDbContext _db; + + public TransactionFacade(NonInventoryDbContext db) + => _db = db ?? throw new ArgumentNullException(nameof(db)); + + public async Task ExecuteAsync(Func> operation, CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(operation); + + var strategy = _db.Database.CreateExecutionStrategy(); + + return await strategy.ExecuteAsync(async () => + { + await using var tx = await _db.Database.BeginTransactionAsync(ct); + try + { + var result = await operation(); + await _db.SaveChangesAsync(ct); + await tx.CommitAsync(ct); + return result; + } + catch + { + await tx.RollbackAsync(ct); + throw; + } + }); + } + + public async Task ExecuteAsync(Func operation, CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(operation); + + await ExecuteAsync(async () => + { + await operation(); + return true; + }, ct); + } + } +} diff --git a/CPRNIMS.Domain/Services/Inventory/InventoryReports.cs b/CPRNIMS.Domain/Services/Inventory/InventoryReports.cs new file mode 100644 index 0000000..2dcd900 --- /dev/null +++ b/CPRNIMS.Domain/Services/Inventory/InventoryReports.cs @@ -0,0 +1,294 @@ +using CPRNIMS.Domain.Contracts.Inventory; +using CPRNIMS.Infrastructure.Database; +using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using Microsoft.EntityFrameworkCore; + +namespace CPRNIMS.Domain.Services.Inventory +{ + public class InventoryReports : IInventoryReports + { + private readonly NonInventoryDbContext _db; + public InventoryReports(NonInventoryDbContext db) => _db = db; + + public async Task GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, + int? departmentId = null, string? userName = "") + { + var endDate = dateTo.Date.AddDays(1); + + var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" }; + bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName) + && allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase); + + var dateToInclusive = dateTo.AddDays(1); + + var query = _db.RIS + .Include(r => r.Discipline) + .Include(r => r.Inventory) + .Include(r => r.MaterialReturns) + .Where(r => r.CreatedDate >= dateFrom && r.CreatedDate < dateToInclusive); + + if (departmentId.HasValue && !seeAllDepartments) + { + query = query.Where(m => + m.Inventory.User != null && + m.Inventory.User.DepartmentId == departmentId.Value); + } + + var rows = await query + .OrderByDescending(r => r.CreatedDate) + .Select(r => new RISReportRow + { + RISNo = r.RISNo, + CreatedDate = r.CreatedDate, + ItemName = r.PRDetail != null ? r.PRDetail.ItemName : "—", + ItemNo = r.Inventory.ItemNo, + DisciplineName = r.Discipline.DisciplineName, + IssuedTo = r.IssuedTo, + QtyIssued = r.QtyIssued, + TotalReturned = r.MaterialReturns + .Where(m => m.Status != 2) + .Sum(m => (int?)m.QtyReturned) ?? 0, + Status = r.Status, + StatusLabel = r.Status == 0 ? "Draft" + : r.Status == 1 ? "Approved" + : "Cancelled" + }) + .ToListAsync(ct); + + foreach (var row in rows) + row.NetIssued = row.QtyIssued - row.TotalReturned; + + var summary = new RISReportSummary + { + TotalRIS = rows.Count, + TotalApproved = rows.Count(r => r.Status == 1), + TotalPending = rows.Count(r => r.Status == 0), + TotalCancelled = rows.Count(r => r.Status == 2), + TotalQtyIssued = rows.Sum(r => r.QtyIssued), + TotalQtyReturned = rows.Sum(r => r.TotalReturned), + TotalNetIssued = rows.Sum(r => r.NetIssued), + ApprovalRatePct = rows.Count > 0 + ? Math.Round(rows.Count(r => r.Status == 1) * 100m / rows.Count, 1) + : 0 + }; + + var byDiscipline = rows + .GroupBy(r => r.DisciplineName) + .Select(g => new DisciplineCount { DisciplineName = g.Key, Count = g.Count() }) + .OrderByDescending(d => d.Count) + .ToList(); + + var topRecipients = rows + .GroupBy(r => r.IssuedTo) + .Select(g => new TopRecipient + { + IssuedTo = g.Key, + SlipCount = g.Count(), + TotalQty = g.Sum(r => r.QtyIssued) + }) + .OrderByDescending(t => t.TotalQty) + .Take(5) + .ToList(); + + return new RISReportDto + { + ReportNo = $"RPT-RIS-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}", + DateFrom = dateFrom, + DateTo = dateTo, + Summary = summary, + Rows = rows, + ByDiscipline = byDiscipline, + TopRecipients = topRecipients + }; + } + + public async Task GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, + int? departmentId = null, string? userName = "") + { + var endDate = dateTo.Date.AddDays(1); + + var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" }; + bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName) + && allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase); + + var query = _db.MRS + .Include(m => m.RIS) + .Include(m => m.Inventory) + .ThenInclude(i => i.User) + .Where(m => m.CreatedDate >= dateFrom && + m.CreatedDate < endDate); + + if (departmentId.HasValue && !seeAllDepartments) + { + query = query.Where(m => + m.Inventory.User != null && + m.Inventory.User.DepartmentId == departmentId.Value); + } + + var rows = await query + .OrderByDescending(m => m.CreatedDate) + .Select(m => new MRSReportRow + { + MRSNo = m.MRSNo, + CreatedDate = m.CreatedDate, + RISNo = m.RIS.RISNo, + ItemName = m.RIS.PRDetail != null ? m.RIS.PRDetail.ItemName : "—", + ReturnedBy = m.ReturnedBy, + QtyReturned = m.QtyReturned, + Condition = m.Condition ?? "Good", + Status = m.Status, + StatusLabel = m.Status == 0 ? "Draft" + : m.Status == 1 ? "Approved" + : "Cancelled" + }) + .ToListAsync(ct); + + // Total RIS qty issued in the same period (for the comparison panel) + var totalRISQty = await _db.RIS + .Where(r => r.CreatedDate >= dateFrom && r.CreatedDate < endDate + && r.Status != 2) + .SumAsync(r => (int?)r.QtyIssued, ct) ?? 0; + + var totalReturned = rows.Where(r => r.Status != 2).Sum(r => r.QtyReturned); + var goodCount = rows.Count(r => r.Condition == "Good"); + + var summary = new MRSReportSummary + { + TotalMRS = rows.Count, + TotalQtyReturned = totalReturned, + TotalQtyIssuedRIS = totalRISQty, + NetQtyConsumed = totalRISQty - totalReturned, + ReturnRatePct = totalRISQty > 0 + ? Math.Round(totalReturned * 100m / totalRISQty, 1) + : 0, + GoodConditionPct = rows.Count > 0 + ? Math.Round(goodCount * 100m / rows.Count, 1) + : 0 + }; + + var byCondition = rows + .Where(r => r.Status != 2) + .GroupBy(r => r.Condition) + .Select(g => new ConditionTotal { Condition = g.Key, TotalQty = g.Sum(r => r.QtyReturned) }) + .OrderByDescending(c => c.TotalQty) + .ToList(); + + return new MRSReportDto + { + ReportNo = $"RPT-MRS-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}", + DateFrom = dateFrom, + DateTo = dateTo, + Summary = summary, + Rows = rows, + ByCondition = byCondition + }; + } + + public async Task GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct, + int? departmentId = null, string? userName = "") + { + var endDate = dateTo.Date.AddDays(1); + + var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25" , "LHRIOCAS24" }; + bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName) + && allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase); + + var query = _db.InventTransDetails + .Where(itd => + itd.IsActive && + itd.InventTrans.IsActive && + itd.InventTrans.Inventory.IsActive && + itd.CreatedDate >= dateFrom && + itd.CreatedDate < endDate); + + if (departmentId.HasValue && !seeAllDepartments) + { + query = query.Where(itd => + itd.InventTrans.Inventory.User.DepartmentId == departmentId.Value); + } + + var rawRows = await query + .Select(itd => new + { + itd.InventTrans.Inventory.InventoryId, + itd.InventTrans.Inventory.ItemNo, + itd.InventTrans.Inventory.QtyIn, + itd.InventTrans.Inventory.QtyOut, + itd.InventTrans.Inventory.QtyOnHand, + LotNo = itd.InventTrans.Inventory.Lot != null + ? itd.InventTrans.Inventory.Lot.LotName + : null, + ItemName = itd.InventTrans.Inventory.Item.ItemCode.ItemName ?? "None", + ItemCategoryName = + itd.InventTrans.Inventory.Item.ItemCode.ItemCategory.ItemCategoryName ?? "None", + itd.CreatedDate + }) + .ToListAsync(ct); + + // De-duplicate: one row per Inventory (latest trans detail wins for the date shown) + var rows = rawRows + .GroupBy(r => r.InventoryId) + .Select(g => + { + var inv = g.OrderByDescending(x => x.CreatedDate).First(); + return new InventoryReportRow + { + ItemName = inv.ItemName, + ItemNo = inv.ItemNo, + ItemCategoryName = inv.ItemCategoryName, + LotNo = inv.LotNo, + QtyIn = inv.QtyIn, + QtyOut = inv.QtyOut, + QtyOnHand = inv.QtyOnHand, + StockPct = inv.QtyIn > 0 + ? (int)Math.Round(Math.Max(0, Math.Min(100, (inv.QtyOnHand / inv.QtyIn) * 100))) + : 0 + }; + }) + .OrderBy(r => r.ItemName) + .ToList(); + + var summary = new InventoryReportSummary + { + TotalSKUs = rows.Count, + TotalOnHand = rows.Sum(r => r.QtyOnHand), + TotalQtyIn = rows.Sum(r => r.QtyIn), + TotalQtyOut = rows.Sum(r => r.QtyOut), + LowStockCount = rows.Count(r => r.StockPct < 20 && r.QtyOnHand > 0), + OutOfStockCount = rows.Count(r => r.QtyOnHand <= 0) + }; + + var byCategory = rows + .GroupBy(r => r.ItemCategoryName) + .Select(g => new CategoryStockLevel + { + CategoryName = g.Key, + AvgStockPct = (int)Math.Round(g.Average(r => r.StockPct)) + }) + .OrderByDescending(c => c.AvgStockPct) + .ToList(); + + var alerts = rows + .Where(r => r.StockPct < 20) + .OrderBy(r => r.StockPct) + .Take(10) + .Select(r => new InventoryAlert + { + ItemName = r.ItemName, + QtyOnHand = r.QtyOnHand, + Severity = r.QtyOnHand <= 0 ? "Critical" : "Low" + }) + .ToList(); + + return new InventoryReportDto + { + ReportNo = $"RPT-INV-{DateTime.Now:yyyyMM}-{Random.Shared.Next(1, 999):D3}", + AsOf = dateTo.Date, // reflect the requested period end, not always today + Summary = summary, + Rows = rows, + ByCategory = byCategory, + Alerts = alerts + }; + } + } +} diff --git a/CPRNIMS.Domain/Services/Inventory/MRS.cs b/CPRNIMS.Domain/Services/Inventory/MRS.cs index 1180fb8..13801bb 100644 --- a/CPRNIMS.Domain/Services/Inventory/MRS.cs +++ b/CPRNIMS.Domain/Services/Inventory/MRS.cs @@ -1,24 +1,23 @@ -using CPRNIMS.Domain.Contracts.Inventory; +using CPRNIMS.Domain.Contracts.Common; +using CPRNIMS.Domain.Contracts.Inventory; using CPRNIMS.Infrastructure.Database; using CPRNIMS.Infrastructure.Dto.Inventory; using CPRNIMS.Infrastructure.Dto.Inventory.Request; -using CPRNIMS.Infrastructure.Dto.Inventory.Response; using CPRNIMS.Infrastructure.Entities.Inventory; using Microsoft.EntityFrameworkCore; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace CPRNIMS.Domain.Services.Inventory { public class MRS : IMRS { private readonly NonInventoryDbContext _db; - public MRS(NonInventoryDbContext db) => _db = db; - - public async Task ApproveAsync(long mrsId, string approvedBy) + private readonly ITransactionFacade _transactionFacade; + public MRS(NonInventoryDbContext db, ITransactionFacade transactionFacade) + { + _db = db; + _transactionFacade = transactionFacade; + } + public async Task ApproveAsync(long mrsId, string approvedBy, CancellationToken ct) { var rms = await _db.MRS.FindAsync(mrsId) ?? throw new InvalidOperationException("MRS not found."); @@ -30,73 +29,108 @@ namespace CPRNIMS.Domain.Services.Inventory rms.ApprovedBy = approvedBy; rms.ApprovedDate = DateTime.Now; - await _db.SaveChangesAsync(); + await _db.SaveChangesAsync(ct); } - public async Task CreateAsync(CreateMRSRequest dto, string createdBy) + public async Task CancelAsync(CancelMRSRequest request,string canceledBy, CancellationToken ct) { - var ris = await _db.RIS - .Include(r => r.Inventory) - .FirstOrDefaultAsync(r => r.RISId == dto.RISId) - ?? throw new InvalidOperationException("Referenced RIS not found."); - - if (dto.QtyReturned > ris.QtyIssued) - throw new InvalidOperationException( - $"Cannot return more than issued. Issued: {ris.QtyIssued}."); - - var mrsNo = await GenerateMRSNoAsync(); - - var mrs = new Infrastructure.Entities.Inventory.MRS + await _transactionFacade.ExecuteAsync(async () => { - MRSNo = mrsNo, - RISId = dto.RISId, - InventoryId = ris.InventoryId, - ReturnedBy = dto.ReturnedBy, - QtyReturned = dto.QtyReturned, - Condition = dto.Condition, - Remarks = dto.Remarks, - Status = 0, - CreatedBy = createdBy, - CreatedDate = DateTime.Now - }; - _db.MRS.Add(mrs); + var mrs = await _db.MRS + .Include(m => m.Inventory) + .FirstOrDefaultAsync(m => m.MRSId == request.MRSId) + ?? throw new InvalidOperationException("MRS not found."); - var inventory = ris.Inventory; - inventory.QtyOut = Math.Max(0m, inventory.QtyOut - dto.QtyReturned); + if (mrs.Status == 2) + throw new InvalidOperationException("MRS is already cancelled."); - inventory.QtyOnHand = inventory.QtyIn - inventory.QtyOut; - - var trans = await _db.InventTrans - .FirstOrDefaultAsync(t => t.InventoryId == ris.InventoryId && t.IsActive == true)!; - - _db.InventTransDetails.Add(new InventTransDetail - { - InventTransId = trans.InventTransId, - TransTypeId = 6, - QtyIn = dto.QtyReturned, - CreatedDate = DateTime.Now, - Remarks = $"MRS: {mrsNo} — return from MRS: {ris.RISNo}", - IsActive = true - }); - - await _db.SaveChangesAsync(); - return mrs; + // Reverse the return: deduct qty back out + mrs.Inventory.QtyOut = Math.Max(0m, mrs.Inventory.QtyOut) + mrs.QtyReturned; + mrs.Inventory.QtyOnHand = mrs.Inventory.QtyIn - mrs.Inventory.QtyOut; + mrs.Reason = request.Reason; + mrs.Status = 2; + mrs.CanceledDate = DateTime.Now; + mrs.CanceledBy = canceledBy; + }, ct); } - public async Task GetByIdAsync(long mrsId) + public async Task CreateAsync(CreateMRSRequest dto, string createdBy, CancellationToken ct) + { + return await _transactionFacade.ExecuteAsync(async () => + { + var ris = await _db.RIS + .Include(r => r.Inventory) + .FirstOrDefaultAsync(r => r.RISId == dto.RISId, ct) + ?? throw new InvalidOperationException("Referenced RIS not found."); + + if (dto.QtyReturned > ris.QtyIssued) + throw new InvalidOperationException( + $"Cannot return more than issued. Issued: {ris.QtyIssued}."); + + var mrsNo = await GenerateMRSNoAsync(); + + var mrs = new Infrastructure.Entities.Inventory.MRS + { + MRSNo = mrsNo, + RISId = dto.RISId, + InventoryId = ris.InventoryId, + ReturnedBy = dto.ReturnedBy, + QtyReturned = dto.QtyReturned, + Condition = dto.Condition, + Remarks = dto.Remarks, + Status = 0, + CreatedBy = createdBy, + CreatedDate = DateTime.Now + }; + _db.MRS.Add(mrs); + + var inventory = ris.Inventory; + inventory.QtyOut = Math.Max(0m, inventory.QtyOut - dto.QtyReturned); + + inventory.QtyOnHand = inventory.QtyIn - inventory.QtyOut; + + var trans = await _db.InventTrans + .FirstOrDefaultAsync(t => t.InventoryId == ris.InventoryId && t.IsActive == true, ct)!; + + _db.InventTransDetails.Add(new InventTransDetail + { + InventTransId = trans.InventTransId, + TransTypeId = 6, + QtyIn = dto.QtyReturned, + CreatedDate = DateTime.Now, + Remarks = $"MRS: {mrsNo} — return from MRS: {ris.RISNo}", + IsActive = true + }); + + return mrs; + }, ct); + } + + public async Task GetByIdAsync(long mrsId, CancellationToken ct) => await _db.MRS .Include(r => r.Inventory) .Include(r => r.RIS) - .FirstOrDefaultAsync(r => r.RISId == mrsId); + .FirstOrDefaultAsync(r => r.RISId == mrsId, ct); - public async Task GetPagedAsync(MRSFilterDto filter) + public async Task GetPagedAsync(MRSFilterDto filter, CancellationToken ct, + int? departmentId = null, string? userName = "") { + var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" }; + bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName) + && allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase); + var q = _db.MRS .Include(m => m.RIS) .Include(m => m.Inventory) .AsQueryable(); + if (departmentId.HasValue && !seeAllDepartments) + { + q = q.Where(itd => + itd.Inventory.User.DepartmentId == departmentId.Value); + } + if (!string.IsNullOrWhiteSpace(filter.SearchMRSNo)) q = q.Where(m => m.MRSNo.Contains(filter.SearchMRSNo)); @@ -112,13 +146,13 @@ namespace CPRNIMS.Domain.Services.Inventory if (filter.DateTo.HasValue) q = q.Where(m => m.CreatedDate <= filter.DateTo.Value.AddDays(1)); - var total = await q.CountAsync(); + var total = await q.CountAsync(ct); var data = await q .OrderByDescending(m => m.CreatedDate) - .Skip((filter.Page - 1) * filter.PageSize) + .Skip((filter.PageNumber - 1) * filter.PageSize) .Take(filter.PageSize) - .Select(m => new MRSResponse + .Select(m => new MRSPagedDto { MRSId = m.MRSId, MRSNo = m.MRSNo, @@ -139,7 +173,7 @@ namespace CPRNIMS.Domain.Services.Inventory ApprovedBy = m.ApprovedBy, ApprovedDate = m.ApprovedDate }) - .ToListAsync(); + .ToListAsync(ct); return new MRSPagedResult { Data = data, RecordsTotal = total }; } diff --git a/CPRNIMS.Domain/Services/Inventory/RIS.cs b/CPRNIMS.Domain/Services/Inventory/RIS.cs index 649645b..5f32441 100644 --- a/CPRNIMS.Domain/Services/Inventory/RIS.cs +++ b/CPRNIMS.Domain/Services/Inventory/RIS.cs @@ -1,5 +1,5 @@ -using CPRNIMS.Domain.Contracts.Inventory; -using CPRNIMS.Domain.UIServices.Inventory; +using CPRNIMS.Domain.Contracts.Common; +using CPRNIMS.Domain.Contracts.Inventory; using CPRNIMS.Infrastructure.Database; using CPRNIMS.Infrastructure.Dto.Inventory; using CPRNIMS.Infrastructure.Dto.Inventory.Request; @@ -17,75 +17,63 @@ namespace CPRNIMS.Domain.Services.Inventory public class RIS : IRIS { private readonly NonInventoryDbContext _db; - - public RIS(NonInventoryDbContext db) => _db = db; - + private readonly ITransactionFacade _transactionFacade; + public RIS(NonInventoryDbContext db, ITransactionFacade transactionFacade) + { + _db = db; + _transactionFacade = transactionFacade; + } public async Task CreateAsync(CreateRISRequest dto, string createdBy, CancellationToken ct) { - var strategy = _db.Database.CreateExecutionStrategy(); - - return await strategy.ExecuteAsync(async () => + return await _transactionFacade.ExecuteAsync(async () => { - await using var tx = await _db.Database.BeginTransactionAsync(ct); - try - { - var inventory = await _db.Inventories + var inventory = await _db.Inventories .FirstOrDefaultAsync(i => i.InventoryId == dto.InventoryId, ct) ?? throw new InvalidOperationException("Inventory record not found."); - if (inventory.QtyOnHand < dto.QtyIssued) - throw new InvalidOperationException( - $"Insufficient stock. On hand: {inventory.QtyOnHand}, requested: {dto.QtyIssued}."); + if (inventory.QtyOnHand < dto.QtyIssued) + throw new InvalidOperationException( + $"Insufficient stock. On hand: {inventory.QtyOnHand}, requested: {dto.QtyIssued}."); - var risNo = await GenerateRISNoAsync(ct); + var risNo = await GenerateRISNoAsync(ct); - var ris = new Infrastructure.Entities.Inventory.RIS - { - RISNo = risNo, - InventoryId = dto.InventoryId, - PRDetailId = dto.PRDetailId, - IssuedTo = dto.IssuedTo, - DisciplineId = dto.DisciplineId, - QtyIssued = dto.QtyIssued, - Remarks = dto.Remarks, - Status = 0, - CreatedBy = createdBy, - CreatedDate = DateTime.Now - }; - _db.RIS.Add(ris); - await _db.SaveChangesAsync(ct); - - var trans = await _db.InventTrans - .Where(t => t.InventoryId == dto.InventoryId && t.IsActive == true) - .FirstOrDefaultAsync(ct) - ?? throw new InvalidOperationException( - "No active InventTrans found for this inventory record."); - - _db.InventTransDetails.Add(new InventTransDetail - { - InventTransId = trans.InventTransId, - TransTypeId = 5, - PRDetailId = dto.PRDetailId, - QtyOut = dto.QtyIssued, - CreatedDate = DateTime.Now, - Remarks = $"RIS: {risNo}", - IsActive = true - }); - - inventory.QtyOut = Math.Max(0m, inventory.QtyOut) + dto.QtyIssued; - inventory.QtyOnHand = Math.Max(0m, inventory.QtyIn) - (decimal)inventory.QtyOut; - - await _db.SaveChangesAsync(ct); - await tx.CommitAsync(ct); - - return ris; - } - catch + var ris = new Infrastructure.Entities.Inventory.RIS { - await tx.RollbackAsync(ct); - throw; - } - }); + RISNo = risNo, + InventoryId = dto.InventoryId, + PRDetailId = dto.PRDetailId, + IssuedTo = dto.IssuedTo, + DisciplineId = dto.DisciplineId, + QtyIssued = dto.QtyIssued, + Remarks = dto.Remarks, + Status = 0, + CreatedBy = createdBy, + CreatedDate = DateTime.Now + }; + _db.RIS.Add(ris); + + var trans = await _db.InventTrans + .Where(t => t.InventoryId == dto.InventoryId && t.IsActive == true) + .FirstOrDefaultAsync(ct) + ?? throw new InvalidOperationException( + "No active InventTrans found for this inventory record."); + + _db.InventTransDetails.Add(new InventTransDetail + { + InventTransId = trans.InventTransId, + TransTypeId = 5, + PRDetailId = dto.PRDetailId, + QtyOut = dto.QtyIssued, + CreatedDate = DateTime.Now, + Remarks = $"RIS: {risNo}", + IsActive = true + }); + + inventory.QtyOut = Math.Max(0m, inventory.QtyOut) + dto.QtyIssued; + inventory.QtyOnHand = Math.Max(0m, inventory.QtyIn) - (decimal)inventory.QtyOut; + + return ris; + }, ct); } public async Task ApproveAsync(ApproveRISRequest request, string approvedBy, CancellationToken ct) @@ -103,16 +91,11 @@ namespace CPRNIMS.Domain.Services.Inventory await _db.SaveChangesAsync(ct); } - public async Task CancelAsync(CancelRISRequest request, CancellationToken ct) + public async Task CancelAsync(CancelRISRequest request,string canceledBy, CancellationToken ct) { - var strategy = _db.Database.CreateExecutionStrategy(); - - await strategy.ExecuteAsync(async () => - { - await using var tx = await _db.Database.BeginTransactionAsync(ct); - try - { - var ris = await _db.RIS + await _transactionFacade.ExecuteAsync(async () => + { + var ris = await _db.RIS .Include(r => r.Inventory) .FirstOrDefaultAsync(r => r.RISId == request.RISId, ct) ?? throw new InvalidOperationException("RIS not found."); @@ -120,6 +103,23 @@ namespace CPRNIMS.Domain.Services.Inventory if (ris.Status == 2) throw new InvalidOperationException("RIS is already cancelled."); + //Check if already approved the related RIS No to MRS must cannot be cancel + var mrs = await _db.MRS.FirstOrDefaultAsync(m => m.RISId == request.RISId, ct); + + if (mrs != null) + { + if (mrs.Status == 1) + { + throw new InvalidOperationException( + $"MRS #{mrs.MRSNo} has already been approved in relation to RIS #{ris.RISNo}."); + } + + mrs.Status = 2; + mrs.CanceledDate = DateTime.Now; + mrs.CanceledBy = canceledBy; + mrs.Reason = "Canceled in RIS already"; + } + ris.Inventory.QtyOut = Math.Max(0m, ris.Inventory.QtyOut - ris.QtyIssued); ris.Inventory.QtyOnHand = ris.Inventory.QtyIn - ris.Inventory.QtyOut; ris.Reason = request.Reason; @@ -141,31 +141,7 @@ namespace CPRNIMS.Domain.Services.Inventory Remarks = request.Reason, IsActive = true }); - - //var inventory = await _db.Inventories - // .FirstOrDefaultAsync(i => i.InventoryId == ris.InventoryId, ct) - // ?? throw new InvalidOperationException("Inventory record not found."); - - //if (inventory.QtyOnHand < ris.QtyIssued) - // throw new InvalidOperationException( - // $"Insufficient stock. On hand: {inventory.QtyOnHand}, requested: {ris.QtyIssued}."); - - ////restore the QtyOnHand using ris.QtyIssued - //inventory.QtyOnHand = Math.Max(0m, inventory.QtyOnHand) + ris.QtyIssued; - ////reduce the QtyOut using ris.QtyIssued as we cancel the return isuance slip - //inventory.QtyOut = Math.Max(0m, inventory.QtyOut) - ris.QtyIssued; - - await _db.SaveChangesAsync(ct); - await tx.CommitAsync(ct); - - return ris; - } - catch - { - await tx.RollbackAsync(ct); - throw; - } - }); + }, ct); } private async Task GenerateRISNoAsync(CancellationToken ct) @@ -177,19 +153,30 @@ namespace CPRNIMS.Domain.Services.Inventory return $"RIS-{year}{month}-{count:D4}"; // e.g. RIS-202606-0001 } - public async Task GetPagedAsync(RISFilterDto filter, CancellationToken ct) + public async Task GetPagedAsync(RISFilterDto filter, CancellationToken ct, + int? departmentId = null, string? userName = "") { + var allowedAllDeptUsers = new[] { "LSKRISUR24", "LSCYNDIZ25", "LSJONTAN25", "LHRIOCAS24" }; + bool seeAllDepartments = !string.IsNullOrWhiteSpace(userName) + && allowedAllDeptUsers.Contains(userName, StringComparer.OrdinalIgnoreCase); + var q = _db.RIS .Include(r => r.Discipline) .Include(r => r.Inventory) .Include(r => r.MaterialReturns) .AsQueryable(); + if (departmentId.HasValue && !seeAllDepartments) + { + q = q.Where(itd => + itd.Inventory.User.DepartmentId == departmentId.Value); + } + // Status filter (default to Draft=0 if null) if (filter.Status.HasValue) q = q.Where(r => r.Status == filter.Status.Value); - else - q = q.Where(r => r.Status == 0); + //else + // q = q.Where(r => r.Status == 0); // RIS No if (!string.IsNullOrWhiteSpace(filter.SearchRISNo)) diff --git a/CPRNIMS.Domain/Services/Reports/ReportBuilder.cs b/CPRNIMS.Domain/Services/Reports/ReportBuilder.cs new file mode 100644 index 0000000..0557713 --- /dev/null +++ b/CPRNIMS.Domain/Services/Reports/ReportBuilder.cs @@ -0,0 +1,80 @@ +using CPRNIMS.Domain.Contracts.Reports; +using CPRNIMS.Domain.UIContracts.Common; +using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using CPRNIMS.Infrastructure.Helper; +using FastReport; +using Microsoft.Extensions.Configuration; +using System.Data; +using System.Net.Http.Json; +using System.Text.Json; + +namespace CPRNIMS.Infrastructure.Reports +{ + public class ReportBuilder : IReportBuilder + { + private readonly IConfiguration _configuration; + private readonly TokenHelper _tokenHelper; + private readonly IApiConfigurationService _apiConfigurationService; + public ReportBuilder(IConfiguration configuration, TokenHelper tokenHelper, + IApiConfigurationService apiConfigurationService) + { + _configuration = configuration; + _tokenHelper = tokenHelper; + _apiConfigurationService = apiConfigurationService; + } + + private static readonly JsonSerializerOptions _jsonOptions = new() + { + PropertyNameCaseInsensitive = true + }; + public Task MRSBuildAsync(DateTime dateFrom, DateTime dateTo, string templatePath, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public async Task RISBuildAsync( + DateTime dateFrom, DateTime dateTo, string templatePath, CancellationToken ct = default) + { + var token = await _tokenHelper.GetValidTokenAsync(); + if (string.IsNullOrEmpty(token)) + throw new InvalidOperationException("Token has been expired."); + + var endpoint = _configuration["LLI:NonInvent:InventoryMgmt:RISReportData"] + ?? throw new InvalidOperationException("RISReportData endpoint is not configured."); + + // Append the date range as query string + var url = $"{endpoint}?dateFrom={dateFrom:yyyy-MM-dd}&dateTo={dateTo:yyyy-MM-dd}"; + + using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + var response = await httpClient.GetAsync(url, ct); + var json = await response.Content.ReadAsStringAsync(ct); + + if (!response.IsSuccessStatusCode) + throw new InvalidOperationException( + $"Failed to fetch RIS report data. HTTP {(int)response.StatusCode}: {json}"); + + var dto = JsonSerializer.Deserialize(json, _jsonOptions) + ?? new RISReportDataDto(); + + var report = new Report(); + report.Load(templatePath); + + // RegisterData accepts IEnumerable — names must match the .frx datasources. + report.RegisterData(dto.Rows ?? new(), "TRIS"); + report.RegisterData(dto.Disciplines ?? new(), "TDisciplineAgg"); + report.RegisterData(dto.Recipients ?? new(), "TTopRecipients"); + + /* report.GetDataSource("Table").Enabled = true; + report.GetDataSource("Table1").Enabled = true; + report.GetDataSource("Table3").Enabled = true;*/ + report.GetDataSource("TRIS").Enabled = true; + report.GetDataSource("TDisciplineAgg").Enabled = true; + report.GetDataSource("TTopRecipients").Enabled = true; + + report.SetParameterValue("DateFrom", dateFrom); + report.SetParameterValue("DateTo", dateTo); + + return report; + } + } +} \ No newline at end of file diff --git a/CPRNIMS.Domain/Services/Reports/ReportDataService.cs b/CPRNIMS.Domain/Services/Reports/ReportDataService.cs new file mode 100644 index 0000000..0b31e19 --- /dev/null +++ b/CPRNIMS.Domain/Services/Reports/ReportDataService.cs @@ -0,0 +1,109 @@ +using CPRNIMS.Domain.Contracts.Reports; +using CPRNIMS.Infrastructure.Database; +using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using Dapper; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; + +namespace CPRNIMS.Infrastructure.Reports +{ + public class ReportDataService : IReportDataService + { + private readonly NonInventoryDbContext _context; + + public ReportDataService(NonInventoryDbContext context) => _context = context; + + private const string MainSql = """ + SELECT + R.RISNo, + PR.PRNo, + R.QtyIssued, + R.IssuedTo, + CASE R.Status + WHEN 0 THEN 'Draft' + WHEN 1 THEN 'Approved' + WHEN 2 THEN 'Cancelled' + ELSE 'Unknown' + END AS StatusLabel, + CreatedBy.CreatedBy, + ApprovedBy.ApprovedBy, + R.ApprovedDate, + R.CreatedDate, + D.DisciplineName, + PRD.ItemName, + PRD.ItemNo, + IV.QtyIn, + IV.QtyOut, + IV.QtyOnHand, + DEP.Department AS DepartmentName, + ISNULL(MRS_AGG.TotalReturned, 0) AS TotalReturned, + ISNULL(MRS_AGG.MRSCount, 0) AS MRSCount, + R.QtyIssued - ISNULL(MRS_AGG.TotalReturned, 0) AS NetIssued + FROM dbo.RIS R + INNER JOIN dbo.Disciplines D ON R.DisciplineId = D.DisciplineId + INNER JOIN dbo.Inventory IV ON R.InventoryId = IV.InventoryId AND IV.IsActive = 1 + LEFT JOIN dbo.Lot L ON IV.LotId = L.LotId + OUTER APPLY (SELECT U.FullName ApprovedBy FROM dbo.Users U WHERE R.ApprovedBy = U.UserName) ApprovedBy + OUTER APPLY (SELECT U2.FullName CreatedBy FROM dbo.Users U2 WHERE R.CreatedBy = U2.UserName) CreatedBy + INNER JOIN Users U ON IV.UserId = U.Id + LEFT JOIN dbo.Departments DEP ON U.DepartmentId = DEP.DepartmentId + LEFT JOIN dbo.PRDetails PRD ON R.PRDetailId = PRD.PRDetailsId AND PRD.IsActive = 1 + LEFT JOIN dbo.PR PR ON PRD.PRId = PR.PRId AND PR.IsActive = 1 + LEFT JOIN ( + SELECT + RISId, + COUNT(*) AS MRSCount, + SUM(QtyReturned) AS TotalReturned + FROM dbo.MRS + WHERE Status != 2 + GROUP BY RISId + ) MRS_AGG ON R.RISId = MRS_AGG.RISId + WHERE R.Status != 2 + AND R.CreatedDate >= @DateFrom + AND R.CreatedDate < DATEADD(DAY, 1, @DateTo) + ORDER BY R.CreatedDate DESC, R.RISNo ASC; + """; + + private const string DisciplineSql = """ + SELECT + D.DisciplineName, + COUNT(*) AS SlipCount + FROM dbo.RIS R + INNER JOIN dbo.Disciplines D ON R.DisciplineId = D.DisciplineId + WHERE R.Status != 2 + AND R.CreatedDate >= @DateFrom + AND R.CreatedDate < DATEADD(DAY, 1, @DateTo) + GROUP BY D.DisciplineName + ORDER BY SlipCount DESC; + """; + + private const string RecipientsSql = """ + SELECT TOP 5 + R.IssuedTo AS Name, + COUNT(*) AS SlipCount, + SUM(IV.QtyOut) AS QtyOut + FROM dbo.RIS R + INNER JOIN dbo.Inventory IV ON R.InventoryId = IV.InventoryId AND IV.IsActive = 1 + WHERE R.Status != 2 + AND R.CreatedDate >= @DateFrom + AND R.CreatedDate < DATEADD(DAY, 1, @DateTo) + GROUP BY R.IssuedTo + ORDER BY COUNT(*) DESC; + """; + + public List GetMain(DateTime dateFrom, DateTime dateTo) => + Query(MainSql, dateFrom, dateTo); + + public List GetDisciplines(DateTime dateFrom, DateTime dateTo) => + Query(DisciplineSql, dateFrom, dateTo); + + public List GetRecipients(DateTime dateFrom, DateTime dateTo) => + Query(RecipientsSql, dateFrom, dateTo); + + private List Query(string sql, DateTime from, DateTime to) + { + using var conn = new SqlConnection(_context.Database.GetConnectionString()); + return conn.Query(sql, new { DateFrom = from, DateTo = to }).ToList(); + } + } +} \ No newline at end of file diff --git a/CPRNIMS.Domain/UIContracts/Inventory/IInventoryReports.cs b/CPRNIMS.Domain/UIContracts/Inventory/IInventoryReports.cs new file mode 100644 index 0000000..516e853 --- /dev/null +++ b/CPRNIMS.Domain/UIContracts/Inventory/IInventoryReports.cs @@ -0,0 +1,18 @@ +using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using CPRNIMS.Infrastructure.Dto.Inventory.Reports.Response; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Domain.UIContracts.Inventory +{ + public interface IInventoryReports + { + + Task GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct); + Task GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct); + Task GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct); + } +} diff --git a/CPRNIMS.Domain/UIContracts/Inventory/IMRS.cs b/CPRNIMS.Domain/UIContracts/Inventory/IMRS.cs new file mode 100644 index 0000000..db894dd --- /dev/null +++ b/CPRNIMS.Domain/UIContracts/Inventory/IMRS.cs @@ -0,0 +1,20 @@ +using CPRNIMS.Infrastructure.Dto.Common; +using CPRNIMS.Infrastructure.Dto.Inventory; +using CPRNIMS.Infrastructure.Dto.Inventory.Request; +using CPRNIMS.Infrastructure.Dto.Inventory.Response; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Domain.UIContracts.Inventory +{ + public interface IMRS + { + Task GetMRSPaged(MRSPagedRequest request, CancellationToken ct); + Task> CreateMRS(CreateMRSRequest request, CancellationToken ct); + Task> ApproveMRS(ApproveMRSRequest request, CancellationToken ct); + Task> CancelMRS(CancelMRSRequest request, CancellationToken ct); + } +} diff --git a/CPRNIMS.Domain/UIContracts/Inventory/IRIS.cs b/CPRNIMS.Domain/UIContracts/Inventory/IRIS.cs index 8df4e60..579610d 100644 --- a/CPRNIMS.Domain/UIContracts/Inventory/IRIS.cs +++ b/CPRNIMS.Domain/UIContracts/Inventory/IRIS.cs @@ -1,4 +1,5 @@ -using CPRNIMS.Infrastructure.Dto.Inventory.Request; +using CPRNIMS.Infrastructure.Dto.Common; +using CPRNIMS.Infrastructure.Dto.Inventory.Request; using CPRNIMS.Infrastructure.Dto.Inventory.Response; using CPRNIMS.Infrastructure.ViewModel.Inventory; using System; @@ -11,10 +12,9 @@ namespace CPRNIMS.Domain.UIContracts.Inventory { public interface IRIS { - Task ApproveRIS(ApproveRISRequest request, CancellationToken ct); - Task CancelRIS(CancelRISRequest request, CancellationToken ct); - Task CreateRIS(CreateRISRequest request, CancellationToken ct); - Task GetRISById(int risId, CancellationToken ct); + Task> ApproveRIS(ApproveRISRequest request, CancellationToken ct); + Task> CancelRIS(CancelRISRequest request, CancellationToken ct); + Task> CreateRIS(CreateRISRequest request, CancellationToken ct); Task GetRISPaged(RISPagedRequest request, CancellationToken ct); } } diff --git a/CPRNIMS.Domain/UIServices/Inventory/InventoryReports.cs b/CPRNIMS.Domain/UIServices/Inventory/InventoryReports.cs new file mode 100644 index 0000000..6c82c89 --- /dev/null +++ b/CPRNIMS.Domain/UIServices/Inventory/InventoryReports.cs @@ -0,0 +1,96 @@ +using CPRNIMS.Domain.UIContracts.Common; +using CPRNIMS.Domain.UIContracts.Inventory; +using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using CPRNIMS.Infrastructure.Dto.Inventory.Reports.Response; +using CPRNIMS.Infrastructure.Helper; +using Microsoft.Extensions.Configuration; +using System.Text; +using System.Text.Json; + +namespace CPRNIMS.Domain.UIServices.Inventory +{ + public class InventoryReports : IInventoryReports + { + private readonly IConfiguration _configuration; + private readonly TokenHelper _tokenHelper; + private readonly IApiConfigurationService _apiConfigurationService; + public InventoryReports(IConfiguration configuration, TokenHelper tokenHelper, IApiConfigurationService apiConfigurationService) + { + _configuration = configuration; + _tokenHelper = tokenHelper; + _apiConfigurationService = apiConfigurationService; + } + private static readonly JsonSerializerOptions _jsonOpts = new() + { + PropertyNameCaseInsensitive = true + }; + public async Task GetInventoryReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + { + var token = await _tokenHelper.GetValidTokenAsync(); + if (string.IsNullOrEmpty(token)) + throw new InvalidOperationException("Token has been expired."); + + var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetInventoryReport"] + ?? throw new InvalidOperationException("GetInventoryReport endpoint is not configured."); + + var qs = new StringBuilder(baseEndpoint).Append('?'); + qs.Append($"dateFrom={dateFrom:yyyy-MM-dd}&dateTo={dateTo:yyyy-MM-dd}"); + + using var http = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + var response = await http.GetAsync(qs.ToString(), ct); + var json = await response.Content.ReadAsStringAsync(ct); + + if (!response.IsSuccessStatusCode) + return new InventoryReportDto(); + + var result = JsonSerializer.Deserialize(json, _jsonOpts); + return result ?? new InventoryReportDto(); + } + + public async Task GetMRSReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + { + var token = await _tokenHelper.GetValidTokenAsync(); + if (string.IsNullOrEmpty(token)) + throw new InvalidOperationException("Token has been expired."); + + var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetMRSReport"] + ?? throw new InvalidOperationException("GetMRS endpoint is not configured."); + + var qs = new StringBuilder(baseEndpoint).Append('?'); + qs.Append($"dateFrom={dateFrom:yyyy-MM-dd}&dateTo={dateTo:yyyy-MM-dd}"); + + using var http = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + var response = await http.GetAsync(qs.ToString(), ct); + var json = await response.Content.ReadAsStringAsync(ct); + + if (!response.IsSuccessStatusCode) + return new MRSReportDto { }; + + var result = JsonSerializer.Deserialize(json, _jsonOpts); + return result ?? new MRSReportDto(); + } + + public async Task GetRISReportAsync(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + { + var token = await _tokenHelper.GetValidTokenAsync(); + if (string.IsNullOrEmpty(token)) + throw new InvalidOperationException("Token has been expired."); + + var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetRISReport"] + ?? throw new InvalidOperationException("GetMRS endpoint is not configured."); + + var qs = new StringBuilder(baseEndpoint).Append('?'); + qs.Append($"dateFrom={dateFrom:yyyy-MM-dd}&dateTo={dateTo:yyyy-MM-dd}"); + + using var http = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + var response = await http.GetAsync(qs.ToString(), ct); + var json = await response.Content.ReadAsStringAsync(ct); + + if (!response.IsSuccessStatusCode) + return new RISReportDto {}; + + var result = JsonSerializer.Deserialize(json, _jsonOpts); + return result ?? new RISReportDto(); + } + } +} diff --git a/CPRNIMS.Domain/UIServices/Inventory/MRS.cs b/CPRNIMS.Domain/UIServices/Inventory/MRS.cs new file mode 100644 index 0000000..7ccc98d --- /dev/null +++ b/CPRNIMS.Domain/UIServices/Inventory/MRS.cs @@ -0,0 +1,145 @@ +using CPRNIMS.Domain.Services; +using CPRNIMS.Domain.UIContracts.Common; +using CPRNIMS.Domain.UIContracts.Inventory; +using CPRNIMS.Infrastructure.Dto.Common; +using CPRNIMS.Infrastructure.Dto.Inventory; +using CPRNIMS.Infrastructure.Dto.Inventory.Request; +using CPRNIMS.Infrastructure.Dto.Inventory.Response; +using CPRNIMS.Infrastructure.Helper; +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace CPRNIMS.Domain.UIServices.Inventory +{ + public class MRS : IMRS + { + private readonly IConfiguration _configuration; + private readonly TokenHelper _tokenHelper; + private readonly IApiConfigurationService _apiConfigurationService; + public MRS(IConfiguration configuration, TokenHelper tokenHelper,IApiConfigurationService apiConfigurationService) + { + _configuration = configuration; + _tokenHelper = tokenHelper; + _apiConfigurationService = apiConfigurationService; + } + private static readonly JsonSerializerOptions _mrsJsonOpts = new() + { + PropertyNameCaseInsensitive = true + }; + + public async Task GetMRSPaged(MRSPagedRequest request, CancellationToken ct) + { + var token = await _tokenHelper.GetValidTokenAsync(); + if (string.IsNullOrEmpty(token)) + throw new InvalidOperationException("Token has been expired."); + + var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetMRS"] + ?? throw new InvalidOperationException("GetMRS endpoint is not configured."); + + var qs = new StringBuilder(baseEndpoint).Append('?'); + qs.Append($"pageNumber={request.PageNumber}&pageSize={request.PageSize}"); + + if (!string.IsNullOrWhiteSpace(request.SearchMRSNo)) + qs.Append($"&searchMRSNo={Uri.EscapeDataString(request.SearchMRSNo)}"); + if (!string.IsNullOrWhiteSpace(request.SearchRISNo)) + qs.Append($"&searchRISNo={Uri.EscapeDataString(request.SearchRISNo)}"); + if (!string.IsNullOrWhiteSpace(request.SearchItemName)) + qs.Append($"&searchItemName={Uri.EscapeDataString(request.SearchItemName)}"); + if (!string.IsNullOrWhiteSpace(request.SearchReturnedBy)) + qs.Append($"&searchReturnedBy={Uri.EscapeDataString(request.SearchReturnedBy)}"); + if (request.Status.HasValue) + qs.Append($"&status={request.Status.Value}"); + if (!string.IsNullOrWhiteSpace(request.Condition)) + qs.Append($"&condition={Uri.EscapeDataString(request.Condition)}"); + + using var http = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + var response = await http.GetAsync(qs.ToString(), ct); + var json = await response.Content.ReadAsStringAsync(ct); + + if (!response.IsSuccessStatusCode) + return new MRSPagedResponse { Data = [], RecordsTotal = 0 }; + + var result = JsonSerializer.Deserialize(json, _mrsJsonOpts); + return result ?? new MRSPagedResponse(); + } + + public async Task> CreateMRS(CreateMRSRequest request, CancellationToken ct) + { + var token = await _tokenHelper.GetValidTokenAsync(); + if (string.IsNullOrEmpty(token)) + throw new InvalidOperationException("Token has been expired."); + + var endpoint = _configuration["LLI:NonInvent:InventoryMgmt:CreateMRS"] + ?? throw new InvalidOperationException("CreateMRS endpoint is not configured."); + + using var http = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + using var content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); + + var response = await http.PostAsync(endpoint, content, ct); + var json = await response.Content.ReadAsStringAsync(ct); + + var result = JsonSerializer.Deserialize>(json, _mrsJsonOpts) + ?? new ApiResponse { success = false, message = $"HTTP {(int)response.StatusCode}" }; + + if (!response.IsSuccessStatusCode) + result.success = false; + + return result; + } + + public async Task> ApproveMRS(ApproveMRSRequest request, CancellationToken ct) + { + var token = await _tokenHelper.GetValidTokenAsync(); + if (string.IsNullOrEmpty(token)) + throw new InvalidOperationException("Token has been expired."); + + var endpoint = _configuration["LLI:NonInvent:InventoryMgmt:ApproveMRS"] + ?? throw new InvalidOperationException("ApproveMRS endpoint is not configured."); + + using var http = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + using var content = new StringContent( + JsonSerializer.Serialize(request), + Encoding.UTF8, "application/json"); + + var response = await http.PostAsync(endpoint, content, ct); + var json = await response.Content.ReadAsStringAsync(ct); + var result = JsonSerializer.Deserialize>(json, _mrsJsonOpts) + ?? new ApiResponse { success = false, message = $"HTTP {(int)response.StatusCode}" }; + + if (!response.IsSuccessStatusCode) + result.success = false; + + return result; + } + + public async Task> CancelMRS(CancelMRSRequest request, CancellationToken ct) + { + var token = await _tokenHelper.GetValidTokenAsync(); + if (string.IsNullOrEmpty(token)) + throw new InvalidOperationException("Token has been expired."); + + var endpoint = _configuration["LLI:NonInvent:InventoryMgmt:CancelMRS"] + ?? throw new InvalidOperationException("CancelMRS endpoint is not configured."); + + using var http = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + using var content = new StringContent( + JsonSerializer.Serialize(request), + Encoding.UTF8, "application/json"); + + var response = await http.PostAsync(endpoint, content, ct); + var json = await response.Content.ReadAsStringAsync(ct); + var result = JsonSerializer.Deserialize>(json, _mrsJsonOpts) + ?? new ApiResponse { success = false, message = $"HTTP {(int)response.StatusCode}" }; + + if (!response.IsSuccessStatusCode) + result.success = false; + + return result; + } + } +} diff --git a/CPRNIMS.Domain/UIServices/Inventory/RIS.cs b/CPRNIMS.Domain/UIServices/Inventory/RIS.cs index 19c1069..048788a 100644 --- a/CPRNIMS.Domain/UIServices/Inventory/RIS.cs +++ b/CPRNIMS.Domain/UIServices/Inventory/RIS.cs @@ -3,6 +3,7 @@ using CPRNIMS.Domain.UIContracts.Inventory; using CPRNIMS.Infrastructure.Dto.Common; using CPRNIMS.Infrastructure.Dto.Inventory.Request; using CPRNIMS.Infrastructure.Dto.Inventory.Response; +using CPRNIMS.Infrastructure.Dto.PR.Response; using CPRNIMS.Infrastructure.Helper; using Microsoft.Extensions.Configuration; using System; @@ -30,60 +31,51 @@ namespace CPRNIMS.Domain.UIServices.Inventory #region Get public async Task GetRISPaged(RISPagedRequest request, CancellationToken ct) { - try + var token = await _tokenHelper.GetValidTokenAsync(); + if (string.IsNullOrEmpty(token)) + throw new InvalidOperationException("Token has been expired."); + + var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetRIS"] + ?? throw new InvalidOperationException("GetRIS endpoint is not configured."); + + // ── Build query string — GET request, no body ── + var qs = new StringBuilder(baseEndpoint).Append('?'); + qs.Append($"pageNumber={request.PageNumber}"); + qs.Append($"&pageSize={request.PageSize}"); + + if (!string.IsNullOrWhiteSpace(request.SearchRISNo)) + qs.Append($"&searchRISNo={Uri.EscapeDataString(request.SearchRISNo)}"); + + if (!string.IsNullOrWhiteSpace(request.SearchItemName)) + qs.Append($"&searchItemName={Uri.EscapeDataString(request.SearchItemName)}"); + + if (!string.IsNullOrWhiteSpace(request.SearchIssuedTo)) + qs.Append($"&searchIssuedTo={Uri.EscapeDataString(request.SearchIssuedTo)}"); + + if (!string.IsNullOrWhiteSpace(request.Discipline)) + qs.Append($"&discipline={Uri.EscapeDataString(request.Discipline)}"); + + if (request.Status.HasValue) + qs.Append($"&status={request.Status.Value}"); + + using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); + + // ── GET, no content body ── + var response = await httpClient.GetAsync(qs.ToString(), ct); + var json = await response.Content.ReadAsStringAsync(ct); + + if (!response.IsSuccessStatusCode) { - var token = await _tokenHelper.GetValidTokenAsync(); - if (string.IsNullOrEmpty(token)) - throw new InvalidOperationException("Token has been expired."); - - var baseEndpoint = _configuration["LLI:NonInvent:InventoryMgmt:GetRIS"] - ?? throw new InvalidOperationException("GetRIS endpoint is not configured."); - - // ── Build query string — GET request, no body ── - var qs = new StringBuilder(baseEndpoint).Append('?'); - qs.Append($"pageNumber={request.PageNumber}"); - qs.Append($"&pageSize={request.PageSize}"); - - if (!string.IsNullOrWhiteSpace(request.SearchRISNo)) - qs.Append($"&searchRISNo={Uri.EscapeDataString(request.SearchRISNo)}"); - - if (!string.IsNullOrWhiteSpace(request.SearchItemName)) - qs.Append($"&searchItemName={Uri.EscapeDataString(request.SearchItemName)}"); - - if (!string.IsNullOrWhiteSpace(request.SearchIssuedTo)) - qs.Append($"&searchIssuedTo={Uri.EscapeDataString(request.SearchIssuedTo)}"); - - if (!string.IsNullOrWhiteSpace(request.Discipline)) - qs.Append($"&discipline={Uri.EscapeDataString(request.Discipline)}"); - - if (request.Status.HasValue) - qs.Append($"&status={request.Status.Value}"); - - using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); - - // ── GET, no content body ── - var response = await httpClient.GetAsync(qs.ToString(), ct); - var json = await response.Content.ReadAsStringAsync(ct); - - if (!response.IsSuccessStatusCode) - { - Console.WriteLine($"[GetRISPaged] Error {(int)response.StatusCode}: {json}"); - return new RISPagedResponse { Data = [], RecordsTotal = 0 }; - } - - var result = JsonSerializer.Deserialize(json, _jsonOptions); - return result ?? new RISPagedResponse(); + Console.WriteLine($"[GetRISPaged] Error {(int)response.StatusCode}: {json}"); + return new RISPagedResponse { Data = [], RecordsTotal = 0 }; } - catch (Exception ex) - { - ex.ToString(); - throw; - } - + + var result = JsonSerializer.Deserialize(json, _jsonOptions); + return result ?? new RISPagedResponse(); } #endregion #region Post Put - public async Task ApproveRIS(ApproveRISRequest request, CancellationToken ct) + public async Task> ApproveRIS(ApproveRISRequest request, CancellationToken ct) { var token = await _tokenHelper.GetValidTokenAsync(); if (string.IsNullOrEmpty(token)) @@ -97,15 +89,16 @@ namespace CPRNIMS.Domain.UIServices.Inventory var response = await httpClient.PutAsync(endpoint, content, ct); var json = await response.Content.ReadAsStringAsync(ct); + var result = JsonSerializer.Deserialize>(json, _jsonOptions) + ?? new ApiResponse { success = false, message = $"HTTP {(int)response.StatusCode}" }; if (!response.IsSuccessStatusCode) - { - return false; - } - return true; + result.success = false; + + return result; } - public async Task CancelRIS(CancelRISRequest request, CancellationToken ct) + public async Task> CancelRIS(CancelRISRequest request, CancellationToken ct) { var token = await _tokenHelper.GetValidTokenAsync(); if (string.IsNullOrEmpty(token)) @@ -115,19 +108,21 @@ namespace CPRNIMS.Domain.UIServices.Inventory ?? throw new InvalidOperationException("CancelRIS endpoint is not configured."); using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); - using var content = new StringContent( - JsonSerializer.Serialize(request),Encoding.UTF8,"application/json"); + using var content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); var response = await httpClient.PutAsync(endpoint, content, ct); var json = await response.Content.ReadAsStringAsync(ct); + var result = JsonSerializer.Deserialize>(json, _jsonOptions) + ?? new ApiResponse { success = false, message = $"HTTP {(int)response.StatusCode}" }; + if (!response.IsSuccessStatusCode) - { - return false; - } - return true; + result.success = false; + + return result; } - public async Task CreateRIS(CreateRISRequest request, CancellationToken ct) + + public async Task> CreateRIS(CreateRISRequest request, CancellationToken ct) { var token = await _tokenHelper.GetValidTokenAsync(); if (string.IsNullOrEmpty(token)) @@ -138,27 +133,19 @@ namespace CPRNIMS.Domain.UIServices.Inventory throw new InvalidOperationException("CreateRIS endpoint is not configured."); using var httpClient = _apiConfigurationService.CreateHttpClientWithDefaultHeaders(token); - using var content = new StringContent( - JsonSerializer.Serialize(request), - Encoding.UTF8, - "application/json"); + using var content = new StringContent(JsonSerializer.Serialize(request),Encoding.UTF8,"application/json"); var response = await httpClient.PostAsync(endpoint, content,ct); - if (!response.IsSuccessStatusCode) - { - return new RISResponse(); - } - var json = await response.Content.ReadAsStringAsync(); - var result = JsonSerializer.Deserialize>(json, _jsonOptions); - return result?.data ?? new RISResponse(); - } + var result = JsonSerializer.Deserialize>(json, _jsonOptions) + ?? new ApiResponse { success = false, message = $"HTTP {(int)response.StatusCode}" }; - public Task GetRISById(int risId, CancellationToken ct) - { - throw new NotImplementedException(); + if (!response.IsSuccessStatusCode) + result.success = false; + + return result; } private static readonly JsonSerializerOptions _jsonOptions = new() diff --git a/CPRNIMS.Infrastructure/Database/NonInventoryDbContext.cs b/CPRNIMS.Infrastructure/Database/NonInventoryDbContext.cs index 0cd59cf..82e7b57 100644 --- a/CPRNIMS.Infrastructure/Database/NonInventoryDbContext.cs +++ b/CPRNIMS.Infrastructure/Database/NonInventoryDbContext.cs @@ -198,7 +198,7 @@ namespace CPRNIMS.Infrastructure.Database b.HasOne(u => u.Attachment) .WithOne(a => a.ApplicationUser) .HasForeignKey(a => a.AttachmentId) - .IsRequired(false); // Allow null if there is no attachment + .IsRequired(false); }); modelBuilder.Entity(b => { @@ -206,7 +206,7 @@ namespace CPRNIMS.Infrastructure.Database b.HasOne(a => a.AttachmentExtention) .WithOne() .HasForeignKey(a => a.ExtensionId) - .IsRequired(false); // Allow null if there is no extension + .IsRequired(false); }); modelBuilder.Entity(b => { @@ -270,13 +270,23 @@ namespace CPRNIMS.Infrastructure.Database .HasForeignKey(i => i.LotId) .OnDelete(DeleteBehavior.Restrict); - modelBuilder.Entity(e => - { - e.HasMany(r => r.InventTransDetails) - .WithOne() - .HasForeignKey(t => t.InventTransId) - .OnDelete(DeleteBehavior.Restrict); - }); + modelBuilder.Entity() + .HasOne(i => i.Item) + .WithMany() + .HasForeignKey(i => i.ItemNo) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasOne(i => i.ItemCode) + .WithMany() + .HasForeignKey(i => i.ItemCodeId) + .OnDelete(DeleteBehavior.Restrict); + + modelBuilder.Entity() + .HasOne(i => i.ItemCategory) + .WithMany() + .HasForeignKey(i => i.ItemCategoryId) + .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity(e => { diff --git a/CPRNIMS.Infrastructure/Dto/Account/LoginResponse.cs b/CPRNIMS.Infrastructure/Dto/Account/LoginResponse.cs index 4412496..d7fe51e 100644 --- a/CPRNIMS.Infrastructure/Dto/Account/LoginResponse.cs +++ b/CPRNIMS.Infrastructure/Dto/Account/LoginResponse.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Security.Claims; using System.Text; using System.Threading.Tasks; @@ -20,6 +21,7 @@ namespace CPRNIMS.Infrastructure.Dto.Account public string URLAttachment { get; set; } = string.Empty; public string? token { get; set; } public string? company { get; set; } + public int? departmentId { get; set; } public string? refreshToken { get; set; } public DateTime expiresAt { get; set; } public int expiresInSeconds { get; set; } diff --git a/CPRNIMS.Infrastructure/Dto/Account/UserClaimsDto.cs b/CPRNIMS.Infrastructure/Dto/Account/UserClaimsDto.cs index 60dd00e..a4adf19 100644 --- a/CPRNIMS.Infrastructure/Dto/Account/UserClaimsDto.cs +++ b/CPRNIMS.Infrastructure/Dto/Account/UserClaimsDto.cs @@ -13,5 +13,6 @@ namespace CPRNIMS.Infrastructure.Dto.Account public string FullName { get; init; } = default!; public string Company { get; init; } = default!; public IReadOnlyList Roles { get; init; } = []; + public int? DepartmentId { get; set; } } } diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/MRSFilterDto.cs b/CPRNIMS.Infrastructure/Dto/Inventory/MRSFilterDto.cs index 0ef727e..8f100e0 100644 --- a/CPRNIMS.Infrastructure/Dto/Inventory/MRSFilterDto.cs +++ b/CPRNIMS.Infrastructure/Dto/Inventory/MRSFilterDto.cs @@ -8,7 +8,7 @@ namespace CPRNIMS.Infrastructure.Dto.Inventory { public class MRSFilterDto { - public int Page { get; set; } = 1; + public int PageNumber { get; set; } = 1; public int PageSize { get; set; } = 12; public string? SearchMRSNo { get; set; } public long? RISId { get; set; } diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedDto.cs b/CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedDto.cs new file mode 100644 index 0000000..35e4f5c --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedDto.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory +{ + public class MRSPagedDto + { + public long MRSId { get; set; } + public string MRSNo { get; set; } = string.Empty; + public long RISId { get; set; } + public string? RISNo { get; set; } + public string? ItemName { get; set; } + public string? ReturnedBy { get; set; } + public decimal QtyReturned { get; set; } + public string? Condition { get; set; } + public string? Remarks { get; set; } + public short Status { get; set; } + public string? StatusLabel { get; set; } + public string? CreatedBy { get; set; } + public DateTime CreatedDate { get; set; } + public string? ApprovedBy { get; set; } + public DateTime? ApprovedDate { get; set; } + public int InventoryId { get; set; } + public List Data { get; set; } = []; + } +} diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedRequest.cs b/CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedRequest.cs new file mode 100644 index 0000000..c1b6496 --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedRequest.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory +{ + public class MRSPagedRequest + { + public string? SearchMRSNo { get; set; } + public string? SearchRISNo { get; set; } + public string? SearchItemName { get; set; } + public string? SearchReturnedBy { get; set; } + public short? Status { get; set; } + public string? Condition { get; set; } + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 12; + } +} diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedResult.cs b/CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedResult.cs index 2f7e0cc..2dd28cd 100644 --- a/CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedResult.cs +++ b/CPRNIMS.Infrastructure/Dto/Inventory/MRSPagedResult.cs @@ -9,7 +9,7 @@ namespace CPRNIMS.Infrastructure.Dto.Inventory { public class MRSPagedResult { - public IEnumerable Data { get; set; } = []; + public IEnumerable Data { get; set; } = []; public int RecordsTotal { get; set; } public List DepartmentList { get; set; } = new List(); public List DisciplineList { get; set; } = new List(); diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Reports/RISReportDataDto.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/RISReportDataDto.cs new file mode 100644 index 0000000..3a36eb8 --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/RISReportDataDto.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory.Reports +{ + public class RISReportDataDto + { + public List Rows { get; set; } = new(); + public List Disciplines { get; set; } = new(); + public List Recipients { get; set; } = new(); + } + public class RISRowDto + { + public string? RISNo { get; set; } + public long? PRNo { get; set; } + public decimal QtyIssued { get; set; } + public string? IssuedTo { get; set; } + public string? StatusLabel { get; set; } + public string? CreatedBy { get; set; } + public string? ApprovedBy { get; set; } + public DateTime? ApprovedDate { get; set; } + public DateTime? CreatedDate { get; set; } + public string? DisciplineName { get; set; } + public string? ItemName { get; set; } + public long? ItemNo { get; set; } + public decimal QtyIn { get; set; } + public decimal QtyOut { get; set; } + public decimal QtyOnHand { get; set; } + public string? DepartmentName { get; set; } + public decimal TotalReturned { get; set; } + public int MRSCount { get; set; } + public decimal NetIssued { get; set; } + } + + public class DisciplineAggDto + { + public string? DisciplineName { get; set; } + public int SlipCount { get; set; } + } + + public class TopRecipientDto + { + public string? Name { get; set; } + public int SlipCount { get; set; } + public decimal QtyOut { get; set; } + } +} diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Reports/ReportDtos.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/ReportDtos.cs new file mode 100644 index 0000000..6b075ea --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/ReportDtos.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory.Reports +{ + public class RISReportDto + { + public string CompanyName { get; set; } = "Lloyd Laboratories Incorporated"; + public string PreparedBy { get; set; } = "Finance Department"; + public string ReportNo { get; set; } = string.Empty; + public DateTime DateFrom { get; set; } + public DateTime DateTo { get; set; } + + public RISReportSummary Summary { get; set; } = new(); + public List Rows { get; set; } = []; + public List ByDiscipline { get; set; } = []; + public List TopRecipients { get; set; } = []; + } + + public class RISReportSummary + { + public int TotalRIS { get; set; } + public int TotalApproved { get; set; } + public int TotalPending { get; set; } + public int TotalCancelled { get; set; } + public decimal TotalQtyIssued { get; set; } + public decimal TotalQtyReturned { get; set; } + public decimal TotalNetIssued { get; set; } + public decimal ApprovalRatePct { get; set; } + } + + public class RISReportRow + { + public string RISNo { get; set; } = string.Empty; + public DateTime CreatedDate { get; set; } + public string ItemName { get; set; } = string.Empty; + public long ItemNo { get; set; } + public string DisciplineName { get; set; } = string.Empty; + public string IssuedTo { get; set; } = string.Empty; + public decimal QtyIssued { get; set; } + public decimal TotalReturned { get; set; } + public decimal NetIssued { get; set; } + public short Status { get; set; } + public string StatusLabel { get; set; } = string.Empty; + } + + public class DisciplineCount + { + public string DisciplineName { get; set; } = string.Empty; + public int Count { get; set; } + } + + public class TopRecipient + { + public string IssuedTo { get; set; } = string.Empty; + public int SlipCount { get; set; } + public decimal TotalQty { get; set; } + } + + public class MRSReportDto + { + public string CompanyName { get; set; } = "Lloyd Laboratories Incorporated"; + public string PreparedBy { get; set; } = "Finance Department"; + public string ReportNo { get; set; } = string.Empty; + public DateTime DateFrom { get; set; } + public DateTime DateTo { get; set; } + + public MRSReportSummary Summary { get; set; } = new(); + public List Rows { get; set; } = []; + public List ByCondition { get; set; } = []; + } + + public class MRSReportSummary + { + public int TotalMRS { get; set; } + public decimal TotalQtyReturned { get; set; } + public decimal TotalQtyIssuedRIS { get; set; } + public decimal NetQtyConsumed { get; set; } + public decimal ReturnRatePct { get; set; } + public decimal GoodConditionPct { get; set; } + } + + public class MRSReportRow + { + public string MRSNo { get; set; } = string.Empty; + public DateTime CreatedDate { get; set; } + public string RISNo { get; set; } = string.Empty; + public string ItemName { get; set; } = string.Empty; + public string ReturnedBy { get; set; } = string.Empty; + public decimal QtyReturned { get; set; } + public string Condition { get; set; } = string.Empty; + public short Status { get; set; } + public string StatusLabel { get; set; } = string.Empty; + } + + public class ConditionTotal + { + public string Condition { get; set; } = string.Empty; + public decimal TotalQty { get; set; } + } + + public class InventoryReportDto + { + public string CompanyName { get; set; } = "Lloyd Laboratories Incorporated"; + public string PreparedBy { get; set; } = "Finance Department"; + public string ReportNo { get; set; } = string.Empty; + public DateTime AsOf { get; set; } + + public InventoryReportSummary Summary { get; set; } = new(); + public List Rows { get; set; } = []; + public List ByCategory { get; set; } = []; + public List Alerts { get; set; } = []; + } + + public class InventoryReportSummary + { + public int TotalSKUs { get; set; } + public decimal TotalOnHand { get; set; } + public decimal TotalQtyIn { get; set; } + public decimal TotalQtyOut { get; set; } + public int LowStockCount { get; set; } + public int OutOfStockCount { get; set; } + } + + public class InventoryReportRow + { + public string ItemName { get; set; } = string.Empty; + public long ItemNo { get; set; } + public string ItemCategoryName { get; set; } = string.Empty; + public string? LotNo { get; set; } + public decimal QtyIn { get; set; } + public decimal QtyOut { get; set; } + public decimal QtyOnHand { get; set; } + public int StockPct { get; set; } + } + + public class CategoryStockLevel + { + public string CategoryName { get; set; } = string.Empty; + public int AvgStockPct { get; set; } + } + + public class InventoryAlert + { + public string ItemName { get; set; } = string.Empty; + public decimal QtyOnHand { get; set; } + public string Severity { get; set; } = string.Empty; // "Critical" | "Low" + } +} diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Reports/Response/InventoryData.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/Response/InventoryData.cs new file mode 100644 index 0000000..240189c --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/Response/InventoryData.cs @@ -0,0 +1,14 @@ +using CPRNIMS.Infrastructure.Dto.Inventory.Response; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory.Reports.Response +{ + public class InventoryData + { + public List Data { get; set; } = []; + } +} diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Reports/Response/MRSData.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/Response/MRSData.cs new file mode 100644 index 0000000..a4a0c98 --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/Response/MRSData.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory.Reports.Response +{ + public class MRSData + { + public List Data { get; set; } = []; + } +} diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Reports/Response/RISData.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/Response/RISData.cs new file mode 100644 index 0000000..973fcbc --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Reports/Response/RISData.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory.Reports.Response +{ + public class RISData + { + public List Data { get; set; } = []; + } +} diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Request/ApproveMRSRequest.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Request/ApproveMRSRequest.cs new file mode 100644 index 0000000..c888232 --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Request/ApproveMRSRequest.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory.Request +{ + public class ApproveMRSRequest { public long MRSId { get; set; } } +} diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Request/CancelMRSRequest.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Request/CancelMRSRequest.cs new file mode 100644 index 0000000..c66e529 --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Request/CancelMRSRequest.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory.Request +{ + public class CancelMRSRequest + { + public long MRSId { get; set; } + public string Reason { get; set; } = string.Empty; + } +} diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Response/MRSPagedResponse.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Response/MRSPagedResponse.cs new file mode 100644 index 0000000..a13c16d --- /dev/null +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Response/MRSPagedResponse.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Dto.Inventory.Response +{ + public class MRSPagedResponse + { + public List Data { get; set; } = []; + public int RecordsTotal { get; set; } + } + public class MRSListItem + { + public long MRSId { get; set; } + public string MRSNo { get; set; } = string.Empty; + public long RISId { get; set; } + public string? RISNo { get; set; } + public string? ItemName { get; set; } + public string? ReturnedBy { get; set; } + public decimal QtyReturned { get; set; } + public string? Condition { get; set; } + public string? Remarks { get; set; } + public short Status { get; set; } + public string? StatusLabel { get; set; } + public string? CreatedBy { get; set; } + public DateTime CreatedDate { get; set; } + public string? ApprovedBy { get; set; } + public DateTime? ApprovedDate { get; set; } + public int InventoryId { get; set; } + } +} diff --git a/CPRNIMS.Infrastructure/Dto/Inventory/Response/MRSResponse.cs b/CPRNIMS.Infrastructure/Dto/Inventory/Response/MRSResponse.cs index aa5ee86..3283ac1 100644 --- a/CPRNIMS.Infrastructure/Dto/Inventory/Response/MRSResponse.cs +++ b/CPRNIMS.Infrastructure/Dto/Inventory/Response/MRSResponse.cs @@ -1,28 +1,17 @@ -using System; +using CPRNIMS.Infrastructure.Models.Inventory; +using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.Json.Serialization; using System.Threading.Tasks; namespace CPRNIMS.Infrastructure.Dto.Inventory.Response { public class MRSResponse { - public long MRSId { get; set; } - public string MRSNo { get; set; } = string.Empty; - public long RISId { get; set; } - public string RISNo { get; set; } = string.Empty; - public int InventoryId { get; set; } - public string ItemName { get; set; } = string.Empty; - public string ReturnedBy { get; set; } = string.Empty; - public decimal QtyReturned { get; set; } - public string? Condition { get; set; } - public string? Remarks { get; set; } - public short Status { get; set; } - public string StatusLabel => Status switch { 0 => "Draft", 1 => "Approved", 2 => "Cancelled", _ => "Unknown" }; - public string CreatedBy { get; set; } = string.Empty; - public DateTime CreatedDate { get; set; } - public string? ApprovedBy { get; set; } - public DateTime? ApprovedDate { get; set; } + [JsonPropertyName("success")] public bool Success { get; set; } + [JsonPropertyName("message")] public string? Message { get; set; } + [JsonPropertyName("data")] public MRSData? Data { get; set; } } } diff --git a/CPRNIMS.Infrastructure/Entities/Inventory/InventTrans.cs b/CPRNIMS.Infrastructure/Entities/Inventory/InventTrans.cs index 637ba82..542d8bf 100644 --- a/CPRNIMS.Infrastructure/Entities/Inventory/InventTrans.cs +++ b/CPRNIMS.Infrastructure/Entities/Inventory/InventTrans.cs @@ -17,6 +17,8 @@ namespace CPRNIMS.Infrastructure.Entities.Inventory public long RRNo { get; set; } public string CreatedBy { get; set; }=string.Empty; public bool IsActive { get; set; } + public Inventory? Inventory { get; set; } + // public InventTransDetail? InventTransDetail { get; set; } public ICollection InventTransDetails { get; set; } = []; } } diff --git a/CPRNIMS.Infrastructure/Entities/Inventory/InventTransDetail.cs b/CPRNIMS.Infrastructure/Entities/Inventory/InventTransDetail.cs index 3ad7002..16e8762 100644 --- a/CPRNIMS.Infrastructure/Entities/Inventory/InventTransDetail.cs +++ b/CPRNIMS.Infrastructure/Entities/Inventory/InventTransDetail.cs @@ -1,4 +1,5 @@ -using CPRNIMS.Infrastructure.Entities.Purchasing; +using CPRNIMS.Infrastructure.Entities.Items; +using CPRNIMS.Infrastructure.Entities.Purchasing; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -24,5 +25,7 @@ namespace CPRNIMS.Infrastructure.Entities.Inventory public bool IsActive { get; set; } public long PRDetailId { get; set; } public PRDetails? PRDetails { get; set; } + public ItemCategory? ItemCategory { get; set; } + public InventTrans? InventTrans { get; set; } } } diff --git a/CPRNIMS.Infrastructure/Entities/Inventory/Inventory.cs b/CPRNIMS.Infrastructure/Entities/Inventory/Inventory.cs index 6254c2d..08385d1 100644 --- a/CPRNIMS.Infrastructure/Entities/Inventory/Inventory.cs +++ b/CPRNIMS.Infrastructure/Entities/Inventory/Inventory.cs @@ -1,4 +1,5 @@ using CPRNIMS.Infrastructure.Entities.Account; +using CPRNIMS.Infrastructure.Entities.Items; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -24,5 +25,6 @@ namespace CPRNIMS.Infrastructure.Entities.Inventory public Lot? Lot { get; set; } public ICollection InventTrans { get; set; } = []; public ApplicationUser? User { get; set; } + public Item? Item { get; set; } } } diff --git a/CPRNIMS.Infrastructure/Entities/Inventory/MRS.cs b/CPRNIMS.Infrastructure/Entities/Inventory/MRS.cs index 352038d..3b90b3f 100644 --- a/CPRNIMS.Infrastructure/Entities/Inventory/MRS.cs +++ b/CPRNIMS.Infrastructure/Entities/Inventory/MRS.cs @@ -42,5 +42,4 @@ namespace CPRNIMS.Infrastructure.Entities.Inventory public RIS RIS { get; set; } = null!; public Inventory Inventory { get; set; } = null!; } - } diff --git a/CPRNIMS.Infrastructure/Entities/Items/Item.cs b/CPRNIMS.Infrastructure/Entities/Items/Item.cs index daf1a3a..2bbff2a 100644 --- a/CPRNIMS.Infrastructure/Entities/Items/Item.cs +++ b/CPRNIMS.Infrastructure/Entities/Items/Item.cs @@ -1,13 +1,7 @@ -using CPRNIMS.Infrastructure.Entities.Account; -using CPRNIMS.Infrastructure.Entities.Common; -using System; -using System.Collections.Generic; +using CPRNIMS.Infrastructure.Entities.Common; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static System.Runtime.InteropServices.JavaScript.JSType; + namespace CPRNIMS.Infrastructure.Entities.Items { @@ -42,5 +36,6 @@ namespace CPRNIMS.Infrastructure.Entities.Items public string? ItemAttachPath { get; set; } public bool IsMDLD { get; set; } public bool CheckBox { get; set; } + public ItemCode? ItemCode { get; set; } } } diff --git a/CPRNIMS.Infrastructure/Entities/Items/ItemCode.cs b/CPRNIMS.Infrastructure/Entities/Items/ItemCode.cs index d47a36d..3cb2c84 100644 --- a/CPRNIMS.Infrastructure/Entities/Items/ItemCode.cs +++ b/CPRNIMS.Infrastructure/Entities/Items/ItemCode.cs @@ -23,5 +23,7 @@ namespace CPRNIMS.Infrastructure.Entities.Items public byte RequestTypeId { get; set; } public bool IsActive { get; set; } public int CartItemCount { get; set; } + public short ItemCategoryId { get; set; } + public ItemCategory? ItemCategory { get; set; } } } diff --git a/CPRNIMS.Infrastructure/Entities/Purchasing/PR.cs b/CPRNIMS.Infrastructure/Entities/Purchasing/PR.cs index c33169f..fa5bdcd 100644 --- a/CPRNIMS.Infrastructure/Entities/Purchasing/PR.cs +++ b/CPRNIMS.Infrastructure/Entities/Purchasing/PR.cs @@ -6,7 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; -using static System.Runtime.InteropServices.JavaScript.JSType; + namespace CPRNIMS.Infrastructure.Entities.Purchasing { diff --git a/CPRNIMS.Infrastructure/Entities/Purchasing/PRDetails.cs b/CPRNIMS.Infrastructure/Entities/Purchasing/PRDetails.cs index b3d2c0c..cb43ea7 100644 --- a/CPRNIMS.Infrastructure/Entities/Purchasing/PRDetails.cs +++ b/CPRNIMS.Infrastructure/Entities/Purchasing/PRDetails.cs @@ -1,4 +1,5 @@ using CPRNIMS.Infrastructure.Entities.Common; +using CPRNIMS.Infrastructure.Entities.Items; using CPRNIMS.Infrastructure.ViewModel.Items; using System; using System.Collections.Generic; @@ -24,5 +25,6 @@ namespace CPRNIMS.Infrastructure.Entities.Purchasing public decimal Qty { get; set; } public bool IsSearched { get; set; } public PR? PRs { get; set; } + public ItemCategory? ItemCategory { get; set; } } } diff --git a/CPRNIMS.Infrastructure/Models/Inventory/MRSData.cs b/CPRNIMS.Infrastructure/Models/Inventory/MRSData.cs new file mode 100644 index 0000000..298d1e0 --- /dev/null +++ b/CPRNIMS.Infrastructure/Models/Inventory/MRSData.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace CPRNIMS.Infrastructure.Models.Inventory +{ + public class MRSData + { + [JsonPropertyName("mrsId")] public long MRSId { get; set; } + [JsonPropertyName("mrsNo")] public string? MRSNo { get; set; } + [JsonPropertyName("risId")] public long RISId { get; set; } + [JsonPropertyName("inventoryId")] public int InventoryId { get; set; } + [JsonPropertyName("returnedBy")] public string? ReturnedBy { get; set; } + [JsonPropertyName("qtyReturned")] public decimal QtyReturned { get; set; } + [JsonPropertyName("condition")] public string? Condition { get; set; } + [JsonPropertyName("remarks")] public string? Remarks { get; set; } + [JsonPropertyName("status")] public short Status { get; set; } + [JsonPropertyName("createdBy")] public string? CreatedBy { get; set; } + [JsonPropertyName("createdDate")] public DateTime CreatedDate { get; set; } + [JsonPropertyName("approvedBy")] public string? ApprovedBy { get; set; } + [JsonPropertyName("approvedDate")] public DateTime? ApprovedDate { get; set; } + } +} diff --git a/CPRNIMS.WebApi/CPRNIMS.WebApi.csproj b/CPRNIMS.WebApi/CPRNIMS.WebApi.csproj index e381846..d14d03a 100644 --- a/CPRNIMS.WebApi/CPRNIMS.WebApi.csproj +++ b/CPRNIMS.WebApi/CPRNIMS.WebApi.csproj @@ -29,6 +29,9 @@ + + + diff --git a/CPRNIMS.WebApi/CPRNIMS.WebApi.csproj.user b/CPRNIMS.WebApi/CPRNIMS.WebApi.csproj.user index 3340438..33a88a5 100644 --- a/CPRNIMS.WebApi/CPRNIMS.WebApi.csproj.user +++ b/CPRNIMS.WebApi/CPRNIMS.WebApi.csproj.user @@ -2,8 +2,8 @@ https - MvcControllerEmptyScaffolder - root/Common/MVC/Controller + ApiControllerEmptyScaffolder + root/Common/Api D:\sourcecode\NonInventPurchasing\CPRNIMS.WebApi\Properties\PublishProfiles\FolderProfile1.pubxml diff --git a/CPRNIMS.WebApi/Common/ServiceExtensions.cs b/CPRNIMS.WebApi/Common/ServiceExtensions.cs index 56889d1..e21abd0 100644 --- a/CPRNIMS.WebApi/Common/ServiceExtensions.cs +++ b/CPRNIMS.WebApi/Common/ServiceExtensions.cs @@ -1,15 +1,18 @@ using CPRNIMS.Domain.Contracts.Account; using CPRNIMS.Domain.Contracts.Canvass; +using CPRNIMS.Domain.Contracts.Common; using CPRNIMS.Domain.Contracts.Finance; using CPRNIMS.Domain.Contracts.Inventory; using CPRNIMS.Domain.Contracts.Items; using CPRNIMS.Domain.Contracts.PO; using CPRNIMS.Domain.Contracts.PR; using CPRNIMS.Domain.Contracts.Receiving; +using CPRNIMS.Domain.Contracts.Reports; using CPRNIMS.Domain.Contracts.SMTP; using CPRNIMS.Domain.Services; using CPRNIMS.Domain.Services.Account; using CPRNIMS.Domain.Services.Canvass; +using CPRNIMS.Domain.Services.Common; using CPRNIMS.Domain.Services.Finance; using CPRNIMS.Domain.Services.Inventory; using CPRNIMS.Domain.Services.PO; @@ -18,6 +21,7 @@ using CPRNIMS.Domain.Services.SMTP; using CPRNIMS.Infrastructure.Database; using CPRNIMS.Infrastructure.Entities.Account; using CPRNIMS.Infrastructure.Helper; +using CPRNIMS.Infrastructure.Reports; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; @@ -89,7 +93,6 @@ namespace CPRNIMS.WebApi.Common sql.EnableRetryOnFailure(5, TimeSpan.FromHours(2), null); sql.CommandTimeout(20); })); - services.AddDbContext(options => options.UseSqlServer(localConn, sql => { @@ -147,6 +150,7 @@ namespace CPRNIMS.WebApi.Common { services.AddMemoryCache(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -154,7 +158,8 @@ namespace CPRNIMS.WebApi.Common services.AddScoped(); services.AddScoped(); services.AddScoped(); - + services.AddScoped(); + services.AddScoped(); #region Automation using LLM services.AddHttpClient(); #endregion diff --git a/CPRNIMS.WebApi/Controllers/Account/AnonController.cs b/CPRNIMS.WebApi/Controllers/Account/AnonController.cs index e582c34..14770f0 100644 --- a/CPRNIMS.WebApi/Controllers/Account/AnonController.cs +++ b/CPRNIMS.WebApi/Controllers/Account/AnonController.cs @@ -67,6 +67,7 @@ namespace CPRNIMS.WebApi.Controllers.Account userId = user.Id, userName = user.UserName, fullName = user.FullName, + departmentId = user.DepartmentId, email = user.Email, phoneNumber = user.PhoneNumber, company = user.Company, diff --git a/CPRNIMS.WebApi/Controllers/Inventory/InventoryMgmtController.cs b/CPRNIMS.WebApi/Controllers/Inventory/InventoryMgmtController.cs index dcf6f2e..46ed6f4 100644 --- a/CPRNIMS.WebApi/Controllers/Inventory/InventoryMgmtController.cs +++ b/CPRNIMS.WebApi/Controllers/Inventory/InventoryMgmtController.cs @@ -2,7 +2,6 @@ using CPRNIMS.Domain.Services; using CPRNIMS.Infrastructure.Dto.Inventory; using CPRNIMS.Infrastructure.Dto.Inventory.Request; -using CPRNIMS.Infrastructure.Dto.PR; using CPRNIMS.WebApi.Controllers.Base; using Microsoft.AspNetCore.Mvc; diff --git a/CPRNIMS.WebApi/Controllers/Inventory/InventoryReportsController.cs b/CPRNIMS.WebApi/Controllers/Inventory/InventoryReportsController.cs new file mode 100644 index 0000000..241c5a9 --- /dev/null +++ b/CPRNIMS.WebApi/Controllers/Inventory/InventoryReportsController.cs @@ -0,0 +1,66 @@ +using CPRNIMS.Domain.Contracts.Inventory; +using CPRNIMS.Domain.Contracts.Reports; +using CPRNIMS.Infrastructure.Dto.Inventory.Reports; +using CPRNIMS.WebApi.Security; +using Microsoft.AspNetCore.Mvc; + +namespace CPRNIMS.WebApi.Controllers.Inventory +{ + [Route("api/[controller]")] + [ApiController] + public class InventoryReportsController : ControllerBase + { + private readonly IReportDataService _data; + private readonly IInventoryReports _reports; + public InventoryReportsController(IReportDataService data, IInventoryReports reports) + { + _data = data; + _reports = reports; + } + + [HttpGet("GetRISReport")] + public async Task GetRISReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + { + var currentUser = User.ToUserClaims(); + if (currentUser == null) + return BadRequest(); + + var data = await _reports.GetRISReportAsync(dateFrom, dateTo, ct, currentUser.DepartmentId, currentUser.UserName); + return Ok(data); + } + + [HttpGet("GetMRSReport")] + public async Task GetMRSReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + { + var currentUser = User.ToUserClaims(); + if (currentUser == null) + return BadRequest(); + + var data = await _reports.GetMRSReportAsync(dateFrom, dateTo, ct, currentUser.DepartmentId, currentUser.UserName); + return Ok(data); + } + + [HttpGet("GetInventoryReport")] + public async Task GetInventoryReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + { + var currentUser = User.ToUserClaims(); + if (currentUser == null) + return BadRequest(); + + var data = await _reports.GetInventoryReportAsync(dateFrom, dateTo, ct, currentUser.DepartmentId, currentUser.UserName); + return Ok(data); + } + + [HttpGet("GetRISData")] + public IActionResult GetData([FromQuery] DateTime dateFrom, [FromQuery] DateTime dateTo,CancellationToken ct) + { + var result = new RISReportDataDto + { + Rows = _data.GetMain(dateFrom, dateTo), + Disciplines = _data.GetDisciplines(dateFrom, dateTo), + Recipients = _data.GetRecipients(dateFrom, dateTo) + }; + return Ok(result); + } + } +} diff --git a/CPRNIMS.WebApi/Controllers/Inventory/MRSMgmtController.cs b/CPRNIMS.WebApi/Controllers/Inventory/MRSMgmtController.cs index 9fe8990..8f907f7 100644 --- a/CPRNIMS.WebApi/Controllers/Inventory/MRSMgmtController.cs +++ b/CPRNIMS.WebApi/Controllers/Inventory/MRSMgmtController.cs @@ -1,12 +1,63 @@ -using Microsoft.AspNetCore.Mvc; +using CPRNIMS.Domain.Contracts.Inventory; +using CPRNIMS.Infrastructure.Dto.Inventory; +using CPRNIMS.Infrastructure.Dto.Inventory.Request; +using CPRNIMS.WebApi.Security; +using Microsoft.AspNetCore.Mvc; namespace CPRNIMS.WebApi.Controllers.Inventory { - public class MRSMgmtController : Controller + [Route("api/[controller]")] + [ApiController] + public partial class MRSMgmtController : ControllerBase { - public IActionResult Index() + private readonly IMRS _mrs; + public MRSMgmtController(IMRS mrs) => _mrs = mrs; + + [HttpGet] + public async Task GetMRS([FromQuery] MRSFilterDto filter, CancellationToken ct) { - return View(); + var currentUser = User.ToUserClaims(); + if (currentUser == null) + return BadRequest(); + + var result = await _mrs.GetPagedAsync(filter, ct, currentUser.DepartmentId, currentUser.UserName); + return Ok(result); + } + [HttpPost()] + public async Task CreateMRS([FromBody] CreateMRSRequest request, CancellationToken ct) + { + var currentUser = User.ToUserClaims(); + if (currentUser == null) + return BadRequest(); + + var result = await _mrs.CreateAsync(request, currentUser.UserName, ct); + return Ok(new { success = true, message = $"MRS# {result.MRSNo} created successfully." }); + } + + [HttpPost("Approve")] + public async Task ApproveMRS([FromBody] ApproveMRSRequest request, CancellationToken ct) + { + var currentUser = User.ToUserClaims(); + if (currentUser == null) + return BadRequest(); + + await _mrs.ApproveAsync(request.MRSId, currentUser.UserName, ct); + return Ok(new { success = true, message = "MRS approved successfully." }); + } + + [HttpPost("Cancel")] + public async Task CancelMRS([FromBody] CancelMRSRequest request, CancellationToken ct) + { + if (string.IsNullOrWhiteSpace(request.Reason)) + return BadRequest(new { success = false, message = "A reason for cancellation is required." }); + + var currentUser = User.ToUserClaims(); + if (currentUser == null) + return BadRequest(); + + await _mrs.CancelAsync(request, currentUser.UserName, ct); + return Ok(new {success = true,message = "MRS cancelled and inventory adjusted."}); + } } } diff --git a/CPRNIMS.WebApi/Controllers/Inventory/RISMgmtController.cs b/CPRNIMS.WebApi/Controllers/Inventory/RISMgmtController.cs index dea47b2..a0ed2c9 100644 --- a/CPRNIMS.WebApi/Controllers/Inventory/RISMgmtController.cs +++ b/CPRNIMS.WebApi/Controllers/Inventory/RISMgmtController.cs @@ -4,7 +4,6 @@ using CPRNIMS.Infrastructure.Dto.Inventory; using CPRNIMS.Infrastructure.Dto.Inventory.Request; using CPRNIMS.WebApi.Security; using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; namespace CPRNIMS.WebApi.Controllers.Inventory { @@ -20,7 +19,11 @@ namespace CPRNIMS.WebApi.Controllers.Inventory [HttpGet("GetRIS")] public async Task GetRIS([FromQuery] RISFilterDto filter, CancellationToken ct) { - var result = await _risRepo.GetPagedAsync(filter,ct); + var currentUser = User.ToUserClaims(); + if (currentUser == null) + return BadRequest(); + + var result = await _risRepo.GetPagedAsync(filter,ct, currentUser.DepartmentId, currentUser.UserName); return Ok(result); } @@ -48,12 +51,7 @@ namespace CPRNIMS.WebApi.Controllers.Inventory }); var ris = await _risRepo.CreateAsync(request, currentUser.UserName,ct); - return Ok(new - { - success = true, - message = $"RIS {ris.RISNo} created successfully.", - data= ris - }); + return Ok(new { success = true, message = $"RIS# {ris.RISNo} created successfully." }); } [HttpPut("ApproveRIS")] @@ -69,7 +67,11 @@ namespace CPRNIMS.WebApi.Controllers.Inventory [HttpPut("CancelRIS")] public async Task CancelRIS([FromBody] CancelRISRequest request, CancellationToken ct) { - await _risRepo.CancelAsync(request, ct); + var currentUser = User.ToUserClaims(); + if (currentUser == null) + return BadRequest(); + + await _risRepo.CancelAsync(request, currentUser.UserName, ct); return Ok(new { success = true, message = "RIS cancelled and inventory restored." }); } } diff --git a/CPRNIMS.WebApi/Program.cs b/CPRNIMS.WebApi/Program.cs index b804e42..152a480 100644 --- a/CPRNIMS.WebApi/Program.cs +++ b/CPRNIMS.WebApi/Program.cs @@ -5,6 +5,7 @@ using CPRNIMS.WebApi.Common; var builder = WebApplication.CreateBuilder(args); builder.Services.AddApplicationServices(builder); +builder.Services.AddFastReport(); builder.Services.AddAutoMapper(cfg => { }, typeof(SupplierRequestProfile)); @@ -30,6 +31,8 @@ app.UseCors("AllowAnyOrigin"); app.UseHttpsRedirection(); +app.UseFastReport(); + app.UseAuthentication(); app.UseAuthorization(); diff --git a/CPRNIMS.WebApi/Security/ClaimsPrincipalExtensions.cs b/CPRNIMS.WebApi/Security/ClaimsPrincipalExtensions.cs index 80b6e90..7bd640b 100644 --- a/CPRNIMS.WebApi/Security/ClaimsPrincipalExtensions.cs +++ b/CPRNIMS.WebApi/Security/ClaimsPrincipalExtensions.cs @@ -16,6 +16,7 @@ namespace CPRNIMS.WebApi.Security UserName = user.FindFirstValue(ClaimTypes.Name) ?? "", FullName = user.FindFirstValue("FullName") ?? "", Company = user.FindFirstValue("Company") ?? "", + DepartmentId = Convert.ToInt32(user.FindFirstValue("DepartmentId")), Roles = user.FindAll(ClaimTypes.Role) .Select(r => r.Value) .ToList() diff --git a/CPRNIMS.WebApi/Sql/Phase 5/StoredProc.sql b/CPRNIMS.WebApi/Sql/Phase 5/StoredProc.sql index 1ed4e0f..b22b571 100644 --- a/CPRNIMS.WebApi/Sql/Phase 5/StoredProc.sql +++ b/CPRNIMS.WebApi/Sql/Phase 5/StoredProc.sql @@ -1,13 +1,13 @@ USE [CPRNIMS] GO -/****** Object: StoredProcedure [dbo].[GetInventory] Script Date: 6/11/2026 9:29:29 AM ******/ +/****** Object: StoredProcedure [dbo].[GetInventory] Script Date: 6/18/2026 3:09:49 PM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO ALTER PROCEDURE [dbo].[GetInventory] ( - @UserId VARCHAR(450)='89da2977-c70f-4df9-94d4-9a610aa999ea', + @UserId VARCHAR(450)='16b37c00-131f-4205-b8f6-ad4d0f9f3a32', @SearchPRNo VARCHAR(50) = '', @SearchItemNo VARCHAR(50) = '', @SearchItemName VARCHAR(100) = '', @@ -20,6 +20,18 @@ AS BEGIN SET NOCOUNT ON; DECLARE @Offset INT = (@PageNumber - 1) * @PageSize; + DECLARE @HasFullAccess BIT = 0; + DECLARE @UserDepartmentId INT,@UserName VARCHAR(50); + + SELECT @UserDepartmentId = DepartmentId,@UserName=UserName + FROM dbo.Users + WHERE Id = @UserId; + + IF @UserName IN ('LSKRISUR24','LSCYNDIZ25','LSJONTAN25','LHRIOCAS24') + SET @HasFullAccess = 1; + + IF @UserDepartmentId = 6 + SET @HasFullAccess = 1; -- ── 1. Full department list — unaffected by any filter ────────────── SELECT DISTINCT D.Department @@ -49,42 +61,30 @@ BEGIN , ICAT.ItemCategoryName , RRD.RemainingQty , ITD.CreatedDate - ,COALESCE(PCD.ProjectCode,'N/A') ProjectCode + ,COALESCE(PC.ProjectCode,'N/A') ProjectCode INTO #Inventory FROM dbo.Inventory IV - INNER JOIN dbo.InventTrans IT - ON IV.InventoryId = IT.InventoryId - INNER JOIN dbo.InventTransDetail ITD - ON IT.InventTransId = ITD.InventTransId - INNER JOIN dbo.PRDetails PRD - ON ITD.PRDetailId = PRD.PRDetailsId - AND PRD.IsActive = 1 - INNER JOIN dbo.RRDetails RRD - ON PRD.PRDetailsId = RRD.PRDetailId - AND RRD.IsActive = 1 - INNER JOIN dbo.ItemCategories ICAT - ON PRD.ItemCategoryId = ICAT.ItemCategoryId - INNER JOIN dbo.Lot L - ON IV.LotId = L.LotId - INNER JOIN dbo.LotType LT - ON L.LotTypeId = LT.LotTypeId - INNER JOIN dbo.PR PR - ON PR.UserId = IV.UserId - AND PR.IsActive = 1 - INNER JOIN dbo.ProjectCodes PC - ON PR.ProjectCodeId = PC.ProjectCodeId - INNER JOIN dbo.Users U - ON IV.UserId = U.Id - INNER JOIN dbo.Departments D - ON U.DepartmentId = D.DepartmentId - INNER JOIN dbo.ProjectCodes PCD - ON PR.ProjectCodeId = PCD.ProjectCodeId - WHERE ITD.TransTypeId=2 AND IV.IsActive=1 AND ITD.IsActive=1 AND - (@SearchPRNo = '' OR PR.PRNo = @SearchPRNo) - AND (@SearchItemNo = '' OR PRD.ItemNo = @SearchItemNo) - AND (@SearchItemName = '' OR PRD.ItemName LIKE '%' + @SearchItemName + '%') - AND (@SearchDept = '' OR D.Department LIKE '%' + @SearchDept + '%') - AND (@SearchProjectCode = '' OR PC.ProjectCode LIKE '%' + @SearchProjectCode + '%') + INNER JOIN dbo.InventTrans IT ON IV.InventoryId = IT.InventoryId + INNER JOIN dbo.InventTransDetail ITD ON IT.InventTransId = ITD.InventTransId + INNER JOIN dbo.PRDetails PRD ON ITD.PRDetailId = PRD.PRDetailsId AND PRD.IsActive = 1 + INNER JOIN dbo.PR ON PR.PRId=PRD.PRId AND PR.IsActive = 1 + INNER JOIN dbo.RRDetails RRD ON PRD.PRDetailsId = RRD.PRDetailId AND RRD.IsActive = 1 + INNER JOIN dbo.ItemCategories ICAT ON PRD.ItemCategoryId = ICAT.ItemCategoryId + LEFT JOIN dbo.Lot L ON IV.LotId = L.LotId + LEFT JOIN dbo.LotType LT ON L.LotTypeId = LT.LotTypeId + LEFT JOIN dbo.ProjectCodes PC ON PR.ProjectCodeId = PC.ProjectCodeId + INNER JOIN dbo.Users U ON IV.UserId = U.Id + INNER JOIN dbo.Departments D ON U.DepartmentId = D.DepartmentId + WHERE ITD.TransTypeId = 2 AND IV.IsActive = 1 AND ITD.IsActive = 1 + AND ( + @HasFullAccess = 1 + OR D.DepartmentId = @UserDepartmentId + ) + AND (@SearchPRNo = '' OR PR.PRNo = @SearchPRNo) + AND (@SearchItemNo = '' OR PRD.ItemNo = @SearchItemNo) + AND (@SearchItemName = '' OR PRD.ItemName LIKE '%' + @SearchItemName + '%') + AND (@SearchDept = '' OR D.Department LIKE '%' + @SearchDept + '%') + AND (@SearchProjectCode = '' OR PC.ProjectCode LIKE '%' + @SearchProjectCode + '%') GROUP BY IV.InventoryId , IV.ItemNo @@ -100,7 +100,7 @@ BEGIN , ICAT.ItemCategoryName , RRD.RemainingQty , ITD.CreatedDate - ,PCD.ProjectCode; + ,PC.ProjectCode; -- ── 3. Total count of filtered results ────────────────────────────── SELECT COUNT(*) AS TotalCount diff --git a/CPRNIMS.WebApi/wwwroot/Reports/RIS.frx b/CPRNIMS.WebApi/wwwroot/Reports/RIS.frx new file mode 100644 index 0000000..1398230 --- /dev/null +++ b/CPRNIMS.WebApi/wwwroot/Reports/RIS.frx @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CPRNIMS.WebApps/CPRNIMS.WebApps.csproj b/CPRNIMS.WebApps/CPRNIMS.WebApps.csproj index 3352814..409e84b 100644 --- a/CPRNIMS.WebApps/CPRNIMS.WebApps.csproj +++ b/CPRNIMS.WebApps/CPRNIMS.WebApps.csproj @@ -1,11 +1,13 @@ - + net8.0 enable enable - + + true + @@ -58,8 +60,8 @@ - + @@ -68,8 +70,11 @@ + + + - + diff --git a/CPRNIMS.WebApps/Common/ServiceExtensions.cs b/CPRNIMS.WebApps/Common/ServiceExtensions.cs index 0b5b8f8..bb682c9 100644 --- a/CPRNIMS.WebApps/Common/ServiceExtensions.cs +++ b/CPRNIMS.WebApps/Common/ServiceExtensions.cs @@ -1,4 +1,5 @@ -using CPRNIMS.Domain.UIContracts.Account; +using CPRNIMS.Domain.Contracts.Reports; +using CPRNIMS.Domain.UIContracts.Account; using CPRNIMS.Domain.UIContracts.Canvass; using CPRNIMS.Domain.UIContracts.CaptCha; using CPRNIMS.Domain.UIContracts.Common; @@ -22,6 +23,7 @@ using CPRNIMS.Domain.UIServices.Receiving; using CPRNIMS.Domain.UIServices.SMTP; using CPRNIMS.Infrastructure.Database; using CPRNIMS.Infrastructure.Helper; +using CPRNIMS.Infrastructure.Reports; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http.Features; @@ -72,7 +74,7 @@ namespace CPRNIMS.WebApps.Common private static void AddScopedServices(WebApplicationBuilder builder) { builder.Services.AddHttpContextAccessor(); - builder.Services.AddTransient(); + builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddTransient(); builder.Services.AddTransient(); @@ -88,6 +90,9 @@ namespace CPRNIMS.WebApps.Common builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); } private static void AddSessionAndAuthentication(WebApplicationBuilder builder) @@ -170,7 +175,6 @@ namespace CPRNIMS.WebApps.Common }; }); } - private static void AddDbContext(WebApplicationBuilder builder) { builder.Services.AddDbContext(options => diff --git a/CPRNIMS.WebApps/Controllers/HomeController.cs b/CPRNIMS.WebApps/Controllers/HomeController.cs index b915d47..54126d8 100644 --- a/CPRNIMS.WebApps/Controllers/HomeController.cs +++ b/CPRNIMS.WebApps/Controllers/HomeController.cs @@ -164,6 +164,7 @@ namespace CPRNIMS.WebApps.Controllers new Claim(ClaimTypes.NameIdentifier, login.userId), new Claim(ClaimTypes.Name, login.userName), new Claim("FullName", login.fullName), + new Claim("DepartmentId", Convert.ToString(login.departmentId)), new Claim("Company", login.company), new Claim("Token", login.token), new Claim("TokenExpiry", expirationTime.ToString("O")) diff --git a/CPRNIMS.WebApps/Controllers/Inventory/InventoryMgmtController.cs b/CPRNIMS.WebApps/Controllers/Inventory/InventoryMgmtController.cs index cc22a7e..b65d8ab 100644 --- a/CPRNIMS.WebApps/Controllers/Inventory/InventoryMgmtController.cs +++ b/CPRNIMS.WebApps/Controllers/Inventory/InventoryMgmtController.cs @@ -1,5 +1,4 @@ -using Azure.Core; -using CPRNIMS.Domain.UIContracts.Account; +using CPRNIMS.Domain.UIContracts.Account; using CPRNIMS.Domain.UIContracts.Inventory; using CPRNIMS.Infrastructure.Dto.Inventory.Request; using CPRNIMS.Infrastructure.Helper; diff --git a/CPRNIMS.WebApps/Controllers/Inventory/InventoryReportsController.cs b/CPRNIMS.WebApps/Controllers/Inventory/InventoryReportsController.cs new file mode 100644 index 0000000..9907b9d --- /dev/null +++ b/CPRNIMS.WebApps/Controllers/Inventory/InventoryReportsController.cs @@ -0,0 +1,40 @@ +using CPRNIMS.Domain.UIContracts.Inventory; +using Microsoft.AspNetCore.Mvc; + +namespace CPRNIMS.WebApps.Controllers.Inventory +{ + public class InventoryReportsController : Controller + { + private readonly IInventoryReports _reports; + public InventoryReportsController(IInventoryReports reports) + { + _reports=reports; + } + [HttpGet] + public async Task GetInventoryReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + { + var response = await _reports.GetInventoryReportAsync(dateFrom, dateTo, ct); + return GetResponse(response); + } + [HttpGet] + public async Task GetRISReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + { + var response = await _reports.GetRISReportAsync(dateFrom, dateTo, ct); + return GetResponse(response); + } + [HttpGet] + public async Task GetMRSReport(DateTime dateFrom, DateTime dateTo, CancellationToken ct) + { + var response = await _reports.GetMRSReportAsync(dateFrom, dateTo, ct); + return GetResponse(response); + } + protected IActionResult GetResponse(T response) + { + return Json(new + { + success = response != null, + data = response ?? Activator.CreateInstance() + }); + } + } +} diff --git a/CPRNIMS.WebApps/Controllers/Inventory/MRSController.cs b/CPRNIMS.WebApps/Controllers/Inventory/MRSController.cs deleted file mode 100644 index e5cc590..0000000 --- a/CPRNIMS.WebApps/Controllers/Inventory/MRSController.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace CPRNIMS.WebApps.Controllers.Inventory -{ - public class MRSController : Controller - { - public IActionResult Index() - { - return View(); - } - } -} diff --git a/CPRNIMS.WebApps/Controllers/Inventory/MRSMgmtController.cs b/CPRNIMS.WebApps/Controllers/Inventory/MRSMgmtController.cs new file mode 100644 index 0000000..2eaa661 --- /dev/null +++ b/CPRNIMS.WebApps/Controllers/Inventory/MRSMgmtController.cs @@ -0,0 +1,77 @@ +using CPRNIMS.Domain.UIContracts.Inventory; +using CPRNIMS.Domain.UIContracts.Account; +using CPRNIMS.Infrastructure.Dto.Inventory; +using CPRNIMS.Infrastructure.Dto.Inventory.Request; +using CPRNIMS.Infrastructure.Helper; +using CPRNIMS.WebApps.Controllers.Base; +using Microsoft.AspNetCore.Mvc; + +namespace CPRNIMS.WebApps.Controllers.Inventory +{ + public partial class MRSMgmtController : BaseMethod + { + private readonly IMRS _mrs; + public MRSMgmtController(ErrorLogHelper errorMessageService,IWebHostEnvironment webHostEnvironment,TokenHelper tokenHelper, + IMRS mrs, IAccount account) : base(errorMessageService, webHostEnvironment, tokenHelper, account) => _mrs = mrs; + + [HttpGet] + public async Task GetMRS([FromQuery] string? searchMRSNo,string? searchRISNo,string? searchItemName,string? + searchReturnedBy,string? status,string? condition,int pageNumber = 1,int pageSize = 12,CancellationToken ct = default) + { + short? statusCode = status switch + { + "0" => 0, + "1" => 1, + "2" => 2, + _ => null + }; + + var result = await _mrs.GetMRSPaged(new MRSPagedRequest + { + SearchMRSNo = searchMRSNo, + SearchRISNo = searchRISNo, + SearchItemName = searchItemName, + SearchReturnedBy = searchReturnedBy, + Status = statusCode, + Condition = condition, + PageNumber = pageNumber, + PageSize = pageSize + }, ct); + + return Json(new { data = result.Data, recordsTotal = result.RecordsTotal}); + } + + [HttpPost] + public async Task CreateMRS([FromBody] CreateMRSRequest request,CancellationToken ct) + { + var result = await _mrs.CreateMRS(request, ct); + + if (!result.success) + return BadRequest(new { success = false, message = result.message }); + + return Ok(new { success = true, message = result.message }); + } + + [HttpPost] + public async Task ApproveMRS([FromBody] ApproveMRSRequest request,CancellationToken ct) + { + var result = await _mrs.ApproveMRS(request, ct); + + if (!result.success) + return BadRequest(new { success = false, message = result.message }); + + return Ok(new { success = true, message = result.message }); + } + + [HttpPost] + public async Task CancelMRS([FromBody] CancelMRSRequest request,CancellationToken ct) + { + var result = await _mrs.CancelMRS(request, ct); + + if (!result.success) + return BadRequest(new { success = false, message = result.message }); + + return Ok(new { success = true, message = result.message }); + } + } +} diff --git a/CPRNIMS.WebApps/Controllers/Inventory/RISMgmtController.cs b/CPRNIMS.WebApps/Controllers/Inventory/RISMgmtController.cs index 25f285e..f7671a0 100644 --- a/CPRNIMS.WebApps/Controllers/Inventory/RISMgmtController.cs +++ b/CPRNIMS.WebApps/Controllers/Inventory/RISMgmtController.cs @@ -1,63 +1,85 @@ -using CPRNIMS.Domain.UIContracts.Account; +using CPRNIMS.Domain.Contracts.Reports; +using CPRNIMS.Domain.UIContracts.Account; using CPRNIMS.Domain.UIContracts.Inventory; using CPRNIMS.Infrastructure.Dto.Inventory.Request; using CPRNIMS.Infrastructure.Helper; using CPRNIMS.WebApps.Controllers.Base; using Microsoft.AspNetCore.Mvc; +using FastReport; +using FastReport.Web; +using System.Threading.Tasks; namespace CPRNIMS.WebApps.Controllers.Inventory { public class RISMgmtController : BaseMethod { private readonly IRIS _ris; + private readonly IReportBuilder _builder; + private readonly IWebHostEnvironment _env; public RISMgmtController(ErrorLogHelper errorMessageService, IWebHostEnvironment webHostEnvironment, TokenHelper tokenHelper - , IRIS ris, IAccount account) - : base(errorMessageService, webHostEnvironment, tokenHelper, account) + , IRIS ris, IAccount account, IReportBuilder builder) + : base(errorMessageService, webHostEnvironment, tokenHelper, account) { _ris = ris; + _builder = builder; + _env = webHostEnvironment; + } + + public IActionResult RISReport(DateTime? dateFrom, DateTime? dateTo, CancellationToken ct) + { + return View(); + } + public async Task RISReportPdf(DateTime? dateFrom, DateTime? dateTo, CancellationToken ct) + { + var from = dateFrom ?? DateTime.Today.AddDays(-15); + var to = dateTo ?? DateTime.Today; + + var templatePath = Path.Combine(_env.WebRootPath, "Reports", "RIS_v2.frx"); + var report = await _builder.RISBuildAsync(from, to, templatePath, ct); + report.Prepare(); + + using var pdf = new FastReport.Export.PdfSimple.PDFSimpleExport(); + using var ms = new MemoryStream(); + pdf.Export(report, ms); + + return File(ms.ToArray(), "application/pdf", + $"RIS-Report-{from:yyyyMMdd}-{to:yyyyMMdd}.pdf"); + } + [HttpPost] public async Task CreateRIS([FromBody] CreateRISRequest request,CancellationToken ct) { var result = await _ris.CreateRIS(request,ct); + if (!result.success) + return BadRequest(new { success = false, message = result.message }); - return Json(new { success = true, message= $"RIS {result.RISNo} created successfully.", data = result }); + return Ok(new { success = true, message = result.message }); } [HttpPost] public async Task ApproveRIS([FromBody] ApproveRISRequest request,CancellationToken ct) { - bool isSuccess = await _ris.ApproveRIS(request, ct); + var result = await _ris.ApproveRIS(request, ct); - if (!isSuccess) - return BadRequest(new { success = false, message = "RIS cancelled failed" }); + if (!result.success) + return BadRequest(new { success = false, message = result.message }); - return Ok(new - { - success = true, - message = $"RIS approved successfully." - }); + return Ok(new { success = true, message = result.message }); } [HttpPost] public async Task CancelRIS([FromBody] CancelRISRequest request,CancellationToken ct) { if (string.IsNullOrWhiteSpace(request.Reason)) - return BadRequest(new - { - success = false, - message = "A reason for cancellation is required." - }); - bool isSuccess = await _ris.CancelRIS(request,ct); + return BadRequest(new{success = false,message = "A reason for cancellation is required."}); - if (!isSuccess) - return BadRequest(new { success = false, message = "RIS cancelled failed" }); + var result = await _ris.CancelRIS(request, ct); - return Ok(new - { - success = true, - message = "RIS cancelled and inventory restored." - }); + if (!result.success) + return BadRequest(new { success = false, message = result.message }); + + return Ok(new { success = true, message = result.message }); } [HttpGet] @@ -92,10 +114,5 @@ namespace CPRNIMS.WebApps.Controllers.Inventory disciplineList = result.DisciplineList }); } - public async Task GetRISById(int risId, CancellationToken ct) - { - var response = await _ris.GetRISById(risId,ct); - return GetResponse(response); - } } } diff --git a/CPRNIMS.WebApps/Program.cs b/CPRNIMS.WebApps/Program.cs index 28373ee..0161dcc 100644 --- a/CPRNIMS.WebApps/Program.cs +++ b/CPRNIMS.WebApps/Program.cs @@ -1,32 +1,35 @@ -using CPRNIMS.Domain.UIServices.Updater; +using CPRNIMS.Domain.UIServices.Updater; using CPRNIMS.WebApps.Common; +using Microsoft.AspNetCore.StaticFiles; var builder = WebApplication.CreateBuilder(args); +builder.WebHost.UseStaticWebAssets(); + builder.Services.AddApplicationServices(builder); +builder.Services.AddFastReport(); var app = builder.Build(); -// Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } -//app.UseRewriter(options); + +//var provider = new FileExtensionContentTypeProvider(); +//provider.Mappings[".js"] = "text/javascript"; +//provider.Mappings[".css"] = "text/css"; + app.UseHttpsRedirection(); app.UseStaticFiles(); - app.UseRouting(); app.UseSession(); - -app.MapHub("/cartHub"); - app.UseAuthentication(); app.UseAuthorization(); - +app.UseFastReport(); +app.MapHub("/cartHub"); app.MapControllerRoute( name: "default", -//pattern: "{controller=ItemMgmt}/{action=Index}/{id?}"); -pattern: "{controller=Home}/{action=Index}/{id?}"); + pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); \ No newline at end of file diff --git a/CPRNIMS.WebApps/ViewComponents/Inventory/InventoryTabPageViewComponent.cs b/CPRNIMS.WebApps/ViewComponents/Inventory/InventoryTabPageViewComponent.cs index beadb64..8f5dc3a 100644 --- a/CPRNIMS.WebApps/ViewComponents/Inventory/InventoryTabPageViewComponent.cs +++ b/CPRNIMS.WebApps/ViewComponents/Inventory/InventoryTabPageViewComponent.cs @@ -9,9 +9,11 @@ namespace CPRNIMS.WebApps.ViewComponents.Inventory string viewName = InventoryTabPageId switch { 1 => "~/Views/Components/Inventory/TabPage/Inventory.cshtml", - 2 => "~/Views/Components/Inventory/TabPage/RISForApproval.cshtml", + 2 => "~/Views/Components/Inventory/TabPage/RIS.cshtml", 3 => "~/Views/Components/Inventory/TabPage/MRS.cshtml", - _ => "~/Views/Components/Inventory/TabPage/InventoryTransaction.cshtml" + 4 => "~/Views/Components/Inventory/TabPage/Reports/InventorySummaryReport.cshtml", + 5 => "~/Views/Components/Inventory/TabPage/Reports/RISReport.cshtml", + _ => "~/Views/Components/Inventory/TabPage/Reports/MRSReport.cshtml" }; return View(viewName); } diff --git a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/InventoryTransaction.cshtml b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/InventoryTransaction.cshtml new file mode 100644 index 0000000..4c90651 --- /dev/null +++ b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/InventoryTransaction.cshtml @@ -0,0 +1,1007 @@ +@* ── Tab: Return Issuance Slip — For Approval ─────────────────────────────── + Returned by GetInventoryTabPage?id=2 (or whichever tab id you assign) + Depends on: _InventoryStyles, _InventoryHelpers, window.InventoryHelpers +────────────────────────────────────────────────────────────────────────────── *@ + +@* ── FILTER BAR ── *@ +
+ + + + + @* ── Discipline dropdown ── *@ +
+
+ + + All Disciplines + + +
+
+ +
+
+ All Disciplines +
+
+
+
+ + @* ── Status filter ── *@ +
+
+ + + All Status + + +
+
+ +
+
+ All Status +
+
+
+
+ +
+ Show + + 0 results +
+
+ +@* ── CARD GRID ── *@ +
+
+
+

Loading…

+
+
+ +@* ── PAGINATION ── *@ +
+ +
+
+ +@* ── APPROVE CONFIRMATION MODAL ── *@ + + +@* ── CANCEL CONFIRMATION MODAL ── *@ + + + + + diff --git a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/MRS.cshtml b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/MRS.cshtml new file mode 100644 index 0000000..c0c55a9 --- /dev/null +++ b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/MRS.cshtml @@ -0,0 +1,1272 @@ +@* ── FILTER BAR ── *@ +
+ + + + + + @* ── Status dropdown ── *@ +
+
+ + + All Status + + +
+
+ +
+
+ All Status +
+
+
+
+ + @* ── Condition dropdown ── *@ +
+
+ + + All Conditions + + +
+
+ +
+
+ All Conditions +
+
+
+
+ +
+ Show + + 0 results +
+
+ +@* ── CARD GRID ── *@ +
+
+
+

Loading…

+
+
+ +@* ── PAGINATION ── *@ +
+ +
+
+ +@* ══════════════════════════════════════════════════════════════════════════ + CREATE MRS MODAL +══════════════════════════════════════════════════════════════════════════ *@ + + +@* ══════════════════════════════════════════════════════════════════════════ + APPROVE MRS MODAL +══════════════════════════════════════════════════════════════════════════ *@ + + +@* ══════════════════════════════════════════════════════════════════════════ + CANCEL MRS MODAL +══════════════════════════════════════════════════════════════════════════ *@ + + + + + diff --git a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/RISForApproval.cshtml b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/RIS.cshtml similarity index 88% rename from CPRNIMS.WebApps/Views/Components/Inventory/TabPage/RISForApproval.cshtml rename to CPRNIMS.WebApps/Views/Components/Inventory/TabPage/RIS.cshtml index ce1a2f0..54b668a 100644 --- a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/RISForApproval.cshtml +++ b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/RIS.cshtml @@ -1,8 +1,3 @@ -@* ── Tab: Return Issuance Slip — For Approval ─────────────────────────────── - Returned by GetInventoryTabPage?id=2 (or whichever tab id you assign) - Depends on: _InventoryStyles, _InventoryHelpers, window.InventoryHelpers -────────────────────────────────────────────────────────────────────────────── *@ - @* ── FILTER BAR ── *@
- - \ No newline at end of file diff --git a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReport.cshtml b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReport.cshtml new file mode 100644 index 0000000..76960c3 --- /dev/null +++ b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/InventorySummaryReport.cshtml @@ -0,0 +1,710 @@ + +@await Html.PartialAsync("PagesView/Inventory/_InventoryReportHelper") + + \ No newline at end of file diff --git a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/MRSReport.cshtml b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/MRSReport.cshtml new file mode 100644 index 0000000..8445c90 --- /dev/null +++ b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/MRSReport.cshtml @@ -0,0 +1,694 @@ + +@await Html.PartialAsync("PagesView/Inventory/_InventoryReportHelper") + diff --git a/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/RISReport.cshtml b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/RISReport.cshtml new file mode 100644 index 0000000..8c40e3c --- /dev/null +++ b/CPRNIMS.WebApps/Views/Components/Inventory/TabPage/Reports/RISReport.cshtml @@ -0,0 +1,700 @@ + +@await Html.PartialAsync("PagesView/Inventory/_InventoryReportHelper") + diff --git a/CPRNIMS.WebApps/Views/Home/_LoginLayout.cshtml b/CPRNIMS.WebApps/Views/Home/_LoginLayout.cshtml index 0e5cf1f..57ca435 100644 --- a/CPRNIMS.WebApps/Views/Home/_LoginLayout.cshtml +++ b/CPRNIMS.WebApps/Views/Home/_LoginLayout.cshtml @@ -7,7 +7,7 @@ - + diff --git a/CPRNIMS.WebApps/Views/InventoryMgmt/Inventory.cshtml b/CPRNIMS.WebApps/Views/InventoryMgmt/Inventory.cshtml index e1b0e00..f12b769 100644 --- a/CPRNIMS.WebApps/Views/InventoryMgmt/Inventory.cshtml +++ b/CPRNIMS.WebApps/Views/InventoryMgmt/Inventory.cshtml @@ -19,18 +19,20 @@ Inventory + RIS + + -
@@ -76,6 +78,7 @@ const html = await res.text(); cache[tabId] = html; injectHtml(tabContent, html); + } catch (err) { console.error("Tab load error:", err); tabContent.innerHTML = ` diff --git a/CPRNIMS.WebApps/Views/RISMgmt/RISReport - Copy.cshtml b/CPRNIMS.WebApps/Views/RISMgmt/RISReport - Copy.cshtml new file mode 100644 index 0000000..963d557 --- /dev/null +++ b/CPRNIMS.WebApps/Views/RISMgmt/RISReport - Copy.cshtml @@ -0,0 +1,84 @@ +@model FastReport.Web.WebReport +@{ + Layout = null; + var from = (DateTime)ViewBag.DateFrom; + var to = (DateTime)ViewBag.DateTo; +} + + + + + + RIS Report — @from.ToString("MMM d") to @to.ToString("MMM d, yyyy") + @* *@ + + + +
+
+

Return Issuance Slip Report

+
+ Period: @from.ToString("MMMM d, yyyy") – @to.ToString("MMMM d, yyyy") +
+
+ + Close + +
+ +
+ @await Model.Render() +
+ + \ No newline at end of file diff --git a/CPRNIMS.WebApps/Views/RISMgmt/RISReport.cshtml b/CPRNIMS.WebApps/Views/RISMgmt/RISReport.cshtml new file mode 100644 index 0000000..bf6ef9c --- /dev/null +++ b/CPRNIMS.WebApps/Views/RISMgmt/RISReport.cshtml @@ -0,0 +1,93 @@ + + + + +
+ + + +
+ +
+ + \ No newline at end of file diff --git a/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryReportHelper.cshtml b/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryReportHelper.cshtml new file mode 100644 index 0000000..300d770 --- /dev/null +++ b/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryReportHelper.cshtml @@ -0,0 +1,120 @@ +
+
+ + +
+
+ + +
+ + + + +
+ +
+
+
+ Loading report… +
+
+ + diff --git a/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryStyles.cshtml b/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryStyles.cshtml index b0ec0c0..0256077 100644 --- a/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryStyles.cshtml +++ b/CPRNIMS.WebApps/Views/Shared/PagesView/Inventory/_InventoryStyles.cshtml @@ -96,6 +96,7 @@ .inv-tabs { display: flex; gap: 6px; + flex-wrap: wrap; background: #fff; border-radius: var(--radius-lg); padding: 6px; @@ -104,7 +105,8 @@ } .inv-tab-btn { - flex: 1; + flex: 1 1 auto; /* grow, but allow shrinking and wrapping */ + min-width: 140px; /* keep each tab readable before it wraps */ display: flex; align-items: center; justify-content: center; diff --git a/CPRNIMS.WebApps/Views/Shared/PartialView/_PartialScripts.cshtml b/CPRNIMS.WebApps/Views/Shared/PartialView/_PartialScripts.cshtml index 4181902..b842801 100644 --- a/CPRNIMS.WebApps/Views/Shared/PartialView/_PartialScripts.cshtml +++ b/CPRNIMS.WebApps/Views/Shared/PartialView/_PartialScripts.cshtml @@ -1,4 +1,4 @@ - +@* *@ diff --git a/CPRNIMS.WebApps/Views/_ViewImports.cshtml b/CPRNIMS.WebApps/Views/_ViewImports.cshtml index b47e9d7..cbce107 100644 --- a/CPRNIMS.WebApps/Views/_ViewImports.cshtml +++ b/CPRNIMS.WebApps/Views/_ViewImports.cshtml @@ -1,3 +1,5 @@ @using CPRNIMS.WebApps @using CPRNIMS.WebApps.Models @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, FastReport.Web +@using FastReport.Web