using CPRNIMS.Domain.Contracts.Inventory; using CPRNIMS.Domain.UIServices.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; public RIS(NonInventoryDbContext db) => _db = db; public async Task CreateAsync(CreateRISRequest dto, string createdBy, CancellationToken ct) { var strategy = _db.Database.CreateExecutionStrategy(); return await strategy.ExecuteAsync(async () => { await using var tx = await _db.Database.BeginTransactionAsync(ct); try { 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, 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 { await tx.RollbackAsync(ct); throw; } }); } public async Task ApproveAsync(ApproveRISRequest request, string approvedBy, CancellationToken ct) { 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, 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 .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."); 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 }); //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; } }); } 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) { var q = _db.RIS .Include(r => r.Discipline) .Include(r => r.Inventory) .Include(r => r.MaterialReturns) .AsQueryable(); // 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 if (!string.IsNullOrWhiteSpace(filter.SearchIssuedTo)) q = q.Where(r => r.IssuedTo.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, IssuedTo = r.IssuedTo, 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 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, IssuedTo = r.IssuedTo, 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); } } }