< Summary

Information
Class: FAU.API.Controllers.PeriodosController
Assembly: FAU.API
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/PeriodosController.cs
Line coverage
58%
Covered lines: 89
Uncovered lines: 63
Coverable lines: 152
Total lines: 358
Line coverage: 58.5%
Branch coverage
63%
Covered branches: 47
Total branches: 74
Branch coverage: 63.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
AbrirPeriodo()80%131069.23%
GetUltimos()0%620%
GetPeriodoActivo()100%2262.5%
GetPeriodo()100%2262.5%
GetPrecondiciones()0%620%
ValidarInsumos()0%620%
CerrarInsumos()75%201675%
ReabrirInsumos()75%11863.63%
ReabrirDesdeCalculada()0%7280%
Calcular()87.5%88100%
<Calcular()100%1130%
Cerrar()87.5%9872.72%
MapearPeriodo(...)50%66100%

File(s)

/home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.API/Controllers/PeriodosController.cs

#LineLine coverage
 1using FAU.Entidades;
 2using FAU.Logica.DTOs;
 3using FAU.Logica.Exceptions;
 4using FAU.Logica.Services;
 5using Microsoft.AspNetCore.Authorization;
 6using Microsoft.AspNetCore.Mvc;
 7
 8namespace FAU.API.Controllers;
 9
 10[ApiController]
 11[Route("api/liquidacion/[controller]")]
 12[Authorize]
 13[Produces("application/json")]
 14public class PeriodosController : ControllerBase
 15{
 16    private readonly IPeriodoService _periodoService;
 17    private readonly ILiquidacionService _liquidacionService;
 18    private readonly ILogger<PeriodosController> _logger;
 19    private readonly IServiceScopeFactory _scopeFactory;
 20
 1921    public PeriodosController(
 1922        IPeriodoService periodoService,
 1923        ILiquidacionService liquidacionService,
 1924        ILogger<PeriodosController> logger,
 1925        IServiceScopeFactory scopeFactory)
 26    {
 1927        _periodoService       = periodoService;
 1928        _liquidacionService   = liquidacionService;
 1929        _logger               = logger;
 1930        _scopeFactory         = scopeFactory;
 1931    }
 32
 33    [HttpPost]
 34    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 35    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status201Created)]
 36    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status400BadRequest)]
 37    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 38    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 39    public async Task<IActionResult> AbrirPeriodo([FromBody] AbrirPeriodoRequest request)
 40    {
 41        try
 42        {
 343            if (!ModelState.IsValid)
 44            {
 045                return BadRequest(ApiResponse<string>.ErrorResult("Solicitud de apertura de período inválida"));
 46            }
 47
 348            var (success, periodo, error) = await _periodoService.AbrirPeriodoAsync(request.Anio, request.Mes);
 49
 350            if (!success)
 51            {
 252                if (error?.Contains("Ya existe un período activo", StringComparison.OrdinalIgnoreCase) == true)
 53                {
 154                    return Conflict(ApiResponse<string>.ErrorResult(error));
 55                }
 56
 157                return BadRequest(ApiResponse<string>.ErrorResult(error ?? "Error al abrir el período"));
 58            }
 59
 160            var periodoFinal = await _periodoService.ObtenerPorIdAsync(periodo!.Id);
 161            return CreatedAtAction(nameof(GetPeriodo), new { id = periodo.Id }, ApiResponse<PeriodoDto>.SuccessResult(Ma
 62        }
 063        catch (Exception ex)
 64        {
 065            _logger.LogError(ex, "Error al abrir período");
 066            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 67        }
 368    }
 69
 70    [HttpGet]
 71    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 72    [ProducesResponseType(typeof(ApiResponse<List<PeriodoDto>>), StatusCodes.Status200OK)]
 73    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 74    public async Task<IActionResult> GetUltimos([FromQuery] int cantidad = 6)
 75    {
 76        try
 77        {
 078            var periodos = await _periodoService.ObtenerUltimosAsync(Math.Min(cantidad, 12));
 079            return Ok(ApiResponse<List<PeriodoDto>>.SuccessResult(periodos.Select(MapearPeriodo).ToList()));
 80        }
 081        catch (Exception ex)
 82        {
 083            _logger.LogError(ex, "Error al obtener últimos períodos");
 084            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 85        }
 086    }
 87
 88    [HttpGet("activo")]
 89    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 90    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 91    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 92    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 93    public async Task<IActionResult> GetPeriodoActivo()
 94    {
 95        try
 96        {
 297            var periodo = await _periodoService.ObtenerEnCursoAsync();
 298            if (periodo == null)
 99            {
 1100                return NotFound(ApiResponse<string>.ErrorResult("No hay un período activo"));
 101            }
 102
 1103            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodo)));
 104        }
 0105        catch (Exception ex)
 106        {
 0107            _logger.LogError(ex, "Error al obtener período activo");
 0108            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 109        }
 2110    }
 111
 112    [HttpGet("{id}")]
 113    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 114    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 115    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 116    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 117    public async Task<IActionResult> GetPeriodo(long id)
 118    {
 119        try
 120        {
 2121            var periodo = await _periodoService.ObtenerPorIdAsync(id);
 2122            if (periodo == null)
 123            {
 1124                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 125            }
 126
 1127            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodo)));
 128        }
 0129        catch (Exception ex)
 130        {
 0131            _logger.LogError(ex, "Error al obtener período {Id}", id);
 0132            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 133        }
 2134    }
 135
 136    [HttpGet("{id}/precondiciones")]
 137    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 138    [ProducesResponseType(typeof(ApiResponse<PrecondicionesDto>), StatusCodes.Status200OK)]
 139    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 140    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 141    public async Task<IActionResult> GetPrecondiciones(long id)
 142    {
 143        try
 144        {
 0145            var precondiciones = await _periodoService.ObtenerPrecondicionesAsync(id);
 0146            if (precondiciones == null)
 0147                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 0148            return Ok(ApiResponse<PrecondicionesDto>.SuccessResult(precondiciones));
 149        }
 0150        catch (Exception ex)
 151        {
 0152            _logger.LogError(ex, "Error al obtener precondiciones del período {Id}", id);
 0153            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 154        }
 0155    }
 156
 157    [HttpGet("{id}/validar-insumos")]
 158    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 159    [ProducesResponseType(typeof(ApiResponse<ValidacionInsumosDto>), StatusCodes.Status200OK)]
 160    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 161    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 162    public async Task<IActionResult> ValidarInsumos(long id)
 163    {
 164        try
 165        {
 0166            var validacion = await _periodoService.ValidarInsumosAsync(id);
 0167            if (validacion == null)
 0168                return NotFound(ApiResponse<string>.ErrorResult("Período no encontrado"));
 0169            return Ok(ApiResponse<ValidacionInsumosDto>.SuccessResult(validacion));
 170        }
 0171        catch (Exception ex)
 172        {
 0173            _logger.LogError(ex, "Error al validar insumos del período {Id}", id);
 0174            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 175        }
 0176    }
 177
 178    [HttpPost("{id}/cerrar-insumos")]
 179    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 180    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 181    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 182    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 183    [ProducesResponseType(typeof(ValidacionInsumosDto), StatusCodes.Status422UnprocessableEntity)]
 184    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 185    public async Task<IActionResult> CerrarInsumos(long id, [FromBody] CerrarInsumosRequest? request = null)
 186    {
 187        try
 188        {
 4189            var (success, periodo, validacion, error) =
 4190                await _periodoService.CerrarInsumosAsync(id, request?.Confirmaciones);
 191
 4192            if (!success)
 193            {
 3194                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 1195                    return NotFound(ApiResponse<string>.ErrorResult(error));
 2196                if (error?.Contains("Solo se puede", StringComparison.OrdinalIgnoreCase) == true)
 1197                    return Conflict(ApiResponse<string>.ErrorResult(error));
 1198                if (validacion != null)
 1199                    return UnprocessableEntity(validacion);
 0200                return UnprocessableEntity(ApiResponse<string>.ErrorResult(error ?? "Error al cerrar insumos"));
 201            }
 202
 1203            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1204            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodoFinal!), "Insumos cerrados. Período lis
 205        }
 0206        catch (Exception ex)
 207        {
 0208            _logger.LogError(ex, "Error al cerrar insumos del período {Id}", id);
 0209            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 210        }
 4211    }
 212
 213    [HttpPost("{id}/reabrir-insumos")]
 214    [Authorize(Roles = "Administrador,Supervisor")]
 215    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 216    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 217    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 218    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 219    public async Task<IActionResult> ReabrirInsumos(long id)
 220    {
 221        try
 222        {
 2223            var (success, periodo, error) = await _periodoService.ReabrirInsumosAsync(id);
 224
 2225            if (!success)
 226            {
 1227                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 0228                    return NotFound(ApiResponse<string>.ErrorResult(error));
 1229                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al reabrir insumos"));
 230            }
 231
 1232            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1233            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodoFinal!), "Insumos reabiertos. Período e
 234        }
 0235        catch (Exception ex)
 236        {
 0237            _logger.LogError(ex, "Error al reabrir insumos del período {Id}", id);
 0238            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 239        }
 2240    }
 241
 242    [HttpPost("{id}/reabrir")]
 243    [Authorize(Roles = "Administrador,Supervisor")]
 244    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 245    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 246    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 247    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 248    public async Task<IActionResult> ReabrirDesdeCalculada(long id)
 249    {
 250        try
 251        {
 0252            var (success, periodo, error) = await _periodoService.ReabrirDesdeCalculadaAsync(id);
 253
 0254            if (!success)
 255            {
 0256                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 0257                    return NotFound(ApiResponse<string>.ErrorResult(error));
 0258                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al reabrir período"));
 259            }
 260
 0261            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 0262            return Ok(ApiResponse<PeriodoDto>.SuccessResult(
 0263                MapearPeriodo(periodoFinal!),
 0264                "Período reabierto. Realizá las correcciones y volvé a calcular. El próximo cálculo generará una nueva v
 265        }
 0266        catch (Exception ex)
 267        {
 0268            _logger.LogError(ex, "Error al reabrir período {Id}", id);
 0269            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 270        }
 0271    }
 272
 273    [HttpPost("{id}/calcular")]
 274    [Authorize(Roles = "Administrador,Supervisor,Usuario")]
 275    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status202Accepted)]
 276    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 277    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 278    public async Task<IActionResult> Calcular(long id)
 279    {
 3280        var (success, _, error) = await _periodoService.IniciarCalculoAsync(id);
 3281        if (!success)
 282        {
 2283            if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 1284                return NotFound(ApiResponse<string>.ErrorResult(error));
 1285            return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al iniciar cálculo"));
 286        }
 287
 288        // ponytail: fire-and-forget con scope propio; los servicios scoped se disponen al terminar el request
 1289        _ = Task.Run(async () =>
 1290        {
 1291            using var scope = _scopeFactory.CreateScope();
 1292            var liquidacionService = scope.ServiceProvider.GetRequiredService<ILiquidacionService>();
 0293            var periodoService     = scope.ServiceProvider.GetRequiredService<IPeriodoService>();
 1294            try
 1295            {
 0296                var (exito, _, __) = await liquidacionService.GenerarLiquidacionAsync(id);
 0297                await periodoService.FinalizarCalculoAsync(id, exito);
 0298            }
 0299            catch (Exception ex)
 1300            {
 0301                _logger.LogError(ex, "Error en cálculo background del período {Id}", id);
 0302                await periodoService.FinalizarCalculoAsync(id, exitoso: false);
 1303            }
 1304        });
 305
 1306        return Accepted(ApiResponse<string>.SuccessResult("en_proceso", "Cálculo iniciado. Consultá el estado del períod
 3307    }
 308
 309    [HttpPost("{id}/cerrar")]
 310    [Authorize(Roles = "Administrador,Supervisor")]
 311    [ProducesResponseType(typeof(ApiResponse<PeriodoDto>), StatusCodes.Status200OK)]
 312    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status404NotFound)]
 313    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status409Conflict)]
 314    [ProducesResponseType(typeof(ApiResponse<string>), StatusCodes.Status500InternalServerError)]
 315    public async Task<IActionResult> Cerrar(long id)
 316    {
 317        try
 318        {
 3319            var (success, periodo, error) = await _periodoService.CerrarAsync(id);
 320
 3321            if (!success)
 322            {
 2323                if (error?.Contains("no encontrado", StringComparison.OrdinalIgnoreCase) == true)
 1324                    return NotFound(ApiResponse<string>.ErrorResult(error));
 1325                return Conflict(ApiResponse<string>.ErrorResult(error ?? "Error al cerrar período"));
 326            }
 327
 1328            var periodoFinal = await _periodoService.ObtenerPorIdAsync(id);
 1329            return Ok(ApiResponse<PeriodoDto>.SuccessResult(MapearPeriodo(periodoFinal!), "Período cerrado definitivamen
 330        }
 0331        catch (Exception ex)
 332        {
 0333            _logger.LogError(ex, "Error al cerrar período {Id}", id);
 0334            return StatusCode(500, ApiResponse<string>.ErrorResult("Error interno del servidor"));
 335        }
 3336    }
 337
 338    private static PeriodoDto MapearPeriodo(Periodo periodo)
 339    {
 6340        return new PeriodoDto
 6341        {
 6342            Id = periodo.Id,
 6343            Anio = periodo.Anio,
 6344            Mes = periodo.Mes,
 6345            Estado = periodo.Estado,
 6346            SnapshotId = periodo.SnapshotId,
 6347            FechaApertura = periodo.FechaApertura,
 6348            UsuarioApertura = periodo.UsuarioAperturaUsername,
 6349            HashSnapshot = periodo.HashSnapshot,
 6350            FechaCierreInsumos = periodo.FechaCierreInsumos,
 6351            UsuarioCierreInsumos = periodo.UsuarioCierreInsumos?.Username,
 6352            FechaCalculo = periodo.FechaCalculo,
 6353            UsuarioCalculo = periodo.UsuarioCalculo?.Username,
 6354            FechaCierre = periodo.FechaCierre,
 6355            UsuarioCierre = periodo.UsuarioCierre?.Username
 6356        };
 357    }
 358}