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 RIS : IRIS { private readonly NonInventoryDbContext _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) { return await _transactionFacade.ExecuteAsync(async () => { 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}."); var risNo = await GenerateRISNoAsync(ct); var ris = new Infrastructure.Entities.Inventory.RIS { RISNo = risNo, InventoryId = dto.InventoryId, PRDetailId = dto.PRDetailId, ProjectCodeId = dto.ProjectCodeId, DisciplineId = dto.DisciplineId, QtyIssued = dto.QtyIssued, Remarks = dto.Remarks, Status = 1,//Approved alredy 0 is Draft 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) { //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 approved this item."); // } //} var ris = await _db.RIS.FindAsync(request.RISId, ct) ?? throw new InvalidOperationException("RIS not found."); if (ris.Status != 0) throw new InvalidOperationException("Only Draft RIS records can be approved."); ris.Status = 1; // Approved ris.ApprovedBy = approvedBy; ris.ApprovedDate = DateTime.Now; await _db.SaveChangesAsync(ct); } public async Task CancelAsync(CancelRISRequest 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 ris = await _db.RIS .Include(r => r.Inventory) .FirstOrDefaultAsync(r => r.RISId == request.RISId, ct) ?? throw new InvalidOperationException("RIS not found."); 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; ris.Status = 2; // Cancelled var trans = await _db.InventTrans .Where(t => t.InventoryId == ris.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 = ris.PRDetailId, QtyIn = ris.QtyIssued,//Return the issued Qty CreatedDate = DateTime.Now, Remarks = request.Reason, IsActive = true }); }, ct); } private async Task GenerateRISNoAsync(CancellationToken ct) { var year = DateTime.Now.Year; var month = DateTime.Now.Month.ToString("D2"); var count = await _db.RIS .CountAsync(r => r.CreatedDate.Year == year,ct) + 1; return $"RIS-{year}{month}-{count:D4}"; // e.g. RIS-202606-0001 } 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); // RIS No if (!string.IsNullOrWhiteSpace(filter.SearchRISNo)) q = q.Where(r => r.RISNo.Contains(filter.SearchRISNo)); // Item Name if (!string.IsNullOrWhiteSpace(filter.SearchItemName)) q = q.Where(r => r.Inventory.InventTrans .SelectMany(t => t.InventTransDetails) .Any(d => d.PRDetails != null && d.PRDetails.ItemName.Contains(filter.SearchItemName))); // Issued To is Project code/name if (!string.IsNullOrWhiteSpace(filter.SearchIssuedTo)) q = q.Where(r => r.ProjectCodes.ProjectCode.Contains(filter.SearchIssuedTo) || r.ProjectCodes.ProjectName.Contains(filter.SearchIssuedTo)); // Discipline if (!string.IsNullOrWhiteSpace(filter.Discipline)) q = q.Where(r => r.Discipline.DisciplineName == filter.Discipline); // Date range (if you ever add date filters) if (filter.DateFrom.HasValue) q = q.Where(r => r.CreatedDate >= filter.DateFrom.Value); if (filter.DateTo.HasValue) q = q.Where(r => r.CreatedDate <= filter.DateTo.Value.AddDays(1)); var total = await q.CountAsync(ct); var data = await q .OrderByDescending(r => r.CreatedDate) .Skip((filter.PageNumber - 1) * filter.PageSize) .Take(filter.PageSize) .Select(r => new RISResponse { RISId = r.RISId, RISNo = r.RISNo, InventoryId = r.InventoryId, ItemName = r.Inventory.InventTrans .SelectMany(t => t.InventTransDetails) .Select(d => d.PRDetails != null ? d.PRDetails.ItemName : "—") .FirstOrDefault() ?? "—", ItemNo = r.Inventory.ItemNo, LotNo = r.Inventory.Lot != null ? r.Inventory.Lot.LotName : null, ProjectName = r.ProjectCodes.ProjectName, ProjectCode = r.ProjectCodes.ProjectCode, DisciplineName = r.Discipline.DisciplineName, DisciplineId = r.DisciplineId, QtyIssued = r.QtyIssued, Remarks = r.Remarks, Status = r.Status, CreatedBy = r.CreatedBy, CreatedDate = r.CreatedDate, ApprovedBy = r.ApprovedBy, ApprovedDate = r.ApprovedDate, MRSCount = r.MaterialReturns.Count(m => m.Status != 2), TotalReturned = r.MaterialReturns .Where(m => m.Status != 2) .Sum(m => m.QtyReturned) }) .ToListAsync(ct); // Full department list (never filtered) var departments = await _db.Departments .Select(d => d.Department) .OrderBy(d => d) .ToListAsync(ct); var disciplines = await GetDisciplinesAsync(ct); return new RISPagedResult { Data = data, RecordsTotal = total, DepartmentList = departments, DisciplineList = disciplines }; } public async Task> GetDisciplinesAsync(CancellationToken ct) { return await _db.Disciplines .OrderBy(d => d.DisciplineName) .Select(d => new DisciplineDto { DisciplineId = d.DisciplineId, DisciplineName = d.DisciplineName }) .ToListAsync(ct); } public async Task> GetProjectCodesAsync(CancellationToken ct) { return await _db.ProjectCodes .Where(p=>p.StatusName !="Completed") .OrderBy(d => d.ProjectName) .Select(d => new ProjectCodeDto { ProjectCodeId= d.ProjectCodeId, ProjectCode = d.ProjectCode ?? "N/A", ProjectName = d.ProjectName ?? "N/A" }) .ToListAsync(ct); } public async Task GetByIdAsync(long risId, CancellationToken ct) { return await _db.RIS .Where(r => r.RISId == risId) .Select(r => new RISResponse { RISId = r.RISId, RISNo = r.RISNo, InventoryId = r.InventoryId, ProjectCodeId = r.ProjectCodeId, DisciplineName = r.Discipline.DisciplineName, DisciplineId = r.DisciplineId, QtyIssued = r.QtyIssued, Remarks = r.Remarks, Status = r.Status, CreatedBy = r.CreatedBy, CreatedDate = r.CreatedDate, ApprovedBy = r.ApprovedBy, ApprovedDate = r.ApprovedDate, MRSCount = r.MaterialReturns.Count(m => m.Status != 2), TotalReturned = r.MaterialReturns .Where(m => m.Status != 2) .Sum(m => m.QtyReturned) }) .FirstOrDefaultAsync(ct); } } }