Add GetAll and UpdateReasons endpoints to ReturnController
This commit is contained in:
@@ -3,8 +3,10 @@ namespace Distribution.Features.Returns;
|
||||
public interface IReturnRepository
|
||||
{
|
||||
Task<Models.SaleReturn?> GetByIdAsync(Guid id);
|
||||
Task<List<Models.SaleReturn>> GetAllAsync();
|
||||
Task<List<Models.SaleReturn>> GetByOrderIdAsync(Guid orderId);
|
||||
Task AddAsync(Models.SaleReturn entity);
|
||||
void Update(Models.SaleReturn entity);
|
||||
Task AddCreditNoteAsync(Models.CreditNote entity);
|
||||
|
||||
/// <summary>Whether this store has ever had this item on any of its (active) orders.</summary>
|
||||
|
||||
@@ -6,6 +6,10 @@ namespace Distribution.Features.Returns;
|
||||
[ApiController]
|
||||
public class ReturnController(ReturnService service, ICurrentUserService currentUser) : ControllerBase
|
||||
{
|
||||
[HttpGet("api/returns")]
|
||||
public async Task<IActionResult> GetAll() =>
|
||||
Ok(await service.GetAllAsync());
|
||||
|
||||
[HttpGet("api/returns/{id:guid}")]
|
||||
public async Task<IActionResult> GetById(Guid id)
|
||||
{
|
||||
@@ -13,6 +17,17 @@ public class ReturnController(ReturnService service, ICurrentUserService current
|
||||
return result is null ? NotFound() : Ok(result);
|
||||
}
|
||||
|
||||
[HttpPatch("api/returns/{id:guid}/reasons")]
|
||||
public async Task<IActionResult> UpdateReasons(Guid id, UpdateSaleReturnReasonsRequest request, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await service.UpdateReasonsAsync(id, request, ct);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); }
|
||||
}
|
||||
|
||||
[HttpGet("api/orders/{id:guid}/returns")]
|
||||
public async Task<IActionResult> GetByOrder(Guid id) =>
|
||||
Ok(await service.GetByOrderIdAsync(id));
|
||||
@@ -20,10 +35,13 @@ public class ReturnController(ReturnService service, ICurrentUserService current
|
||||
[HttpPost("api/orders/{id:guid}/returns")]
|
||||
public async Task<IActionResult> Create(Guid id, CreateSaleReturnRequest 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.CreatedByUserId;
|
||||
if (userId is null) return BadRequest(new { error = "CreatedByUserId 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 }); }
|
||||
|
||||
@@ -4,7 +4,17 @@ namespace Distribution.Features.Returns;
|
||||
|
||||
public record ReturnItemRequest(Guid ItemId, decimal Quantity, ReturnReasonCode ReasonCode);
|
||||
|
||||
public record CreateSaleReturnRequest(ReturnMode Mode, List<ReturnItemRequest> Items);
|
||||
public record CreateSaleReturnRequest(
|
||||
ReturnMode Mode,
|
||||
List<ReturnItemRequest> Items,
|
||||
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
|
||||
Guid? CreatedByUserId = null);
|
||||
|
||||
// The return's financial/stock effect (credit, order total, vehicle stock credit) is applied
|
||||
// immediately on create with no separate approval step, so quantities/mode can never be edited
|
||||
// after the fact without desyncing stock and order totals. Only each line's reason code is editable.
|
||||
public record UpdateReturnReasonItem(Guid ItemId, ReturnReasonCode ReasonCode);
|
||||
public record UpdateSaleReturnReasonsRequest(List<UpdateReturnReasonItem> Items);
|
||||
|
||||
public record SaleReturnItemResponse(
|
||||
Guid Id,
|
||||
|
||||
@@ -11,6 +11,14 @@ public class ReturnRepository(AppDBContext context) : IReturnRepository
|
||||
.Include(r => r.Items).ThenInclude(i => i.Item)
|
||||
.FirstOrDefaultAsync(r => r.Id == id);
|
||||
|
||||
public async Task<List<Models.SaleReturn>> GetAllAsync() =>
|
||||
await context.SaleReturns
|
||||
.Include(r => r.Order)
|
||||
.Include(r => r.Items).ThenInclude(i => i.Item)
|
||||
.Where(r => r.IsActive)
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<List<Models.SaleReturn>> GetByOrderIdAsync(Guid orderId) =>
|
||||
await context.SaleReturns
|
||||
.Include(r => r.Order)
|
||||
@@ -22,6 +30,9 @@ public class ReturnRepository(AppDBContext context) : IReturnRepository
|
||||
public async Task AddAsync(Models.SaleReturn entity) =>
|
||||
await context.SaleReturns.AddAsync(entity);
|
||||
|
||||
public void Update(Models.SaleReturn entity) =>
|
||||
context.SaleReturns.Update(entity);
|
||||
|
||||
public async Task AddCreditNoteAsync(Models.CreditNote entity) =>
|
||||
await context.CreditNotes.AddAsync(entity);
|
||||
|
||||
|
||||
@@ -31,6 +31,33 @@ public class ReturnService(
|
||||
return Map(r);
|
||||
}
|
||||
|
||||
public async Task<List<SaleReturnResponse>> GetAllAsync() =>
|
||||
(await repo.GetAllAsync()).Select(Map).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Quantities/mode are immutable once created (the stock/order effect already happened) —
|
||||
/// only each line's reason code can be corrected after the fact.
|
||||
/// </summary>
|
||||
public async Task<SaleReturnResponse> UpdateReasonsAsync(Guid id, UpdateSaleReturnReasonsRequest request, CancellationToken ct = default)
|
||||
{
|
||||
var r = await repo.GetByIdAsync(id);
|
||||
if (r is null || !r.IsActive)
|
||||
throw new InvalidOperationException("Return not found.");
|
||||
|
||||
foreach (var line in request.Items)
|
||||
{
|
||||
var item = r.Items.FirstOrDefault(i => i.FK_ItemId == line.ItemId && i.IsActive)
|
||||
?? throw new InvalidOperationException($"Item {line.ItemId} not found on this return.");
|
||||
item.ReasonCode = line.ReasonCode;
|
||||
}
|
||||
|
||||
await uow.BeginAsync(ct);
|
||||
try { repo.Update(r); await uow.CommitAsync(ct); }
|
||||
catch { await uow.RollbackAsync(ct); throw; }
|
||||
|
||||
return Map(r);
|
||||
}
|
||||
|
||||
public async Task<List<SaleReturnResponse>> GetByOrderIdAsync(Guid orderId) =>
|
||||
(await repo.GetByOrderIdAsync(orderId)).Select(Map).ToList();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user