295 lines
12 KiB
C#
295 lines
12 KiB
C#
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<Infrastructure.Entities.Inventory.RIS> 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,
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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<string> 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<RISPagedResult> 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
|
|
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<IEnumerable<DisciplineDto>> 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<RISResponse?> 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);
|
|
}
|
|
}
|
|
}
|