Implement GetAll and Update functionality for Supplier Claims
This commit is contained in:
@@ -3,6 +3,7 @@ namespace Distribution.Features.SupplierClaims;
|
||||
public interface ISupplierClaimRepository
|
||||
{
|
||||
Task<Models.SupplierClaim?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.SupplierClaim>> GetAllAsync();
|
||||
Task<List<Models.SupplierClaim>> GetByPurchaseOrderIdAsync(Guid purchaseOrderId);
|
||||
Task AddAsync(Models.SupplierClaim entity);
|
||||
void Update(Models.SupplierClaim entity);
|
||||
|
||||
@@ -6,6 +6,10 @@ namespace Distribution.Features.SupplierClaims;
|
||||
[ApiController]
|
||||
public class SupplierClaimController(SupplierClaimService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("api/supplier-claims")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("api/supplier-claims/{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
@@ -20,22 +24,37 @@ public class SupplierClaimController(SupplierClaimService service, ICurrentUserS
|
||||
[HttpPost("api/purchase-orders/{id:guid}/claims")]
|
||||
public async Task<IActionResult> Create(Guid id, CreateSupplierClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
// TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware
|
||||
// (testbug.md #28) — fall back to the explicitly supplied user until that's fixed.
|
||||
var userId = currentUser.UserId ?? request.RaisedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "RaisedByUserId is required while auth is unavailable" });
|
||||
try
|
||||
{
|
||||
var result = await service.CreateAsync(id, request, userId, ct);
|
||||
var result = await service.CreateAsync(id, request, userId.Value, ct);
|
||||
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPut("api/supplier-claims/{id:guid}")]
|
||||
public async Task<IActionResult> Update(Guid id, UpdateSupplierClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpPost("api/supplier-claims/{id:guid}/approve")]
|
||||
public async Task<IActionResult> Approve(Guid id, ApproveSupplierClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
var userId = currentUser.UserId ?? request.ApprovedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "ApprovedByUserId is required while auth is unavailable" });
|
||||
try
|
||||
{
|
||||
var result = await service.ApproveAsync(id, request, userId, ct);
|
||||
var result = await service.ApproveAsync(id, request, userId.Value, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
@@ -45,10 +64,11 @@ public class SupplierClaimController(SupplierClaimService service, ICurrentUserS
|
||||
[HttpPost("api/supplier-claims/{id:guid}/reject")]
|
||||
public async Task<IActionResult> Reject(Guid id, RejectSupplierClaimRequest request, CancellationToken ct)
|
||||
{
|
||||
var userId = currentUser.UserId ?? throw new UnauthorizedAccessException();
|
||||
var userId = currentUser.UserId ?? request.RejectedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "RejectedByUserId is required while auth is unavailable" });
|
||||
try
|
||||
{
|
||||
var result = await service.RejectAsync(id, request, userId, ct);
|
||||
var result = await service.RejectAsync(id, request, userId.Value, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (KeyNotFoundException) { return NotFound(); }
|
||||
|
||||
@@ -6,6 +6,15 @@ public record SupplierClaimItemRequest(Guid ItemId, decimal Quantity, SupplierCl
|
||||
|
||||
public record CreateSupplierClaimRequest(
|
||||
Guid? GoodsReceiptId,
|
||||
string Details,
|
||||
string? EvidenceUrl,
|
||||
List<SupplierClaimItemRequest> Items,
|
||||
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
|
||||
Guid? RaisedByUserId = null);
|
||||
|
||||
// Only editable while the claim is still Open — Approve/Reject resolve it, and a resolved
|
||||
// claim's details/items shouldn't silently change after the fact.
|
||||
public record UpdateSupplierClaimRequest(
|
||||
string Details,
|
||||
string? EvidenceUrl,
|
||||
List<SupplierClaimItemRequest> Items);
|
||||
@@ -13,9 +22,14 @@ public record CreateSupplierClaimRequest(
|
||||
public record ApproveSupplierClaimRequest(
|
||||
SupplierClaimResolutionType ResolutionType,
|
||||
decimal? ResolutionAmount,
|
||||
string? ResolutionNotes);
|
||||
string? ResolutionNotes,
|
||||
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
|
||||
Guid? ApprovedByUserId = null);
|
||||
|
||||
public record RejectSupplierClaimRequest(string? ResolutionNotes);
|
||||
public record RejectSupplierClaimRequest(
|
||||
string? ResolutionNotes,
|
||||
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
|
||||
Guid? RejectedByUserId = null);
|
||||
|
||||
public record SupplierClaimItemResponse(
|
||||
Guid Id,
|
||||
|
||||
@@ -10,6 +10,13 @@ public class SupplierClaimRepository(AppDBContext context) : ISupplierClaimRepos
|
||||
.Include(c => c.Items).ThenInclude(i => i.Item)
|
||||
.FirstOrDefaultAsync(c => c.Id == id);
|
||||
|
||||
public async Task<List<Models.SupplierClaim>> GetAllAsync() =>
|
||||
await context.SupplierClaims
|
||||
.Include(c => c.Items).ThenInclude(i => i.Item)
|
||||
.Where(c => c.IsActive)
|
||||
.OrderByDescending(c => c.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<List<Models.SupplierClaim>> GetByPurchaseOrderIdAsync(Guid purchaseOrderId) =>
|
||||
await context.SupplierClaims
|
||||
.Include(c => c.Items).ThenInclude(i => i.Item)
|
||||
|
||||
@@ -26,6 +26,9 @@ public class SupplierClaimService(
|
||||
return Map(c);
|
||||
}
|
||||
|
||||
public async Task<List<SupplierClaimResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync()).Select(Map).ToList();
|
||||
|
||||
public async Task<List<SupplierClaimResponse>> GetByPurchaseOrderIdAsync(Guid purchaseOrderId) =>
|
||||
(await repo.GetByPurchaseOrderIdAsync(purchaseOrderId)).Select(Map).ToList();
|
||||
|
||||
@@ -83,6 +86,44 @@ public class SupplierClaimService(
|
||||
return Map(saved!);
|
||||
}
|
||||
|
||||
public async Task<SupplierClaimResponse> UpdateAsync(Guid id, UpdateSupplierClaimRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var claim = await repo.GetByIdAsync(id)
|
||||
?? throw new InvalidOperationException("Supplier claim not found.");
|
||||
if (claim.Status != SupplierClaimStatus.Open)
|
||||
throw new InvalidOperationException("Only an open claim can be edited.");
|
||||
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.");
|
||||
|
||||
claim.Details = request.Details;
|
||||
claim.EvidenceUrl = request.EvidenceUrl;
|
||||
|
||||
foreach (var existing in claim.Items.Where(i => i.IsActive))
|
||||
existing.IsActive = false;
|
||||
|
||||
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 { repo.Update(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)
|
||||
|
||||
Reference in New Issue
Block a user