using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SIGCM2.Api.Authorization; using SIGCM2.Application.Abstractions; using SIGCM2.Application.Rubros.Create; using SIGCM2.Application.Rubros.Deactivate; using SIGCM2.Application.Rubros.Dtos; using SIGCM2.Application.Rubros.GetById; using SIGCM2.Application.Rubros.GetTree; using SIGCM2.Application.Rubros.Move; using SIGCM2.Application.Rubros.Update; namespace SIGCM2.Api.Controllers; /// /// CAT-001: Rubro N-ary tree management. /// Read endpoints at /api/v1/rubros — require authentication (any role). /// Write endpoints at /api/v1/admin/rubros — require 'catalogo:rubros:gestionar'. /// [ApiController] public sealed class RubrosController : ControllerBase { private readonly IDispatcher _dispatcher; public RubrosController(IDispatcher dispatcher) { _dispatcher = dispatcher; } // ── READ endpoints ───────────────────────────────────────────────────────── /// Returns the full Rubro tree. Requires authentication. [HttpGet("api/v1/rubros/tree")] [Authorize] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task GetRubroTree([FromQuery] bool incluirInactivos = false) { var query = new GetRubroTreeQuery(incluirInactivos); var result = await _dispatcher.Send>(query); return Ok(result); } /// Returns a single Rubro by id. Requires authentication. [HttpGet("api/v1/rubros/{id:int}")] [Authorize] [ProducesResponseType(typeof(RubroDetailDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetRubroById([FromRoute] int id) { var query = new GetRubroByIdQuery(id); var result = await _dispatcher.Send(query); return Ok(result); } // ── WRITE endpoints ──────────────────────────────────────────────────────── /// Creates a new Rubro. Requires catalogo:rubros:gestionar. [HttpPost("api/v1/admin/rubros")] [RequirePermission("catalogo:rubros:gestionar")] [ProducesResponseType(typeof(RubroCreatedDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] public async Task CreateRubro([FromBody] CreateRubroRequest request) { var command = new CreateRubroCommand( Nombre: request.Nombre ?? string.Empty, ParentId: request.ParentId, TarifarioBaseId: request.TarifarioBaseId); var result = await _dispatcher.Send(command); return CreatedAtAction(nameof(GetRubroById), new { id = result.Id }, result); } /// Updates a Rubro's nombre. Requires catalogo:rubros:gestionar. [HttpPut("api/v1/admin/rubros/{id:int}")] [RequirePermission("catalogo:rubros:gestionar")] [ProducesResponseType(typeof(RubroUpdatedDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task UpdateRubro([FromRoute] int id, [FromBody] UpdateRubroRequest request) { var command = new UpdateRubroCommand( Id: id, Nombre: request.Nombre ?? string.Empty); var result = await _dispatcher.Send(command); return Ok(result); } /// Soft-deletes (deactivates) a Rubro. Requires catalogo:rubros:gestionar. [HttpDelete("api/v1/admin/rubros/{id:int}")] [RequirePermission("catalogo:rubros:gestionar")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task DeactivateRubro([FromRoute] int id) { var command = new DeactivateRubroCommand(id); await _dispatcher.Send(command); return NoContent(); } /// Moves a Rubro to a new parent. Requires catalogo:rubros:gestionar. [HttpPatch("api/v1/admin/rubros/{id:int}/mover")] [RequirePermission("catalogo:rubros:gestionar")] [ProducesResponseType(typeof(RubroMovedDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] public async Task MoveRubro([FromRoute] int id, [FromBody] MoveRubroRequest request) { var command = new MoveRubroCommand( Id: id, NuevoParentId: request.NuevoParentId, NuevoOrden: request.NuevoOrden); var result = await _dispatcher.Send(command); return Ok(result); } } // ── Request body records ────────────────────────────────────────────────────── /// CAT-001: Create rubro request body. public sealed record CreateRubroRequest( string? Nombre, int? ParentId, int? TarifarioBaseId); /// CAT-001: Update rubro request body. public sealed record UpdateRubroRequest( string? Nombre); /// CAT-001: Move rubro request body. public sealed record MoveRubroRequest( int? NuevoParentId, int NuevoOrden);