feat(contables): cierre mensual de cuenta corriente de distribuidor

Permite congelar el saldo de un distribuidor por empresa a una fecha de
corte y bloquear modificaciones retroactivas sobre el período cerrado.
El saldo se calcula sumando movimientos en rango (sin tocar cue_Saldos).
Incluye reapertura controlada exclusivamente por SuperAdmin, reporte con
saldo inicial, atajo "Desde último cierre", y auditoría del ciclo de
vida _H. Permisos CC001/CC002/CC003. Middleware global mapea bloqueos
por período cerrado a HTTP 409.
This commit is contained in:
2026-05-07 12:03:26 -03:00
parent 7e274ef114
commit 24eaf18fd9
62 changed files with 2813 additions and 162 deletions

View File

@@ -20,6 +20,7 @@ namespace GestionIntegral.Api.Services.Contables
private readonly ITipoPagoRepository _tipoPagoRepo;
private readonly IEmpresaRepository _empresaRepo;
private readonly ISaldoRepository _saldoRepo;
private readonly IPeriodoCerradoValidator _periodoCerrado;
private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<PagoDistribuidorService> _logger;
@@ -29,6 +30,7 @@ namespace GestionIntegral.Api.Services.Contables
ITipoPagoRepository tipoPagoRepo,
IEmpresaRepository empresaRepo,
ISaldoRepository saldoRepo,
IPeriodoCerradoValidator periodoCerrado,
DbConnectionFactory connectionFactory,
ILogger<PagoDistribuidorService> logger)
{
@@ -37,6 +39,7 @@ namespace GestionIntegral.Api.Services.Contables
_tipoPagoRepo = tipoPagoRepo;
_empresaRepo = empresaRepo;
_saldoRepo = saldoRepo;
_periodoCerrado = periodoCerrado;
_connectionFactory = connectionFactory;
_logger = logger;
}
@@ -106,6 +109,11 @@ namespace GestionIntegral.Api.Services.Contables
return (null, mensajeError);
}
// Bloqueo por período cerrado: la fecha de operación no puede caer dentro de un cierre vigente.
var bloqueoCrear = await _periodoCerrado.EstaCerradoAsync("Distribuidores", createDto.IdDistribuidor, createDto.IdEmpresa, createDto.Fecha);
if (bloqueoCrear.EstaCerrado)
throw new BloqueoPorPeriodoCerradoException(bloqueoCrear.IdCierre!.Value, bloqueoCrear.FechaCorte!.Value);
var nuevoPago = new PagoDistribuidor
{
IdDistribuidor = createDto.IdDistribuidor,
@@ -182,6 +190,14 @@ namespace GestionIntegral.Api.Services.Contables
return (false, "Pago no encontrado.");
}
// Bloqueo por período cerrado sobre la fecha original del pago (el DTO de update no permite cambiar Fecha).
var bloqueoUpd = await _periodoCerrado.EstaCerradoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, pagoExistente.Fecha);
if (bloqueoUpd.EstaCerrado)
{
transaction.Rollback();
throw new BloqueoPorPeriodoCerradoException(bloqueoUpd.IdCierre!.Value, bloqueoUpd.FechaCorte!.Value);
}
if (await _tipoPagoRepo.GetByIdAsync(updateDto.IdTipoPago) == null)
{
transaction.Rollback();
@@ -219,6 +235,11 @@ namespace GestionIntegral.Api.Services.Contables
return (true, null);
}
catch (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync PagoDistribuidor (KeyNotFound)."); } return (false, "Pago no encontrado."); }
catch (BloqueoPorPeriodoCerradoException)
{
try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync PagoDistribuidor (Bloqueo)."); }
throw;
}
catch (Exception ex)
{
try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync PagoDistribuidor."); }
@@ -253,6 +274,14 @@ namespace GestionIntegral.Api.Services.Contables
return (false, "Pago no encontrado.");
}
// Bloqueo por período cerrado: no se puede eliminar un pago cuya fecha cae en un cierre vigente.
var bloqueoDel = await _periodoCerrado.EstaCerradoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, pagoExistente.Fecha);
if (bloqueoDel.EstaCerrado)
{
transaction.Rollback();
throw new BloqueoPorPeriodoCerradoException(bloqueoDel.IdCierre!.Value, bloqueoDel.FechaCorte!.Value);
}
decimal montoReversion = pagoExistente.TipoMovimiento == "Recibido" ? pagoExistente.Monto : -pagoExistente.Monto;
var eliminado = await _pagoRepo.DeleteAsync(idPago, idUsuario, transaction);
@@ -266,6 +295,11 @@ namespace GestionIntegral.Api.Services.Contables
return (true, null);
}
catch (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync PagoDistribuidor (KeyNotFound)."); } return (false, "Pago no encontrado."); }
catch (BloqueoPorPeriodoCerradoException)
{
try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync PagoDistribuidor (Bloqueo)."); }
throw;
}
catch (Exception ex)
{
try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync PagoDistribuidor."); }