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.Entities.Inventory; using Microsoft.EntityFrameworkCore; namespace CPRNIMS.Domain.Services.Inventory { public class MRS : IMRS { private readonly NonInventoryDbContext _db; 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 user = await _userManager.FindByNameAsync(approvedBy); //if (user != null) //{ // bool isApprover2 = await _userManager.IsInRoleAsync(user, "APPROVER2"); // if (!isApprover2) // { // throw new Exception("You have no permission to approve this item."); // } //} var rms = await _db.MRS.FindAsync(mrsId) ?? throw new InvalidOperationException("MRS not found."); if (rms.Status != 0) throw new InvalidOperationException("Only Draft MRS records can be approved."); rms.Status = 1; // Approved rms.ApprovedBy = approvedBy; rms.ApprovedDate = DateTime.Now; await _db.SaveChangesAsync(ct); } public async Task CancelAsync(CancelMRSRequest request,string canceledBy, CancellationToken ct) { //var user = await _userManager.FindByNameAsync(canceledBy); //if (user != null) //{ // bool isApprover2 = await _userManager.IsInRoleAsync(user, "APPROVER2"); // if (!isApprover2) // { // throw new Exception("You have no permission to cancel this item."); // } //} await _transactionFacade.ExecuteAsync(async () => { var mrs = await _db.MRS .Include(m => m.Inventory) .FirstOrDefaultAsync(m => m.MRSId == request.MRSId) ?? throw new InvalidOperationException("MRS not found."); if (mrs.Status == 2) throw new InvalidOperationException("MRS is already cancelled."); // 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 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 = 1,//Matic Approve for now 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, ct); 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)); if (filter.RISId.HasValue) q = q.Where(m => m.RISId == filter.RISId.Value); if (filter.Status.HasValue) q = q.Where(m => m.Status == filter.Status.Value); if (filter.DateFrom.HasValue) q = q.Where(m => m.CreatedDate >= filter.DateFrom.Value); if (filter.DateTo.HasValue) q = q.Where(m => m.CreatedDate <= filter.DateTo.Value.AddDays(1)); var total = await q.CountAsync(ct); var data = await q .OrderByDescending(m => m.CreatedDate) .Skip((filter.PageNumber - 1) * filter.PageSize) .Take(filter.PageSize) .Select(m => new MRSPagedDto { MRSId = m.MRSId, MRSNo = m.MRSNo, RISId = m.RISId, RISNo = m.RIS.RISNo, InventoryId = m.InventoryId, ItemName = m.Inventory.InventTrans .SelectMany(t => t.InventTransDetails) .Select(d => d.PRDetails != null ? d.PRDetails.ItemName : "—") .FirstOrDefault() ?? "—", ReturnedBy = m.ReturnedBy, QtyReturned = m.QtyReturned, Condition = m.Condition, Remarks = m.Remarks, Status = m.Status, CreatedBy = m.CreatedBy, CreatedDate = m.CreatedDate, ApprovedBy = m.ApprovedBy, ApprovedDate = m.ApprovedDate }) .ToListAsync(ct); return new MRSPagedResult { Data = data, RecordsTotal = total }; } private async Task GenerateMRSNoAsync() { var year = DateTime.Now.Year; var month = DateTime.Now.Month.ToString("D2"); var count = await _db.MRS.CountAsync(m => m.CreatedDate.Year == year) + 1; return $"MRS-{year}{month}-{count:D4}"; } public async Task> SearchRISForReturnAsync(string? risNoQuery, int? projectCodeId, CancellationToken ct) { var query = _db.RIS .Where(r => r.Status == 1); if (projectCodeId.HasValue) query = query.Where(r => r.ProjectCodeId == projectCodeId.Value); if (!string.IsNullOrWhiteSpace(risNoQuery)) query = query.Where(r => r.RISNo.Contains(risNoQuery)); return await query .Select(r => new RISSearchResultDto { RISId = r.RISId, RISNo = r.RISNo, ProjectCodeId = r.ProjectCodeId, ProjectCode = r.ProjectCodes.ProjectCode ?? "N/A", ProjectName = r.ProjectCodes.ProjectName ?? "N/A", DisciplineName = r.Discipline.DisciplineName, QtyAvailableToReturn = r.QtyIssued - r.MaterialReturns.Sum(m => m.QtyReturned) }) .Where(r => r.QtyAvailableToReturn > 0) .OrderByDescending(r => r.RISId) .Take(20) .ToListAsync(ct); } public async Task> GetProjectsWithOpenRISAsync(string? nameQuery, CancellationToken ct) { var query = _db.RIS .Where(r => r.Status == 1 && r.QtyIssued > r.MaterialReturns.Sum(m => m.QtyReturned)); if (!string.IsNullOrWhiteSpace(nameQuery)) query = query.Where(r => r.ProjectCodes.ProjectName.Contains(nameQuery) || r.ProjectCodes.ProjectCode.Contains(nameQuery)); return await query .Select(r => new ProjectCodeOptionDto { ProjectCodeId = r.ProjectCodeId, ProjectCode = r.ProjectCodes.ProjectCode ?? "N/A", ProjectName = r.ProjectCodes.ProjectName ?? "N/A" }) .Distinct() .OrderBy(p => p.ProjectName) .Take(20) .ToListAsync(ct); } } }