using Distribution.Infra.Authorization; using Distribution.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Distribution.Features.Expense; [ApiController] [Route("api/expenses")] public class ExpenseController( ExpenseService service, IPermissionService permissions, ICurrentUserService currentUser) : ControllerBase { private async Task HasAsync(string code) { if (currentUser.RoleId is not { } roleId) return false; return await permissions.HasPermissionAsync(roleId, code); } [HttpGet] [Authorize("expense.view")] public async Task List( [FromQuery] ExpenseStatus? status, [FromQuery] Guid? categoryId, [FromQuery] Guid? warehouseId, [FromQuery] DateTime? from, [FromQuery] DateTime? to, [FromQuery] int page = 1, [FromQuery] int pageSize = 20) => Ok(await service.ListAsync(status, categoryId, warehouseId, from, to, page, pageSize)); [HttpGet("{id:guid}")] [Authorize("expense.view")] public async Task Get(Guid id) { var result = await service.GetAsync(id); return result is null ? NotFound() : Ok(result); } /// /// Create endpoint takes a required ?action= query to drive the 4 form buttons: /// draft → Save draft; post → Save & post (requires expense.post); pay → Save & pay /// (requires expense.post + expense.pay). Draft is the default. /// [HttpPost] [Authorize("expense.create")] public async Task Create( CreateExpenseRequest request, [FromQuery] ExpenseAction action = ExpenseAction.Draft, CancellationToken ct = default) { if (action is ExpenseAction.Post or ExpenseAction.Pay && !await HasAsync("expense.post")) return Forbid(); if (action == ExpenseAction.Pay && !await HasAsync("expense.pay")) return Forbid(); try { return Ok(await service.CreateAsync(request, action, ct)); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } } [HttpPut("{id:guid}")] [Authorize("expense.create")] public async Task UpdateDraft(Guid id, UpdateExpenseDraftRequest request, CancellationToken ct) { try { var result = await service.UpdateDraftAsync(id, request, ct); return result is null ? NotFound() : Ok(result); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } } [HttpPost("{id:guid}/post")] [Authorize("expense.post")] public async Task Post(Guid id, CancellationToken ct) { try { var result = await service.PostAsync(id, ct); return result is null ? NotFound() : Ok(result); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } } [HttpPost("{id:guid}/pay")] [Authorize("expense.pay")] public async Task Pay(Guid id, ExpensePaymentRequest request, CancellationToken ct) { try { var result = await service.PayAsync(id, request, ct); return result is null ? NotFound() : Ok(result); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } } [HttpPost("{id:guid}/void")] [Authorize("expense.void")] public async Task Void(Guid id, VoidExpenseRequest request, CancellationToken ct) { try { var result = await service.VoidAsync(id, request?.Reason, ct); return result is null ? NotFound() : Ok(result); } catch (InvalidOperationException ex) { return BadRequest(new { error = ex.Message }); } } }