137 lines
5.2 KiB
C#
137 lines
5.2 KiB
C#
using Distribution.Infra.Authorization;
|
|
using Distribution.Models;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace Distribution.Features.Stock;
|
|
|
|
[ApiController]
|
|
[Route("api/stock")]
|
|
public class StockController(StockService service, ICurrentUserService currentUser) : ControllerBase
|
|
{
|
|
// ── Query ──────────────────────────────────────────────────────────────────
|
|
|
|
[HttpGet("balances")]
|
|
[Authorize("stock.view")]
|
|
public async Task<IActionResult> GetBalances(
|
|
[FromQuery] Guid? locationId,
|
|
[FromQuery] LocationType? locationType,
|
|
[FromQuery] Guid? itemId,
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 20)
|
|
{
|
|
var result = await service.GetBalancesAsync(locationId, locationType, itemId, page, pageSize);
|
|
return Ok(result);
|
|
}
|
|
|
|
[HttpGet("low-stock")]
|
|
[Authorize("stock.view")]
|
|
public async Task<IActionResult> GetLowStock([FromQuery] Guid? locationId)
|
|
{
|
|
var result = await service.GetLowStockAsync(locationId);
|
|
return Ok(result);
|
|
}
|
|
|
|
[HttpGet("balances/{itemId}/total")]
|
|
[Authorize("stock.view")]
|
|
public async Task<IActionResult> GetItemTotal(Guid itemId)
|
|
{
|
|
var result = await service.GetItemTotalAsync(itemId);
|
|
if (result is null) return NotFound();
|
|
return Ok(result);
|
|
}
|
|
|
|
/// <summary>Next free number for auto-generated serials (e.g. after ...-0005 the answer is 6).</summary>
|
|
[HttpGet("serials/next-sequence")]
|
|
[Authorize("stock.view")]
|
|
public async Task<IActionResult> GetNextSerialSequence([FromQuery] Guid itemId, [FromQuery] string prefix = "")
|
|
{
|
|
var next = await service.GetNextSerialSequenceAsync(itemId, prefix);
|
|
return Ok(new { next });
|
|
}
|
|
|
|
[HttpGet("movements")]
|
|
[Authorize("stock.view")]
|
|
public async Task<IActionResult> GetMovements(
|
|
[FromQuery] Guid? locationId,
|
|
[FromQuery] Guid? itemId,
|
|
[FromQuery] MovementType? type,
|
|
[FromQuery] DateTime? from,
|
|
[FromQuery] DateTime? to,
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 20)
|
|
{
|
|
var result = await service.GetMovementsAsync(locationId, itemId, type, from, to, page, pageSize);
|
|
return Ok(result);
|
|
}
|
|
|
|
// ── Write ──────────────────────────────────────────────────────────────────
|
|
|
|
[HttpPost("transfer")]
|
|
[Authorize("stock.manage")]
|
|
public async Task<IActionResult> Transfer(TransferRequest request, CancellationToken ct)
|
|
{
|
|
if (currentUser.UserId is null) return Unauthorized();
|
|
try
|
|
{
|
|
var result = await service.TransferAsync(request, currentUser.UserId.Value, ct);
|
|
return Ok(result);
|
|
}
|
|
catch (InvalidOperationException ex) { return BadRequest(ex.Message); }
|
|
}
|
|
|
|
[HttpPost("damage")]
|
|
[Authorize("stock.manage")]
|
|
public async Task<IActionResult> RecordDamage(DamageRequest request, CancellationToken ct)
|
|
{
|
|
if (currentUser.UserId is null) return Unauthorized();
|
|
try
|
|
{
|
|
var result = await service.RecordDamageAsync(request, currentUser.UserId.Value, ct);
|
|
return Ok(result);
|
|
}
|
|
catch (InvalidOperationException ex) { return BadRequest(ex.Message); }
|
|
}
|
|
|
|
[HttpPost("customer-return")]
|
|
[Authorize("stock.manage")]
|
|
public async Task<IActionResult> RecordCustomerReturn(CustomerReturnRequest request, CancellationToken ct)
|
|
{
|
|
if (currentUser.UserId is null) return Unauthorized();
|
|
try
|
|
{
|
|
var result = await service.RecordCustomerReturnAsync(request, currentUser.UserId.Value, ct);
|
|
return Ok(result);
|
|
}
|
|
catch (InvalidOperationException ex) { return BadRequest(ex.Message); }
|
|
}
|
|
|
|
[HttpPost("adjustment")]
|
|
[Authorize("stock.manage")]
|
|
public async Task<IActionResult> Adjust(AdjustmentRequest request, CancellationToken ct)
|
|
{
|
|
// 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.AdjustedByUserId;
|
|
if (userId is null) return BadRequest(new { error = "AdjustedByUserId is required while auth is unavailable" });
|
|
try
|
|
{
|
|
var result = await service.AdjustAsync(request, userId.Value, ct);
|
|
return Ok(result);
|
|
}
|
|
catch (InvalidOperationException ex) { return BadRequest(ex.Message); }
|
|
}
|
|
|
|
[HttpPatch("movements/{id}/reason")]
|
|
[Authorize("stock.manage")]
|
|
public async Task<IActionResult> UpdateMovementReason(Guid id, UpdateMovementReasonRequest request, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
var result = await service.UpdateMovementReasonAsync(id, request, ct);
|
|
return Ok(result);
|
|
}
|
|
catch (InvalidOperationException ex) { return BadRequest(ex.Message); }
|
|
}
|
|
}
|