using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using SIGCM2.Api.Authorization;
using SIGCM2.Application.Abstractions;
using SIGCM2.Application.Common;
using SIGCM2.Application.PuntosDeVenta.Create;
using SIGCM2.Application.PuntosDeVenta.Deactivate;
using SIGCM2.Application.PuntosDeVenta.GetById;
using SIGCM2.Application.PuntosDeVenta.List;
using SIGCM2.Application.PuntosDeVenta.Reactivate;
using SIGCM2.Application.PuntosDeVenta.Update;
namespace SIGCM2.Api.Controllers;
///
/// ADM-008: PuntoDeVenta management endpoints at /api/v1/admin/puntos-de-venta.
/// All endpoints require permission 'administracion:puntos_de_venta:gestionar'.
///
[ApiController]
[Route("api/v1/admin/puntos-de-venta")]
public sealed class PuntosDeVentaController : ControllerBase
{
private readonly IDispatcher _dispatcher;
private readonly IValidator _createValidator;
private readonly IValidator _updateValidator;
public PuntosDeVentaController(
IDispatcher dispatcher,
IValidator createValidator,
IValidator updateValidator)
{
_dispatcher = dispatcher;
_createValidator = createValidator;
_updateValidator = updateValidator;
}
/// Creates a new punto de venta. Requires administracion:puntos_de_venta:gestionar.
[HttpPost]
[RequirePermission("administracion:puntos_de_venta:gestionar")]
[ProducesResponseType(typeof(PuntoDeVentaCreatedDto), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task CreatePuntoDeVenta([FromBody] CreatePuntoDeVentaRequest request)
{
var command = new CreatePuntoDeVentaCommand(
MedioId: request.MedioId ?? 0,
NumeroAFIP: request.NumeroAFIP ?? 0,
Nombre: request.Nombre ?? string.Empty,
Descripcion: request.Descripcion);
var validation = await _createValidator.ValidateAsync(command);
if (!validation.IsValid)
{
var errors = validation.Errors
.GroupBy(e => e.PropertyName)
.ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray());
return BadRequest(new { errors });
}
var result = await _dispatcher.Send(command);
return CreatedAtAction(nameof(GetPuntoDeVentaById), new { id = result.Id }, result);
}
/// Lists puntos de venta with optional filters.
[HttpGet]
[RequirePermission("administracion:puntos_de_venta:gestionar")]
[ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task ListPuntosDeVenta(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
[FromQuery] int? medioId = null,
[FromQuery] bool? activo = null)
{
if (page < 1) return BadRequest(new { error = "page must be >= 1" });
if (pageSize < 1) return BadRequest(new { error = "pageSize must be >= 1" });
var query = new ListPuntosDeVentaQuery(page, pageSize, medioId, activo);
var result = await _dispatcher.Send>(query);
return Ok(result);
}
/// Gets a single punto de venta by id.
[HttpGet("{id:int}")]
[RequirePermission("administracion:puntos_de_venta:gestionar")]
[ProducesResponseType(typeof(PuntoDeVentaDetailDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task GetPuntoDeVentaById([FromRoute] int id)
{
var query = new GetPuntoDeVentaByIdQuery(id);
var result = await _dispatcher.Send(query);
return Ok(result);
}
/// Updates a punto de venta's editable fields.
[HttpPut("{id:int}")]
[RequirePermission("administracion:puntos_de_venta:gestionar")]
[ProducesResponseType(typeof(PuntoDeVentaUpdatedDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task UpdatePuntoDeVenta([FromRoute] int id, [FromBody] UpdatePuntoDeVentaRequest request)
{
var command = new UpdatePuntoDeVentaCommand(
Id: id,
Nombre: request.Nombre ?? string.Empty,
NumeroAFIP: request.NumeroAFIP ?? 0,
Descripcion: request.Descripcion);
var validation = await _updateValidator.ValidateAsync(command);
if (!validation.IsValid)
{
var errors = validation.Errors
.GroupBy(e => e.PropertyName)
.ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray());
return BadRequest(new { errors });
}
var result = await _dispatcher.Send(command);
return Ok(result);
}
/// Deactivates a punto de venta.
[HttpPost("{id:int}/deactivate")]
[RequirePermission("administracion:puntos_de_venta:gestionar")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task DeactivatePuntoDeVenta([FromRoute] int id)
{
var command = new DeactivatePuntoDeVentaCommand(id);
await _dispatcher.Send(command);
return NoContent();
}
/// Reactivates a punto de venta (only if parent Medio is active).
[HttpPost("{id:int}/reactivate")]
[RequirePermission("administracion:puntos_de_venta:gestionar")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task ReactivatePuntoDeVenta([FromRoute] int id)
{
var command = new ReactivatePuntoDeVentaCommand(id);
await _dispatcher.Send(command);
return NoContent();
}
}
// ── Request body records ──────────────────────────────────────────────────────
/// ADM-008: Create punto de venta request body.
public sealed record CreatePuntoDeVentaRequest(
int? MedioId,
short? NumeroAFIP,
string? Nombre,
string? Descripcion);
/// ADM-008: Update punto de venta request body.
public sealed record UpdatePuntoDeVentaRequest(
string? Nombre,
short? NumeroAFIP,
string? Descripcion);