Add returns and claims
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
using Distribution.Features.Grn;
|
||||
using Distribution.Features.Po;
|
||||
using Distribution.Infra.UoW;
|
||||
using Distribution.Models;
|
||||
|
||||
namespace Distribution.Features.SupplierClaims;
|
||||
|
||||
public class SupplierClaimService(
|
||||
ISupplierClaimRepository repo,
|
||||
IPoRepository poRepo,
|
||||
IGrnRepository grnRepo,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
private static SupplierClaimItemResponse MapItem(SupplierClaimItem i) =>
|
||||
new(i.Id, i.FK_ItemId, i.Item.Sku, i.Item.Name, i.Quantity, i.IssueType);
|
||||
|
||||
private static SupplierClaimResponse Map(SupplierClaim c) =>
|
||||
new(c.Id, c.FK_PurchaseOrderId, c.FK_GoodsReceiptId, c.Details, c.EvidenceUrl, c.Status,
|
||||
c.ResolutionType, c.ResolutionAmount, c.ResolutionNotes, c.ResolvedAt, c.IsActive, c.CreatedAt,
|
||||
c.Items.Where(i => i.IsActive).Select(MapItem).ToList());
|
||||
|
||||
public async Task<SupplierClaimResponse?> GetByIdAsync(Guid id)
|
||||
{
|
||||
var c = await repo.GetByIdAsync(id);
|
||||
if (c is null || !c.IsActive) return null;
|
||||
return Map(c);
|
||||
}
|
||||
|
||||
public async Task<List<SupplierClaimResponse>> GetByPurchaseOrderIdAsync(Guid purchaseOrderId) =>
|
||||
(await repo.GetByPurchaseOrderIdAsync(purchaseOrderId)).Select(Map).ToList();
|
||||
|
||||
public async Task<SupplierClaimResponse> CreateAsync(Guid purchaseOrderId, CreateSupplierClaimRequest request, Guid raisedByUserId, CancellationToken ct = default)
|
||||
{
|
||||
var po = await poRepo.GetByIdAsync(purchaseOrderId);
|
||||
if (po is null || !po.IsActive)
|
||||
throw new InvalidOperationException("Purchase order not found.");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Details))
|
||||
throw new InvalidOperationException("Claim details are required.");
|
||||
|
||||
if (request.Items is null || request.Items.Count == 0)
|
||||
throw new InvalidOperationException("A supplier claim must have at least one line item.");
|
||||
|
||||
if (request.GoodsReceiptId is not null)
|
||||
{
|
||||
var grn = await grnRepo.GetByIdAsync(request.GoodsReceiptId.Value)
|
||||
?? throw new InvalidOperationException("Goods receipt not found.");
|
||||
if (grn.FK_PurchaseOrderId != purchaseOrderId)
|
||||
throw new InvalidOperationException("Goods receipt does not belong to this purchase order.");
|
||||
}
|
||||
|
||||
var claim = new SupplierClaim
|
||||
{
|
||||
FK_PurchaseOrderId = purchaseOrderId,
|
||||
FK_GoodsReceiptId = request.GoodsReceiptId,
|
||||
Details = request.Details,
|
||||
EvidenceUrl = request.EvidenceUrl,
|
||||
RaisedByUserId = raisedByUserId
|
||||
};
|
||||
|
||||
foreach (var line in request.Items)
|
||||
{
|
||||
if (line.Quantity <= 0)
|
||||
throw new InvalidOperationException("Claim quantity must be greater than zero.");
|
||||
|
||||
claim.Items.Add(new SupplierClaimItem
|
||||
{
|
||||
FK_ItemId = line.ItemId,
|
||||
Quantity = line.Quantity,
|
||||
IssueType = line.IssueType
|
||||
});
|
||||
}
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
await repo.AddAsync(claim);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
var saved = await repo.GetByIdAsync(claim.Id);
|
||||
return Map(saved!);
|
||||
}
|
||||
|
||||
public async Task<SupplierClaimResponse> ApproveAsync(Guid id, ApproveSupplierClaimRequest request, Guid approverId, CancellationToken ct = default)
|
||||
{
|
||||
var claim = await repo.GetByIdAsync(id)
|
||||
?? throw new KeyNotFoundException("Supplier claim not found.");
|
||||
if (claim.Status != SupplierClaimStatus.Open)
|
||||
throw new InvalidOperationException("Only an open claim can be approved.");
|
||||
|
||||
if (request.ResolutionType != SupplierClaimResolutionType.Replacement && request.ResolutionAmount is null)
|
||||
throw new InvalidOperationException("ResolutionAmount is required for Credit, Refund, and PriceAdjustment.");
|
||||
|
||||
claim.Status = SupplierClaimStatus.Approved;
|
||||
claim.ResolutionType = request.ResolutionType;
|
||||
claim.ResolutionAmount = request.ResolutionType == SupplierClaimResolutionType.Replacement ? null : request.ResolutionAmount;
|
||||
claim.ResolutionNotes = request.ResolutionNotes;
|
||||
claim.ResolvedByUserId = approverId;
|
||||
claim.ResolvedAt = DateTime.Now;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(claim);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return Map(claim);
|
||||
}
|
||||
|
||||
public async Task<SupplierClaimResponse> RejectAsync(Guid id, RejectSupplierClaimRequest request, Guid approverId, CancellationToken ct = default)
|
||||
{
|
||||
var claim = await repo.GetByIdAsync(id)
|
||||
?? throw new KeyNotFoundException("Supplier claim not found.");
|
||||
if (claim.Status != SupplierClaimStatus.Open)
|
||||
throw new InvalidOperationException("Only an open claim can be rejected.");
|
||||
|
||||
claim.Status = SupplierClaimStatus.Rejected;
|
||||
claim.ResolutionNotes = request.ResolutionNotes;
|
||||
claim.ResolvedByUserId = approverId;
|
||||
claim.ResolvedAt = DateTime.Now;
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try
|
||||
{
|
||||
repo.Update(claim);
|
||||
await uow.CommitAsync(ct);
|
||||
}
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return Map(claim);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user