Compare commits
38 Commits
9e248efc84
...
feat/cierr
| Author | SHA1 | Date | |
|---|---|---|---|
| 24eaf18fd9 | |||
| 7e274ef114 | |||
| 5212e31a03 | |||
| 9201d7222b | |||
| fc27b4b43e | |||
| 35e8d803b9 | |||
| 8e1b8d2326 | |||
| bc19e184aa | |||
| 29109cff13 | |||
| 7f1fadfc84 | |||
| 74f07df960 | |||
| 6ceb1477ae | |||
| c049c1e544 | |||
| 8c7278ceae | |||
| e8215f8586 | |||
| bf7d7c22ef | |||
| 2c584e9383 | |||
| e123dae182 | |||
| c27dc2a0ba | |||
| 24b1c07342 | |||
| cb64bbc1f5 | |||
| 057310ca47 | |||
| e95c851e5b | |||
| 038faefd35 | |||
| da50c052f1 | |||
| 5781713b13 | |||
| 9f8d577265 | |||
| b594a48fde | |||
| 2e7d1e36be | |||
| dd2277fce2 | |||
| 9412556fa8 | |||
| 8c194b8441 | |||
| 1a288fcfa5 | |||
| 7dc0940001 | |||
| 5a806eda38 | |||
| 21c5c1d7d9 | |||
| 899e0a173f | |||
| 9cfe9d012e |
@@ -26,12 +26,6 @@ jobs:
|
|||||||
set -e
|
set -e
|
||||||
echo "--- INICIO DEL DESPLIEGUE OPTIMIZADO ---"
|
echo "--- INICIO DEL DESPLIEGUE OPTIMIZADO ---"
|
||||||
|
|
||||||
# --- Asegurar que el Stack de la Base de Datos esté corriendo ---
|
|
||||||
echo "Asegurando que el stack de la base de datos esté activo..."
|
|
||||||
cd /opt/shared-services/database
|
|
||||||
# El comando 'up -d' es idempotente. Si ya está corriendo, no hace nada.
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
# 1. Preparar entorno
|
# 1. Preparar entorno
|
||||||
TEMP_DIR=$(mktemp -d)
|
TEMP_DIR=$(mktemp -d)
|
||||||
REPO_OWNER="dmolinari"
|
REPO_OWNER="dmolinari"
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -19,9 +19,6 @@ lerna-debug.log*
|
|||||||
|
|
||||||
# Variables de entorno
|
# Variables de entorno
|
||||||
# -------------------------------
|
# -------------------------------
|
||||||
# Nunca subas tus claves de API, contraseñas de BD, etc.
|
|
||||||
# Crea un archivo .env.example con las variables vacías para guiar a otros desarrolladores.
|
|
||||||
.env
|
|
||||||
.env.local
|
.env.local
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
@@ -163,4 +160,6 @@ junit.xml
|
|||||||
|
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
# Fin del archivo .gitignore
|
# Fin del archivo .gitignore
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
Backend/SQL
|
||||||
|
.atl
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
private readonly IPerfilService _perfilService;
|
private readonly IPerfilService _perfilService;
|
||||||
private readonly IPermisoService _permisoService;
|
private readonly IPermisoService _permisoService;
|
||||||
private readonly ICambioParadaService _cambioParadaService;
|
private readonly ICambioParadaService _cambioParadaService;
|
||||||
|
private readonly ICierreCuentaCorrienteService _cierreCcService;
|
||||||
private readonly ILogger<AuditoriaController> _logger;
|
private readonly ILogger<AuditoriaController> _logger;
|
||||||
|
|
||||||
// Permiso general para ver cualquier auditoría.
|
// Permiso general para ver cualquier auditoría.
|
||||||
@@ -86,6 +87,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
IPerfilService perfilService,
|
IPerfilService perfilService,
|
||||||
IPermisoService permisoService,
|
IPermisoService permisoService,
|
||||||
ICambioParadaService cambioParadaService,
|
ICambioParadaService cambioParadaService,
|
||||||
|
ICierreCuentaCorrienteService cierreCcService,
|
||||||
ILogger<AuditoriaController> logger)
|
ILogger<AuditoriaController> logger)
|
||||||
{
|
{
|
||||||
_usuarioService = usuarioService;
|
_usuarioService = usuarioService;
|
||||||
@@ -116,6 +118,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
_perfilService = perfilService;
|
_perfilService = perfilService;
|
||||||
_cambioParadaService = cambioParadaService;
|
_cambioParadaService = cambioParadaService;
|
||||||
_permisoService = permisoService;
|
_permisoService = permisoService;
|
||||||
|
_cierreCcService = cierreCcService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,6 +695,26 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("cierres-cuenta-corriente")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<CierreCuentaCorrienteHistorialDto>), StatusCodes.Status200OK)]
|
||||||
|
public async Task<IActionResult> GetHistorialCierresCuentaCorriente(
|
||||||
|
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
|
||||||
|
[FromQuery] int? idUsuarioModifico, [FromQuery] string? tipoModificacion,
|
||||||
|
[FromQuery] int? idCierreAfectado)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerAuditoria)) return Forbid();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var historial = await _cierreCcService.ObtenerHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idCierreAfectado);
|
||||||
|
return Ok(historial ?? Enumerable.Empty<CierreCuentaCorrienteHistorialDto>());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error obteniendo historial de Cierres de Cuenta Corriente.");
|
||||||
|
return StatusCode(500, "Error interno al obtener historial de Cierres de Cuenta Corriente.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("cambios-parada-canilla")]
|
[HttpGet("cambios-parada-canilla")]
|
||||||
[ProducesResponseType(typeof(IEnumerable<CambioParadaHistorialDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(IEnumerable<CambioParadaHistorialDto>), StatusCodes.Status200OK)]
|
||||||
public async Task<IActionResult> GetHistorialCambiosParada(
|
public async Task<IActionResult> GetHistorialCambiosParada(
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Comunicaciones;
|
||||||
|
using GestionIntegral.Api.Services.Comunicaciones;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Comunicaciones
|
||||||
|
{
|
||||||
|
[Route("api/lotes-envio")]
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class LotesEnvioController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IEmailLogService _emailLogService;
|
||||||
|
|
||||||
|
public LotesEnvioController(IEmailLogService emailLogService)
|
||||||
|
{
|
||||||
|
_emailLogService = emailLogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/lotes-envio/123/detalles
|
||||||
|
[HttpGet("{idLote:int}/detalles")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<EmailLogDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetDetallesLote(int idLote)
|
||||||
|
{
|
||||||
|
// Reutilizamos un permiso existente, ya que esta es una función de auditoría relacionada.
|
||||||
|
var tienePermiso = User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == "SU006");
|
||||||
|
if (!tienePermiso)
|
||||||
|
{
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
var detalles = await _emailLogService.ObtenerDetallesPorLoteId(idLote);
|
||||||
|
|
||||||
|
// Devolvemos OK con un array vacío si no hay resultados, el frontend lo manejará.
|
||||||
|
return Ok(detalles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Auditoria;
|
||||||
|
using GestionIntegral.Api.Dtos.Contables;
|
||||||
|
using GestionIntegral.Api.Services.Contables;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Contables
|
||||||
|
{
|
||||||
|
[Route("api/cierres-cc")]
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class CierresCuentaCorrienteController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ICierreCuentaCorrienteService _cierreService;
|
||||||
|
private readonly ILogger<CierresCuentaCorrienteController> _logger;
|
||||||
|
|
||||||
|
// Permisos asignables a perfiles. La reapertura (CC002) NO se valida acá: es exclusiva de SuperAdmin.
|
||||||
|
private const string PermisoCrear = "CC001";
|
||||||
|
private const string PermisoVer = "CC003";
|
||||||
|
|
||||||
|
public CierresCuentaCorrienteController(
|
||||||
|
ICierreCuentaCorrienteService cierreService,
|
||||||
|
ILogger<CierresCuentaCorrienteController> logger)
|
||||||
|
{
|
||||||
|
_cierreService = cierreService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) =>
|
||||||
|
User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
|
||||||
|
private bool EsSuperAdmin() => User.IsInRole("SuperAdmin");
|
||||||
|
|
||||||
|
private int? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId))
|
||||||
|
return userId;
|
||||||
|
_logger.LogWarning("No se pudo obtener el UserId del token JWT en CierresCuentaCorrienteController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/cierres-cc
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(CierreCuentaCorrienteDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
||||||
|
public async Task<IActionResult> Crear([FromBody] CrearCierreDto dto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoCrear)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (cierre, errorCode, errorMessage) = await _cierreService.CrearCierreAsync(dto, userId.Value);
|
||||||
|
|
||||||
|
if (errorCode != null)
|
||||||
|
{
|
||||||
|
int status = errorCode switch
|
||||||
|
{
|
||||||
|
"CIERRE_FECHA_ANTERIOR_A_ULTIMO" => StatusCodes.Status409Conflict,
|
||||||
|
"CIERRE_ERROR_INTERNO" => StatusCodes.Status500InternalServerError,
|
||||||
|
_ => StatusCodes.Status400BadRequest
|
||||||
|
};
|
||||||
|
return StatusCode(status, new { codigo = errorCode, mensaje = errorMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
return StatusCode(StatusCodes.Status201Created, cierre);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/cierres-cc/{idCierre}/reabrir
|
||||||
|
[HttpPost("{idCierre:int}/reabrir")]
|
||||||
|
[ProducesResponseType(typeof(CierreCuentaCorrienteDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
||||||
|
public async Task<IActionResult> Reabrir(int idCierre, [FromBody] ReabrirCierreDto dto)
|
||||||
|
{
|
||||||
|
if (!EsSuperAdmin())
|
||||||
|
return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (cierre, errorCode, errorMessage) = await _cierreService.ReabrirCierreAsync(idCierre, dto, userId.Value, esSuperAdmin: true);
|
||||||
|
|
||||||
|
if (errorCode != null)
|
||||||
|
{
|
||||||
|
int status = errorCode switch
|
||||||
|
{
|
||||||
|
"CIERRE_NO_ENCONTRADO" => StatusCodes.Status404NotFound,
|
||||||
|
"CIERRE_PERMISO_DENEGADO" => StatusCodes.Status403Forbidden,
|
||||||
|
"CIERRE_HAY_POSTERIORES_VIGENTES" => StatusCodes.Status409Conflict,
|
||||||
|
"CIERRE_YA_ANULADO" => StatusCodes.Status409Conflict,
|
||||||
|
"CIERRE_ERROR_INTERNO" => StatusCodes.Status500InternalServerError,
|
||||||
|
_ => StatusCodes.Status400BadRequest
|
||||||
|
};
|
||||||
|
return StatusCode(status, new { codigo = errorCode, mensaje = errorMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(cierre);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/cierres-cc?idDistribuidor=&idEmpresa=&estado=&fechaCorteDesde=&fechaCorteHasta=
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<CierreCuentaCorrienteDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetAll(
|
||||||
|
[FromQuery] int? idDistribuidor,
|
||||||
|
[FromQuery] int? idEmpresa,
|
||||||
|
[FromQuery] string? estado,
|
||||||
|
[FromQuery] DateTime? fechaCorteDesde,
|
||||||
|
[FromQuery] DateTime? fechaCorteHasta)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
var cierres = await _cierreService.GetAllAsync(idDistribuidor, idEmpresa, estado, fechaCorteDesde, fechaCorteHasta);
|
||||||
|
return Ok(cierres);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/cierres-cc/{idCierre}
|
||||||
|
[HttpGet("{idCierre:int}")]
|
||||||
|
[ProducesResponseType(typeof(CierreCuentaCorrienteDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetById(int idCierre)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
var cierre = await _cierreService.GetByIdAsync(idCierre);
|
||||||
|
if (cierre == null) return NotFound(new { message = $"Cierre #{idCierre} no encontrado." });
|
||||||
|
return Ok(cierre);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/cierres-cc/ultimo?idDistribuidor=&idEmpresa=
|
||||||
|
// Atajo del frontend para autorrellenar "Desde último cierre" en filtros del reporte.
|
||||||
|
// Acepta CC003 (gestión de cierres) o RR001 (acceso al reporte que CONTIENE este atajo):
|
||||||
|
// operadores con solo el permiso del reporte deben poder usar el atajo desde la pantalla del reporte.
|
||||||
|
[HttpGet("ultimo")]
|
||||||
|
[ProducesResponseType(typeof(UltimoCierreDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetUltimoVigente(
|
||||||
|
[FromQuery] int idDistribuidor,
|
||||||
|
[FromQuery] int idEmpresa)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVer) && !TienePermiso("RR001")) return Forbid();
|
||||||
|
var ultimo = await _cierreService.GetUltimoVigenteAsync(idDistribuidor, idEmpresa);
|
||||||
|
if (ultimo == null) return NotFound(new { message = "No hay cierres vigentes para el distribuidor en la empresa indicada." });
|
||||||
|
return Ok(ultimo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/cierres-cc/{idCierre}/historial
|
||||||
|
[HttpGet("{idCierre:int}/historial")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<CierreCuentaCorrienteHistorialDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetHistorial(int idCierre)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
var historial = await _cierreService.GetHistorialAsync(idCierre);
|
||||||
|
return Ok(historial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,9 +18,8 @@ namespace GestionIntegral.Api.Controllers.Contables
|
|||||||
private readonly ISaldoService _saldoService;
|
private readonly ISaldoService _saldoService;
|
||||||
private readonly ILogger<SaldosController> _logger;
|
private readonly ILogger<SaldosController> _logger;
|
||||||
|
|
||||||
// Define un permiso específico para ver saldos, y otro para ajustarlos (SuperAdmin implícito)
|
// Permiso para ver saldos. El ajuste manual es exclusivo de SuperAdmin (no se valida un permiso asignable).
|
||||||
private const string PermisoVerSaldos = "CS001"; // Ejemplo: Cuentas Saldos Ver
|
private const string PermisoVerSaldos = "CS001";
|
||||||
private const string PermisoAjustarSaldos = "CS002"; // Ejemplo: Cuentas Saldos Ajustar (o solo SuperAdmin)
|
|
||||||
|
|
||||||
|
|
||||||
public SaldosController(ISaldoService saldoService, ILogger<SaldosController> logger)
|
public SaldosController(ISaldoService saldoService, ILogger<SaldosController> logger)
|
||||||
@@ -76,11 +75,11 @@ namespace GestionIntegral.Api.Controllers.Contables
|
|||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> AjustarSaldoManualmente([FromBody] AjusteSaldoRequestDto ajusteDto)
|
public async Task<IActionResult> AjustarSaldoManualmente([FromBody] AjusteSaldoRequestDto ajusteDto)
|
||||||
{
|
{
|
||||||
// Esta operación debería ser MUY restringida. Solo SuperAdmin o un permiso muy específico.
|
// El ajuste manual de saldo es operación crítica: solo SuperAdmin. No se admite vía permiso asignable.
|
||||||
if (!User.IsInRole("SuperAdmin") && !TienePermiso(PermisoAjustarSaldos))
|
if (!User.IsInRole("SuperAdmin"))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Intento no autorizado de ajustar saldo por Usuario ID {userId}", GetCurrentUserId() ?? 0);
|
_logger.LogWarning("Intento no autorizado de ajustar saldo por Usuario ID {userId}", GetCurrentUserId() ?? 0);
|
||||||
return Forbid("No tiene permisos para realizar ajustes manuales de saldo.");
|
return Forbid("Solo SuperAdmin puede ajustar saldos manualmente.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
@@ -99,6 +98,10 @@ namespace GestionIntegral.Api.Controllers.Contables
|
|||||||
}
|
}
|
||||||
return Ok(saldoActualizado);
|
return Ok(saldoActualizado);
|
||||||
}
|
}
|
||||||
|
catch (BloqueoPorPeriodoCerradoException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error crítico al ajustar saldo manualmente.");
|
_logger.LogError(ex, "Error crítico al ajustar saldo manualmente.");
|
||||||
|
|||||||
@@ -40,19 +40,19 @@ namespace GestionIntegral.Api.Controllers.Distribucion
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType(typeof(IEnumerable<DistribuidorDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(IEnumerable<DistribuidorDto>), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
public async Task<IActionResult> GetAllDistribuidores([FromQuery] string? nombre, [FromQuery] string? nroDoc)
|
public async Task<IActionResult> GetAllDistribuidores([FromQuery] string? nombre, [FromQuery] string? nroDoc, [FromQuery] bool? soloActivos = true)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVer)) return Forbid();
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
var distribuidores = await _distribuidorService.ObtenerTodosAsync(nombre, nroDoc);
|
var distribuidores = await _distribuidorService.ObtenerTodosAsync(nombre, nroDoc, soloActivos);
|
||||||
return Ok(distribuidores);
|
return Ok(distribuidores);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("dropdown")]
|
[HttpGet("dropdown")]
|
||||||
[ProducesResponseType(typeof(IEnumerable<DistribuidorDropdownDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(IEnumerable<DistribuidorDropdownDto>), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
public async Task<IActionResult> GetAllDropdownDistribuidores()
|
public async Task<IActionResult> GetAllDropdownDistribuidores([FromQuery] bool? soloActivos = true)
|
||||||
{
|
{
|
||||||
var distribuidores = await _distribuidorService.GetAllDropdownAsync();
|
var distribuidores = await _distribuidorService.GetAllDropdownAsync(soloActivos);
|
||||||
return Ok(distribuidores);
|
return Ok(distribuidores);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +117,27 @@ namespace GestionIntegral.Api.Controllers.Distribucion
|
|||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id:int}/toggle-baja")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> ToggleBajaDistribuidor(int id, [FromBody] ToggleBajaDistribuidorDto dto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoModificar)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _distribuidorService.ToggleBajaAsync(id, dto.DarDeBaja, dto.FechaBaja, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Distribuidor no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
[HttpDelete("{id:int}")]
|
[HttpDelete("{id:int}")]
|
||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using GestionIntegral.Api.Dtos.Impresion;
|
|||||||
using GestionIntegral.Api.Services.Impresion;
|
using GestionIntegral.Api.Services.Impresion;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -40,6 +41,7 @@ namespace GestionIntegral.Api.Controllers.Impresion
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: api/stockbobinas
|
||||||
// GET: api/stockbobinas
|
// GET: api/stockbobinas
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType(typeof(IEnumerable<StockBobinaDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(IEnumerable<StockBobinaDto>), StatusCodes.Status200OK)]
|
||||||
@@ -47,12 +49,23 @@ namespace GestionIntegral.Api.Controllers.Impresion
|
|||||||
public async Task<IActionResult> GetAllStockBobinas(
|
public async Task<IActionResult> GetAllStockBobinas(
|
||||||
[FromQuery] int? idTipoBobina, [FromQuery] string? nroBobina, [FromQuery] int? idPlanta,
|
[FromQuery] int? idTipoBobina, [FromQuery] string? nroBobina, [FromQuery] int? idPlanta,
|
||||||
[FromQuery] int? idEstadoBobina, [FromQuery] string? remito,
|
[FromQuery] int? idEstadoBobina, [FromQuery] string? remito,
|
||||||
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta)
|
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
|
||||||
|
[FromQuery] DateTime? fechaEstadoDesde, [FromQuery] DateTime? fechaEstadoHasta) // <--- Nuevos parámetros agregados
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerStock)) return Forbid();
|
if (!TienePermiso(PermisoVerStock)) return Forbid();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var bobinas = await _stockBobinaService.ObtenerTodosAsync(idTipoBobina, nroBobina, idPlanta, idEstadoBobina, remito, fechaDesde, fechaHasta);
|
var bobinas = await _stockBobinaService.ObtenerTodosAsync(
|
||||||
|
idTipoBobina,
|
||||||
|
nroBobina,
|
||||||
|
idPlanta,
|
||||||
|
idEstadoBobina,
|
||||||
|
remito,
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta,
|
||||||
|
fechaEstadoDesde,
|
||||||
|
fechaEstadoHasta
|
||||||
|
);
|
||||||
return Ok(bobinas);
|
return Ok(bobinas);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -127,7 +140,7 @@ namespace GestionIntegral.Api.Controllers.Impresion
|
|||||||
if (!ModelState.IsValid) return BadRequest(ModelState); // Validaciones de DTO (Required, Range, etc.)
|
if (!ModelState.IsValid) return BadRequest(ModelState); // Validaciones de DTO (Required, Range, etc.)
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentUserId();
|
||||||
if (userId == null) return Unauthorized();
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
// La validación de que IdPublicacion/IdSeccion son requeridos para estado "En Uso"
|
// La validación de que IdPublicacion/IdSeccion son requeridos para estado "En Uso"
|
||||||
// ahora está más robusta en el servicio. Se puede quitar del controlador
|
// ahora está más robusta en el servicio. Se puede quitar del controlador
|
||||||
// si se prefiere que el servicio sea la única fuente de verdad para esa lógica.
|
// si se prefiere que el servicio sea la única fuente de verdad para esa lógica.
|
||||||
@@ -172,5 +185,72 @@ namespace GestionIntegral.Api.Controllers.Impresion
|
|||||||
}
|
}
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: api/stockbobinas/verificar-remito
|
||||||
|
[HttpGet("verificar-remito")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<StockBobinaDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
// CAMBIO: Hacer fechaRemito opcional (nullable)
|
||||||
|
public async Task<IActionResult> VerificarRemito([FromQuery, BindRequired] int idPlanta, [FromQuery, BindRequired] string remito, [FromQuery] DateTime? fechaRemito)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoIngresarBobina)) return Forbid();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Pasamos el parámetro nullable al servicio
|
||||||
|
var bobinasExistentes = await _stockBobinaService.VerificarRemitoExistenteAsync(idPlanta, remito, fechaRemito);
|
||||||
|
return Ok(bobinasExistentes);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al verificar remito {Remito} para planta {IdPlanta}", remito, idPlanta);
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al verificar el remito.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("actualizar-fecha-remito")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> ActualizarFechaRemitoLote([FromBody] UpdateFechaRemitoLoteDto dto)
|
||||||
|
{
|
||||||
|
// Reutilizamos el permiso de modificar datos, ya que es una corrección.
|
||||||
|
if (!TienePermiso(PermisoModificarDatos)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _stockBobinaService.ActualizarFechaRemitoLoteAsync(dto, userId.Value);
|
||||||
|
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/stockbobinas/lote
|
||||||
|
[HttpPost("lote")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> IngresarLoteBobinas([FromBody] CreateStockBobinaLoteDto loteDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoIngresarBobina)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _stockBobinaService.IngresarBobinaLoteAsync(loteDto, userId.Value);
|
||||||
|
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoContent(); // 204 es una buena respuesta para un lote procesado exitosamente sin devolver contenido.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
|
|
||||||
void ComposeContent(IContainer container)
|
void ComposeContent(IContainer container)
|
||||||
{
|
{
|
||||||
container.PaddingTop(1, Unit.Centimetre).Column(column =>
|
// << CAMBIO: Reducido el padding superior de 1cm a 5mm >>
|
||||||
|
container.PaddingTop(5, Unit.Millimetre).Column(column =>
|
||||||
{
|
{
|
||||||
column.Spacing(15);
|
// << CAMBIO: Reducido el espaciado principal entre elementos de 15 a 10 puntos >>
|
||||||
|
column.Spacing(10);
|
||||||
|
|
||||||
column.Item().Row(row =>
|
column.Item().Row(row =>
|
||||||
{
|
{
|
||||||
@@ -59,23 +61,24 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
column.Item().PaddingTop(5).Border(1).Background(Colors.Grey.Lighten3).AlignCenter().Padding(2).Text(Model.NombreEmpresa).SemiBold();
|
column.Item().PaddingTop(3).Border(1).Background(Colors.Grey.Lighten3).AlignCenter().Padding(2).Text(Model.NombreEmpresa).SemiBold();
|
||||||
|
|
||||||
column.Item().Border(1).Padding(10).Column(innerCol =>
|
column.Item().Border(1).Padding(8).Column(innerCol => // << CAMBIO: Padding reducido de 10 a 8 >>
|
||||||
{
|
{
|
||||||
innerCol.Spacing(5);
|
// << CAMBIO: Reducido el espaciado interno de 5 a 4 >>
|
||||||
|
innerCol.Spacing(4);
|
||||||
|
|
||||||
// Fila de "Ingresados por Remito" con borde inferior sólido.
|
|
||||||
innerCol.Item().BorderBottom(1, Unit.Point).BorderColor(Colors.Grey.Medium).Row(row =>
|
innerCol.Item().BorderBottom(1, Unit.Point).BorderColor(Colors.Grey.Medium).Row(row =>
|
||||||
{
|
{
|
||||||
row.RelativeItem().Text("Ingresados por Remito:").SemiBold();
|
row.RelativeItem().Text("Ingresados por Remito:").SemiBold();
|
||||||
row.RelativeItem().AlignRight().Text(Model.TotalIngresadosPorRemito.ToString("N0"));
|
row.RelativeItem().AlignRight().Text(Model.TotalIngresadosPorRemito.ToString("N0"));
|
||||||
}); // <-- SOLUCIÓN: Borde sólido simple.
|
});
|
||||||
|
|
||||||
foreach (var item in Model.Detalles)
|
foreach (var item in Model.Detalles)
|
||||||
{
|
{
|
||||||
var totalSeccion = item.Devueltos - item.Llevados;
|
var totalSeccion = item.Devueltos - item.Llevados;
|
||||||
innerCol.Item().PaddingTop(5).Row(row =>
|
// << CAMBIO: Reducido el padding superior de 5 a 3 >>
|
||||||
|
innerCol.Item().PaddingTop(3).Row(row =>
|
||||||
{
|
{
|
||||||
row.ConstantItem(100).Text(item.Tipo).SemiBold();
|
row.ConstantItem(100).Text(item.Tipo).SemiBold();
|
||||||
|
|
||||||
@@ -90,7 +93,8 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
r.RelativeItem().Text("Devueltos");
|
r.RelativeItem().Text("Devueltos");
|
||||||
r.RelativeItem().AlignRight().Text($"{item.Devueltos:N0}");
|
r.RelativeItem().AlignRight().Text($"{item.Devueltos:N0}");
|
||||||
});
|
});
|
||||||
sub.Item().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(2).Row(r => {
|
// << CAMBIO: Reducido el padding superior de 2 a 1 >>
|
||||||
|
sub.Item().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(1).Row(r => {
|
||||||
r.RelativeItem().Text(t => t.Span("Total").SemiBold());
|
r.RelativeItem().Text(t => t.Span("Total").SemiBold());
|
||||||
r.RelativeItem().AlignRight().Text(t => t.Span(totalSeccion.ToString("N0")).SemiBold());
|
r.RelativeItem().AlignRight().Text(t => t.Span(totalSeccion.ToString("N0")).SemiBold());
|
||||||
});
|
});
|
||||||
@@ -99,7 +103,8 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
column.Item().PaddingTop(10).Column(finalCol =>
|
// << CAMBIO: Reducido el padding superior de 10 a 8 >>
|
||||||
|
column.Item().PaddingTop(8).Column(finalCol =>
|
||||||
{
|
{
|
||||||
finalCol.Spacing(2);
|
finalCol.Spacing(2);
|
||||||
|
|
||||||
@@ -112,13 +117,15 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
if (isBold) valueText.SemiBold();
|
if (isBold) valueText.SemiBold();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Usamos bordes superiores para separar las líneas de total
|
// << CAMBIO: Reducido el padding superior de 2 a 1 en las siguientes líneas >>
|
||||||
finalCol.Item().BorderTop(2f).BorderColor(Colors.Black).PaddingTop(2).Row(row => AddTotalRow(row, "Total Devolución a la Fecha", Model.TotalDevolucionALaFecha.ToString("N0"), false));
|
finalCol.Item().BorderTop(2f).BorderColor(Colors.Black).PaddingTop(1).Row(row => AddTotalRow(row, "Total Devolución a la Fecha", Model.TotalDevolucionALaFecha.ToString("N0"), false));
|
||||||
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(2).Row(row => AddTotalRow(row, "Total Devolución Días Anteriores", Model.TotalDevolucionDiasAnteriores.ToString("N0"), false));
|
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(1).Row(row => AddTotalRow(row, "Total Devolución Días Anteriores", Model.TotalDevolucionDiasAnteriores.ToString("N0"), false));
|
||||||
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(2).Row(row => AddTotalRow(row, "Total Devolución", Model.TotalDevolucionGeneral.ToString("N0"), false));
|
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(1).Row(row => AddTotalRow(row, "Total Devolución", Model.TotalDevolucionGeneral.ToString("N0"), false));
|
||||||
finalCol.Item().BorderTop(2f).BorderColor(Colors.Black).PaddingTop(5).Row(row => AddTotalRow(row, "Sin Cargo", Model.TotalSinCargo.ToString("N0"), false));
|
// << CAMBIO: Reducido el padding superior de 5 a 3 >>
|
||||||
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(2).Row(row => AddTotalRow(row, "Sobrantes", $"-{Model.TotalSobrantes.ToString("N0")}", false));
|
finalCol.Item().BorderTop(2f).BorderColor(Colors.Black).PaddingTop(3).Row(row => AddTotalRow(row, "Sin Cargo", Model.TotalSinCargo.ToString("N0"), false));
|
||||||
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(5).Row(row => AddTotalRow(row, "Diferencia", Model.DiferenciaFinal.ToString("N0"), true));
|
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(1).Row(row => AddTotalRow(row, "Sobrantes", $"-{Model.TotalSobrantes.ToString("N0")}", false));
|
||||||
|
// << CAMBIO: Reducido el padding superior de 5 a 3 >>
|
||||||
|
finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(3).Row(row => AddTotalRow(row, "Diferencia", Model.DiferenciaFinal.ToString("N0"), true));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
if (Model.DebitosCreditos.Any()) column.Item().Element(ComposeDebCredTable);
|
if (Model.DebitosCreditos.Any()) column.Item().Element(ComposeDebCredTable);
|
||||||
|
|
||||||
column.Item().Element(ComposeResumenPeriodo);
|
column.Item().Element(ComposeResumenPeriodo);
|
||||||
column.Item().Element(ComposeSaldoFinal);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +106,11 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Saldo");
|
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Saldo");
|
||||||
});
|
});
|
||||||
|
|
||||||
decimal saldoAcumulado = 0; // Inicia en CERO
|
// Fila Saldo Inicial al inicio de la primera tabla — ancla del acumulado
|
||||||
|
table.Cell().ColumnSpan(6).Border(1).Padding(2).Text(t => t.Span("Saldo Inicial").SemiBold());
|
||||||
|
table.Cell().Border(1).Padding(2).AlignRight().Text(t => t.Span(Model.SaldoInicial.ToString("C", CultureAr)).SemiBold());
|
||||||
|
|
||||||
|
decimal saldoAcumulado = Model.SaldoInicial;
|
||||||
foreach (var item in Model.Movimientos.OrderBy(m => m.Fecha))
|
foreach (var item in Model.Movimientos.OrderBy(m => m.Fecha))
|
||||||
{
|
{
|
||||||
saldoAcumulado += (item.Debe - item.Haber);
|
saldoAcumulado += (item.Debe - item.Haber);
|
||||||
@@ -157,7 +160,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Detalle");
|
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Detalle");
|
||||||
});
|
});
|
||||||
|
|
||||||
decimal saldoAcumulado = Model.TotalMovimientos;
|
decimal saldoAcumulado = Model.SaldoInicial + Model.TotalMovimientos;
|
||||||
foreach (var item in Model.Pagos.OrderBy(p => p.Fecha).ThenBy(p => p.Recibo))
|
foreach (var item in Model.Pagos.OrderBy(p => p.Fecha).ThenBy(p => p.Recibo))
|
||||||
{
|
{
|
||||||
saldoAcumulado += (item.Debe - item.Haber);
|
saldoAcumulado += (item.Debe - item.Haber);
|
||||||
@@ -204,7 +207,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Saldo");
|
header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Saldo");
|
||||||
});
|
});
|
||||||
|
|
||||||
decimal saldoAcumulado = Model.TotalMovimientos + Model.TotalPagos;
|
decimal saldoAcumulado = Model.SaldoInicial + Model.TotalMovimientos + Model.TotalPagos;
|
||||||
foreach (var item in Model.DebitosCreditos.OrderBy(dc => dc.Fecha))
|
foreach (var item in Model.DebitosCreditos.OrderBy(dc => dc.Fecha))
|
||||||
{
|
{
|
||||||
saldoAcumulado += (item.Debe - item.Haber);
|
saldoAcumulado += (item.Debe - item.Haber);
|
||||||
@@ -236,25 +239,17 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
row.RelativeItem().Text(label);
|
row.RelativeItem().Text(label);
|
||||||
row.ConstantItem(120).AlignRight().Text(value.ToString("C", CultureAr));
|
row.ConstantItem(120).AlignRight().Text(value.ToString("C", CultureAr));
|
||||||
};
|
};
|
||||||
|
col.Item().Row(row => AddResumenRow(row, "Saldo Inicial", Model.SaldoInicial));
|
||||||
col.Item().Row(row => AddResumenRow(row, "Movimientos", Model.TotalMovimientos));
|
col.Item().Row(row => AddResumenRow(row, "Movimientos", Model.TotalMovimientos));
|
||||||
col.Item().Row(row => AddResumenRow(row, "Débitos/Créditos", Model.TotalDebitosCreditos));
|
col.Item().Row(row => AddResumenRow(row, "Débitos/Créditos", Model.TotalDebitosCreditos));
|
||||||
col.Item().Row(row => AddResumenRow(row, "Pagos", Model.TotalPagos));
|
col.Item().Row(row => AddResumenRow(row, "Pagos", Model.TotalPagos));
|
||||||
col.Item().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(5).Row(row =>
|
col.Item().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(5).Row(row =>
|
||||||
{
|
{
|
||||||
row.RelativeItem().Text("Total").SemiBold();
|
row.RelativeItem().Text("Saldo Final").SemiBold();
|
||||||
row.ConstantItem(120).AlignRight().Text(t => t.Span(Model.TotalPeriodo.ToString("C", CultureAr)).SemiBold());
|
row.ConstantItem(120).AlignRight().Text(t => t.Span(Model.SaldoFinal.ToString("C", CultureAr)).SemiBold());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeSaldoFinal(IContainer container)
|
|
||||||
{
|
|
||||||
container.PaddingTop(5, Unit.Millimetre).AlignLeft().Text(text =>
|
|
||||||
{
|
|
||||||
text.Span($"Saldo Total del Distribuidor al {Model.FechaReporte} ").SemiBold().FontSize(12);
|
|
||||||
text.Span(Model.SaldoDeCuenta.ToString("C", CultureAr)).SemiBold().FontSize(12);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
// --- REEMPLAZAR ARCHIVO: Controllers/Reportes/PdfTemplates/DistribucionCanillasDocument.cs ---
|
|
||||||
using GestionIntegral.Api.Dtos.Reportes;
|
using GestionIntegral.Api.Dtos.Reportes;
|
||||||
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
|
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
|
||||||
using QuestPDF.Fluent;
|
using QuestPDF.Fluent;
|
||||||
using QuestPDF.Helpers;
|
using QuestPDF.Helpers;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
||||||
|
{
|
||||||
|
public class DistribucionSuscripcionesDocument : IDocument
|
||||||
|
{
|
||||||
|
public DistribucionSuscripcionesViewModel Model { get; }
|
||||||
|
|
||||||
|
public DistribucionSuscripcionesDocument(DistribucionSuscripcionesViewModel model)
|
||||||
|
{
|
||||||
|
Model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
|
||||||
|
|
||||||
|
public void Compose(IDocumentContainer container)
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Margin(1, Unit.Centimetre);
|
||||||
|
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
|
||||||
|
page.Header().Element(ComposeHeader);
|
||||||
|
page.Content().Element(ComposeContent);
|
||||||
|
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeHeader(IContainer container)
|
||||||
|
{
|
||||||
|
container.Column(column =>
|
||||||
|
{
|
||||||
|
column.Item().Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().Text("Reporte de Distribución de Suscripciones").SemiBold().FontSize(14);
|
||||||
|
col.Item().Text($"Período: {Model.FechaDesde} al {Model.FechaHasta}").FontSize(11);
|
||||||
|
});
|
||||||
|
row.ConstantItem(150).AlignRight().Text($"Generado: {Model.FechaGeneracion}");
|
||||||
|
});
|
||||||
|
column.Item().PaddingTop(5).BorderBottom(1).BorderColor(Colors.Grey.Lighten2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeContent(IContainer container)
|
||||||
|
{
|
||||||
|
container.PaddingTop(10).Column(column =>
|
||||||
|
{
|
||||||
|
column.Spacing(20); // Espacio entre elementos principales (sección de altas y sección de bajas)
|
||||||
|
|
||||||
|
// --- Sección 1: Altas y Activas ---
|
||||||
|
column.Item().Column(colAltas =>
|
||||||
|
{
|
||||||
|
colAltas.Item().Text("Altas y Suscripciones Activas en el Período").Bold().FontSize(14).Underline();
|
||||||
|
colAltas.Item().PaddingBottom(10).Text("Listado de suscriptores que deben recibir entregas en el período seleccionado.");
|
||||||
|
|
||||||
|
if (!Model.DatosAgrupadosAltas.Any())
|
||||||
|
{
|
||||||
|
colAltas.Item().PaddingTop(10).Text("No se encontraron suscripciones activas para este período.").Italic();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var empresa in Model.DatosAgrupadosAltas)
|
||||||
|
{
|
||||||
|
colAltas.Item().Element(c => ComposeTablaEmpresa(c, empresa, esBaja: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Sección 2: Bajas ---
|
||||||
|
if (Model.DatosAgrupadosBajas.Any())
|
||||||
|
{
|
||||||
|
column.Item().PageBreak(); // Salto de página para separar las secciones
|
||||||
|
column.Item().Column(colBajas =>
|
||||||
|
{
|
||||||
|
colBajas.Item().Text("Bajas de Suscripciones en el Período").Bold().FontSize(14).Underline().FontColor(Colors.Red.Medium);
|
||||||
|
colBajas.Item().PaddingBottom(10).Text("Listado de suscriptores cuya suscripción finalizó. NO se les debe entregar a partir de su 'Fecha de Baja'.");
|
||||||
|
|
||||||
|
foreach (var empresa in Model.DatosAgrupadosBajas)
|
||||||
|
{
|
||||||
|
colBajas.Item().Element(c => ComposeTablaEmpresa(c, empresa, esBaja: true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeTablaEmpresa(IContainer container, GrupoEmpresa empresa, bool esBaja)
|
||||||
|
{
|
||||||
|
container.Column(column =>
|
||||||
|
{
|
||||||
|
// Cabecera de la EMPRESA (ej. EL DIA)
|
||||||
|
column.Item().Background(Colors.Grey.Lighten2).Padding(5).Text(empresa.NombreEmpresa).Bold().FontSize(12);
|
||||||
|
|
||||||
|
// Contenedor para las tablas de las publicaciones de esta empresa
|
||||||
|
column.Item().PaddingTop(5).Column(colPub =>
|
||||||
|
{
|
||||||
|
colPub.Spacing(10); // Espacio entre cada tabla de publicación
|
||||||
|
foreach (var publicacion in empresa.Publicaciones)
|
||||||
|
{
|
||||||
|
colPub.Item().Element(c => ComposeTablaPublicacion(c, publicacion, esBaja));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeTablaPublicacion(IContainer container, GrupoPublicacion publicacion, bool esBaja)
|
||||||
|
{
|
||||||
|
// Se envuelve la tabla en una columna para poder ponerle un título simple arriba.
|
||||||
|
container.Column(column =>
|
||||||
|
{
|
||||||
|
column.Item().PaddingLeft(2).PaddingBottom(2).Text(publicacion.NombrePublicacion).SemiBold().FontSize(10);
|
||||||
|
column.Item().Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn(2.5f); // Nombre
|
||||||
|
columns.RelativeColumn(3); // Dirección
|
||||||
|
columns.RelativeColumn(1.5f); // Teléfono
|
||||||
|
columns.ConstantColumn(65); // Fecha Inicio / Baja
|
||||||
|
columns.RelativeColumn(1.5f); // Días
|
||||||
|
columns.RelativeColumn(2.5f); // Observaciones
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Header(header =>
|
||||||
|
{
|
||||||
|
header.Cell().BorderBottom(1).Padding(2).Text("Suscriptor").SemiBold();
|
||||||
|
header.Cell().BorderBottom(1).Padding(2).Text("Dirección").SemiBold();
|
||||||
|
header.Cell().BorderBottom(1).Padding(2).Text("Teléfono").SemiBold();
|
||||||
|
header.Cell().BorderBottom(1).Padding(2).Text(esBaja ? "Fecha de Baja" : "Fecha Inicio").SemiBold();
|
||||||
|
header.Cell().BorderBottom(1).Padding(2).Text("Días Entrega").SemiBold();
|
||||||
|
header.Cell().BorderBottom(1).Padding(2).Text("Observaciones").SemiBold();
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var item in publicacion.Suscripciones)
|
||||||
|
{
|
||||||
|
table.Cell().Padding(2).Text(item.NombreSuscriptor);
|
||||||
|
table.Cell().Padding(2).Text(item.Direccion);
|
||||||
|
table.Cell().Padding(2).Text(item.Telefono ?? "-");
|
||||||
|
var fecha = esBaja ? item.FechaFin : item.FechaInicio;
|
||||||
|
table.Cell().Padding(2).Text(fecha?.ToString("dd/MM/yyyy") ?? "-");
|
||||||
|
table.Cell().Padding(2).Text(item.DiasEntrega);
|
||||||
|
table.Cell().Padding(2).Text(item.Observaciones ?? "-");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Reportes.ViewModels;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
||||||
|
{
|
||||||
|
public class FacturasPublicidadDocument : IDocument
|
||||||
|
{
|
||||||
|
public FacturasPublicidadViewModel Model { get; }
|
||||||
|
|
||||||
|
public FacturasPublicidadDocument(FacturasPublicidadViewModel model)
|
||||||
|
{
|
||||||
|
Model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
|
||||||
|
|
||||||
|
public void Compose(IDocumentContainer container)
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Margin(1, Unit.Centimetre);
|
||||||
|
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
|
||||||
|
|
||||||
|
page.Header().Element(ComposeHeader);
|
||||||
|
page.Content().Element(ComposeContent);
|
||||||
|
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeHeader(IContainer container)
|
||||||
|
{
|
||||||
|
// Se envuelve todo el contenido del header en una única Columna.
|
||||||
|
container.Column(column =>
|
||||||
|
{
|
||||||
|
// El primer item de la columna es la fila con los títulos.
|
||||||
|
column.Item().Row(row =>
|
||||||
|
{
|
||||||
|
row.RelativeItem().Column(col =>
|
||||||
|
{
|
||||||
|
col.Item().Text($"Reporte de Suscripciones a Facturar").SemiBold().FontSize(14);
|
||||||
|
col.Item().Text($"Período: {Model.Periodo}").FontSize(11);
|
||||||
|
});
|
||||||
|
|
||||||
|
row.ConstantItem(150).AlignRight().Column(col => {
|
||||||
|
col.Item().AlignRight().Text($"Fecha de Generación:");
|
||||||
|
col.Item().AlignRight().Text(Model.FechaGeneracion);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// El segundo item de la columna es el separador.
|
||||||
|
column.Item().PaddingTop(5).BorderBottom(1).BorderColor(Colors.Grey.Lighten2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeContent(IContainer container)
|
||||||
|
{
|
||||||
|
container.PaddingTop(10).Column(column =>
|
||||||
|
{
|
||||||
|
column.Spacing(20);
|
||||||
|
|
||||||
|
foreach (var empresaData in Model.DatosPorEmpresa)
|
||||||
|
{
|
||||||
|
column.Item().Element(c => ComposeTablaPorEmpresa(c, empresaData));
|
||||||
|
}
|
||||||
|
|
||||||
|
column.Item().AlignRight().PaddingTop(15).Text($"Total General a Facturar: {Model.TotalGeneral.ToString("C", new CultureInfo("es-AR"))}").Bold().FontSize(12);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ComposeTablaPorEmpresa(IContainer container, DatosEmpresaViewModel empresaData)
|
||||||
|
{
|
||||||
|
container.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn(3); // Nombre Suscriptor
|
||||||
|
columns.ConstantColumn(100); // Documento
|
||||||
|
columns.ConstantColumn(100, Unit.Point); // Importe
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Header(header =>
|
||||||
|
{
|
||||||
|
header.Cell().ColumnSpan(3).Background(Colors.Grey.Lighten2)
|
||||||
|
.Padding(5).Text(empresaData.NombreEmpresa).Bold().FontSize(12);
|
||||||
|
|
||||||
|
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten3).Padding(2).Text("Suscriptor").SemiBold();
|
||||||
|
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten3).Padding(2).Text("Documento").SemiBold();
|
||||||
|
header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Importe a Facturar").SemiBold();
|
||||||
|
});
|
||||||
|
|
||||||
|
var facturasPorSuscriptor = empresaData.Facturas.GroupBy(f => f.NombreSuscriptor);
|
||||||
|
|
||||||
|
foreach (var grupoSuscriptor in facturasPorSuscriptor.OrderBy(g => g.Key))
|
||||||
|
{
|
||||||
|
foreach(var item in grupoSuscriptor)
|
||||||
|
{
|
||||||
|
table.Cell().Padding(2).Text(item.NombreSuscriptor);
|
||||||
|
table.Cell().Padding(2).Text($"{item.TipoDocumento} {item.NroDocumento}");
|
||||||
|
table.Cell().Padding(2).AlignRight().Text(item.ImporteFinal.ToString("C", new CultureInfo("es-AR")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(grupoSuscriptor.Count() > 1)
|
||||||
|
{
|
||||||
|
var subtotal = grupoSuscriptor.Sum(i => i.ImporteFinal);
|
||||||
|
table.Cell().ColumnSpan(2).AlignRight().Padding(2).Text($"Subtotal {grupoSuscriptor.Key}:").Italic();
|
||||||
|
table.Cell().AlignRight().Padding(2).Text(subtotal.ToString("C", new CultureInfo("es-AR"))).Italic().SemiBold();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Cell().ColumnSpan(2).BorderTop(1).BorderColor(Colors.Grey.Darken1).AlignRight()
|
||||||
|
.PaddingTop(5).Text("Total Empresa:").Bold();
|
||||||
|
table.Cell().BorderTop(1).BorderColor(Colors.Grey.Darken1).AlignRight()
|
||||||
|
.PaddingTop(5).Text(empresaData.TotalEmpresa.ToString("C", new CultureInfo("es-AR"))).Bold();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,23 +18,32 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
|
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
|
||||||
|
|
||||||
// CORRECCIÓN: El método GetSettings ya no es necesario para este diseño.
|
|
||||||
// La configuración por defecto es suficiente.
|
|
||||||
// public DocumentSettings GetSettings() => DocumentSettings.Default;
|
|
||||||
|
|
||||||
public void Compose(IDocumentContainer container)
|
public void Compose(IDocumentContainer container)
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(5, Unit.Millimetre);
|
||||||
|
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
|
||||||
|
|
||||||
|
page.Content().Column(mainColumn =>
|
||||||
{
|
{
|
||||||
container.Page(page =>
|
mainColumn.Item()
|
||||||
{
|
.AlignCenter()
|
||||||
page.Size(PageSizes.A5);
|
.Width(PageSizes.A6.Width)
|
||||||
page.Margin(1, Unit.Centimetre);
|
.Height(PageSizes.A6.Height)
|
||||||
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9));
|
.Column(a6ContentColumn =>
|
||||||
|
{
|
||||||
page.Header().Element(ComposeHeader);
|
a6ContentColumn.Item().PaddingRight(10, Unit.Millimetre).PaddingLeft(10, Unit.Millimetre).Column(content =>
|
||||||
page.Content().Element(ComposeContent);
|
{
|
||||||
});
|
content.Item().Element(ComposeHeader);
|
||||||
}
|
content.Item().Element(ComposeContent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void ComposeHeader(IContainer container)
|
void ComposeHeader(IContainer container)
|
||||||
{
|
{
|
||||||
@@ -50,13 +59,13 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
column.Item().PaddingTop(10);
|
column.Item().PaddingTop(10);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeContent(IContainer container)
|
void ComposeContent(IContainer container)
|
||||||
{
|
{
|
||||||
container.Column(column =>
|
container.Column(column =>
|
||||||
{
|
{
|
||||||
column.Spacing(15);
|
column.Spacing(15);
|
||||||
|
|
||||||
column.Item().Table(table =>
|
column.Item().Table(table =>
|
||||||
{
|
{
|
||||||
table.ColumnsDefinition(columns =>
|
table.ColumnsDefinition(columns =>
|
||||||
@@ -71,15 +80,15 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
var vendidos = item.TotalCantSalida - item.TotalCantEntrada;
|
var vendidos = item.TotalCantSalida - item.TotalCantEntrada;
|
||||||
|
|
||||||
table.Cell().ColumnSpan(3).Text(item.Publicacion).SemiBold();
|
table.Cell().ColumnSpan(3).Text(item.Publicacion).SemiBold();
|
||||||
|
|
||||||
table.Cell();
|
table.Cell();
|
||||||
table.Cell().Text("Retirados");
|
table.Cell().Text("Retirados");
|
||||||
table.Cell().AlignRight().Text(item.TotalCantSalida.ToString("N0"));
|
table.Cell().AlignRight().Text(item.TotalCantSalida.ToString("N0"));
|
||||||
|
|
||||||
table.Cell();
|
table.Cell();
|
||||||
table.Cell().Text("Devueltos");
|
table.Cell().Text("Devueltos");
|
||||||
table.Cell().AlignRight().Text(item.TotalCantEntrada.ToString("N0"));
|
table.Cell().AlignRight().Text(item.TotalCantEntrada.ToString("N0"));
|
||||||
|
|
||||||
table.Cell();
|
table.Cell();
|
||||||
table.Cell().Text("Vendidos");
|
table.Cell().Text("Vendidos");
|
||||||
table.Cell().AlignRight().Text(vendidos.ToString("N0"));
|
table.Cell().AlignRight().Text(vendidos.ToString("N0"));
|
||||||
@@ -87,7 +96,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
table.Cell();
|
table.Cell();
|
||||||
table.Cell().Text("Precio Unitario");
|
table.Cell().Text("Precio Unitario");
|
||||||
table.Cell().AlignRight().Text(item.PrecioEjemplar.ToString("C", CultureAr));
|
table.Cell().AlignRight().Text(item.PrecioEjemplar.ToString("C", CultureAr));
|
||||||
|
|
||||||
table.Cell();
|
table.Cell();
|
||||||
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(2).Text(t => t.Span("Importe Vendido").SemiBold());
|
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(2).Text(t => t.Span("Importe Vendido").SemiBold());
|
||||||
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(2).AlignRight().Text(t => t.Span(item.TotalRendir.ToString("C", CultureAr)).SemiBold());
|
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(2).AlignRight().Text(t => t.Span(item.TotalRendir.ToString("C", CultureAr)).SemiBold());
|
||||||
@@ -99,20 +108,20 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
row.RelativeItem().Text("Total A Rendir").SemiBold().FontSize(10);
|
row.RelativeItem().Text("Total A Rendir").SemiBold().FontSize(10);
|
||||||
row.RelativeItem().AlignRight().Text(text => text.Span(Model.TotalARendir.ToString("C", CultureAr)).SemiBold().FontSize(10));
|
row.RelativeItem().AlignRight().Text(text => text.Span(Model.TotalARendir.ToString("C", CultureAr)).SemiBold().FontSize(10));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!Model.EsAccionista && Model.Ganancias.Any())
|
if (!Model.EsAccionista && Model.Ganancias.Any())
|
||||||
{
|
{
|
||||||
column.Item().PaddingTop(10).Element(ComposeGananciasTable);
|
column.Item().PaddingTop(10).Element(ComposeGananciasTable);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeGananciasTable(IContainer container)
|
void ComposeGananciasTable(IContainer container)
|
||||||
{
|
{
|
||||||
container.Column(column =>
|
container.Column(column =>
|
||||||
{
|
{
|
||||||
column.Item().Text("Comisiones Acreditadas").SemiBold().FontSize(11);
|
column.Item().Text("Comisiones Acreditadas").SemiBold().FontSize(11);
|
||||||
|
|
||||||
column.Item().PaddingTop(5).Table(table =>
|
column.Item().PaddingTop(5).Table(table =>
|
||||||
{
|
{
|
||||||
table.ColumnsDefinition(columns =>
|
table.ColumnsDefinition(columns =>
|
||||||
@@ -126,7 +135,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
table.Cell().Border(1).BorderColor(Colors.Grey.Lighten2).Padding(3).Text(item.Publicacion);
|
table.Cell().Border(1).BorderColor(Colors.Grey.Lighten2).Padding(3).Text(item.Publicacion);
|
||||||
table.Cell().Border(1).BorderColor(Colors.Grey.Lighten2).Padding(3).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr));
|
table.Cell().Border(1).BorderColor(Colors.Grey.Lighten2).Padding(3).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr));
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).Text("Total Comisiones").SemiBold();
|
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).Text("Total Comisiones").SemiBold();
|
||||||
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).AlignRight().Text(t => t.Span(Model.TotalComisiones.ToString("C", CultureAr)).SemiBold());
|
table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).AlignRight().Text(t => t.Span(Model.TotalComisiones.ToString("C", CultureAr)).SemiBold());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
|
|
||||||
foreach (var item in Model.PromediosPorDia.OrderBy(d => dayOrder.GetValueOrDefault(d.Dia, 99)))
|
foreach (var item in Model.PromediosPorDia.OrderBy(d => dayOrder.GetValueOrDefault(d.Dia, 99)))
|
||||||
{
|
{
|
||||||
var porcDevolucion = item.Llevados > 0 ? (decimal)item.Devueltos * 100 / item.Llevados : 0;
|
var porcDevolucion = item.Promedio_Llevados > 0 ? (decimal)item.Promedio_Devueltos * 100 / item.Promedio_Llevados : 0;
|
||||||
|
|
||||||
table.Cell().Border(1).Padding(3).Text(item.Dia);
|
table.Cell().Border(1).Padding(3).Text(item.Dia);
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Cant.ToString("N0"));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Cant.ToString("N0"));
|
||||||
@@ -162,7 +162,6 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
var general = Model.PromedioGeneral;
|
var general = Model.PromedioGeneral;
|
||||||
if (general != null)
|
if (general != null)
|
||||||
{
|
{
|
||||||
var porcDevolucionGeneral = general.Promedio_Llevados > 0 ? (decimal)general.Promedio_Devueltos * 100 / general.Promedio_Llevados : 0;
|
|
||||||
var boldStyle = TextStyle.Default.SemiBold();
|
var boldStyle = TextStyle.Default.SemiBold();
|
||||||
|
|
||||||
table.Cell().Border(1).Padding(3).Text(text => text.Span(general.Dia).Style(boldStyle));
|
table.Cell().Border(1).Padding(3).Text(text => text.Span(general.Dia).Style(boldStyle));
|
||||||
@@ -170,7 +169,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.Promedio_Llevados.ToString("N0")).Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.Promedio_Llevados.ToString("N0")).Style(boldStyle));
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.Promedio_Devueltos.ToString("N0")).Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.Promedio_Devueltos.ToString("N0")).Style(boldStyle));
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.Promedio_Ventas.ToString("N0")).Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.Promedio_Ventas.ToString("N0")).Style(boldStyle));
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(porcDevolucionGeneral.ToString("F2") + "%").Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.PorcentajeDevolucionGeneral.ToString("F2") + "%").Style(boldStyle));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
{
|
{
|
||||||
page.Margin(1, Unit.Centimetre);
|
page.Margin(1, Unit.Centimetre);
|
||||||
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
|
page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10));
|
||||||
|
|
||||||
page.Header().Element(ComposeHeader);
|
page.Header().Element(ComposeHeader);
|
||||||
page.Content().Element(ComposeContent);
|
page.Content().Element(ComposeContent);
|
||||||
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
|
page.Footer().AlignCenter().Text(x => { x.Span("Página "); x.CurrentPageNumber(); });
|
||||||
@@ -46,7 +46,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposeContent(IContainer container)
|
void ComposeContent(IContainer container)
|
||||||
{
|
{
|
||||||
container.PaddingTop(8, Unit.Millimetre).Column(column =>
|
container.PaddingTop(8, Unit.Millimetre).Column(column =>
|
||||||
@@ -54,7 +54,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
column.Spacing(15);
|
column.Spacing(15);
|
||||||
column.Item().Text("Distribución").SemiBold().FontSize(12);
|
column.Item().Text("Distribución").SemiBold().FontSize(12);
|
||||||
column.Item().Element(ComposeDetalleDiarioTable);
|
column.Item().Element(ComposeDetalleDiarioTable);
|
||||||
|
|
||||||
column.Item().PaddingTop(5, Unit.Millimetre);
|
column.Item().PaddingTop(5, Unit.Millimetre);
|
||||||
|
|
||||||
column.Item().Text("Promedios").SemiBold().FontSize(12);
|
column.Item().Text("Promedios").SemiBold().FontSize(12);
|
||||||
@@ -90,14 +90,14 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
var llevados = item.Llevados ?? 0;
|
var llevados = item.Llevados ?? 0;
|
||||||
var devueltos = item.Devueltos ?? 0;
|
var devueltos = item.Devueltos ?? 0;
|
||||||
var ventaNetaDia = llevados - devueltos;
|
var ventaNetaDia = llevados - devueltos;
|
||||||
if(llevados > 0)
|
if (llevados > 0)
|
||||||
{
|
{
|
||||||
ventaNetaAcumulada += ventaNetaDia;
|
ventaNetaAcumulada += ventaNetaDia;
|
||||||
conteoDias++;
|
conteoDias++;
|
||||||
}
|
}
|
||||||
var promedio = conteoDias > 0 ? ventaNetaAcumulada / conteoDias : 0;
|
var promedio = conteoDias > 0 ? ventaNetaAcumulada / conteoDias : 0;
|
||||||
var porcDevolucion = llevados > 0 ? (decimal)devueltos * 100 / llevados : 0;
|
var porcDevolucion = llevados > 0 ? (decimal)devueltos * 100 / llevados : 0;
|
||||||
|
|
||||||
table.Cell().Border(1).Padding(3).Text(item.Dia.ToString());
|
table.Cell().Border(1).Padding(3).Text(item.Dia.ToString());
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(llevados.ToString("N0"));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(llevados.ToString("N0"));
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(devueltos.ToString("N0"));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(devueltos.ToString("N0"));
|
||||||
@@ -105,12 +105,12 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(promedio.ToString("N0"));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(promedio.ToString("N0"));
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(porcDevolucion.ToString("F2") + "%");
|
table.Cell().Border(1).Padding(3).AlignRight().Text(porcDevolucion.ToString("F2") + "%");
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalLlevados = Model.DetalleDiario.Sum(i => i.Llevados ?? 0);
|
var totalLlevados = Model.DetalleDiario.Sum(i => i.Llevados ?? 0);
|
||||||
var totalDevueltos = Model.DetalleDiario.Sum(i => i.Devueltos ?? 0);
|
var totalDevueltos = Model.DetalleDiario.Sum(i => i.Devueltos ?? 0);
|
||||||
var totalVentaNeta = totalLlevados - totalDevueltos;
|
var totalVentaNeta = totalLlevados - totalDevueltos;
|
||||||
var totalPorcDevolucion = totalLlevados > 0 ? (decimal)totalDevueltos * 100 / totalLlevados : 0;
|
var totalPorcDevolucion = totalLlevados > 0 ? (decimal)totalDevueltos * 100 / totalLlevados : 0;
|
||||||
|
|
||||||
var boldStyle = TextStyle.Default.SemiBold();
|
var boldStyle = TextStyle.Default.SemiBold();
|
||||||
table.Cell().Border(1).Padding(3);
|
table.Cell().Border(1).Padding(3);
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalLlevados.ToString("N0")).Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalLlevados.ToString("N0")).Style(boldStyle));
|
||||||
@@ -120,11 +120,11 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalPorcDevolucion.ToString("F2") + "%").Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalPorcDevolucion.ToString("F2") + "%").Style(boldStyle));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComposePromediosTable(IContainer container)
|
void ComposePromediosTable(IContainer container)
|
||||||
{
|
{
|
||||||
var dayOrder = new Dictionary<string, int> { { "Lunes", 1 }, { "Martes", 2 }, { "Miércoles", 3 }, { "Jueves", 4 }, { "Viernes", 5 }, { "Sábado", 6 }, { "Domingo", 7 }};
|
var dayOrder = new Dictionary<string, int> { { "Lunes", 1 }, { "Martes", 2 }, { "Miércoles", 3 }, { "Jueves", 4 }, { "Viernes", 5 }, { "Sábado", 6 }, { "Domingo", 7 } };
|
||||||
|
|
||||||
container.Table(table =>
|
container.Table(table =>
|
||||||
{
|
{
|
||||||
table.ColumnsDefinition(columns =>
|
table.ColumnsDefinition(columns =>
|
||||||
@@ -147,7 +147,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
var llevados = item.Promedio_Llevados ?? 0;
|
var llevados = item.Promedio_Llevados ?? 0;
|
||||||
var devueltos = item.Promedio_Devueltos ?? 0;
|
var devueltos = item.Promedio_Devueltos ?? 0;
|
||||||
var porcDevolucion = llevados > 0 ? (decimal)devueltos * 100 / llevados : 0;
|
var porcDevolucion = llevados > 0 ? (decimal)devueltos * 100 / llevados : 0;
|
||||||
|
|
||||||
table.Cell().Border(1).Padding(3).Text(item.Dia);
|
table.Cell().Border(1).Padding(3).Text(item.Dia);
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Cant?.ToString("N0") ?? "0");
|
table.Cell().Border(1).Padding(3).AlignRight().Text(item.Cant?.ToString("N0") ?? "0");
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(llevados.ToString("N0"));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(llevados.ToString("N0"));
|
||||||
@@ -161,16 +161,16 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates
|
|||||||
if (general != null)
|
if (general != null)
|
||||||
{
|
{
|
||||||
var boldStyle = TextStyle.Default.SemiBold();
|
var boldStyle = TextStyle.Default.SemiBold();
|
||||||
var llevadosGeneral = general.Llevados ?? 0; // Usamos el total para el %
|
var avgPercentage = Model.PromediosPorDia
|
||||||
var devueltosGeneral = general.Devueltos ?? 0; // Usamos el total para el %
|
.Where(p => p.Dia != "General" && (p.Promedio_Llevados ?? 0) > 0)
|
||||||
var porcDevolucionGeneral = llevadosGeneral > 0 ? (decimal)devueltosGeneral * 100 / llevadosGeneral : 0;
|
.Average(p => (decimal)(p.Promedio_Devueltos ?? 0) * 100 / (p.Promedio_Llevados ?? 1));
|
||||||
|
|
||||||
table.Cell().Border(1).Padding(3).Text(t => t.Span(general.Dia).Style(boldStyle));
|
table.Cell().Border(1).Padding(3).Text(t => t.Span(general.Dia).Style(boldStyle));
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Cant?.ToString("N0")).Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Cant?.ToString("N0")).Style(boldStyle));
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Promedio_Llevados?.ToString("N0")).Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Promedio_Llevados?.ToString("N0")).Style(boldStyle));
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Promedio_Devueltos?.ToString("N0")).Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Promedio_Devueltos?.ToString("N0")).Style(boldStyle));
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Promedio_Ventas?.ToString("N0")).Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(general.Promedio_Ventas?.ToString("N0")).Style(boldStyle));
|
||||||
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(porcDevolucionGeneral.ToString("F2") + "%").Style(boldStyle));
|
table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(avgPercentage.ToString("F2") + "%").Style(boldStyle));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
using GestionIntegral.Api.Services.Reportes;
|
using GestionIntegral.Api.Services.Reportes;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Reporting.NETCore;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GestionIntegral.Api.Dtos.Reportes;
|
using GestionIntegral.Api.Dtos.Reportes;
|
||||||
using GestionIntegral.Api.Data.Repositories.Impresion;
|
using GestionIntegral.Api.Data.Repositories.Impresion;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||||
using GestionIntegral.Api.Services.Distribucion;
|
using GestionIntegral.Api.Services.Distribucion;
|
||||||
using GestionIntegral.Api.Services.Pdf;
|
using GestionIntegral.Api.Services.Pdf;
|
||||||
@@ -45,6 +38,9 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
private const string PermisoVerReporteConsumoBobinas = "RR007";
|
private const string PermisoVerReporteConsumoBobinas = "RR007";
|
||||||
private const string PermisoVerReporteNovedadesCanillas = "RR004";
|
private const string PermisoVerReporteNovedadesCanillas = "RR004";
|
||||||
private const string PermisoVerReporteListadoDistMensual = "RR009";
|
private const string PermisoVerReporteListadoDistMensual = "RR009";
|
||||||
|
private const string PermisoVerReporteFacturasPublicidad = "RR010";
|
||||||
|
private const string PermisoVerReporteDistSuscripciones = "RR011";
|
||||||
|
private const string PermisoVerReportesSecretaria = "RR012";
|
||||||
|
|
||||||
public ReportesController(
|
public ReportesController(
|
||||||
IReportesService reportesService,
|
IReportesService reportesService,
|
||||||
@@ -531,7 +527,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> GetVentaMensualSecretariaElDia([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
public async Task<IActionResult> GetVentaMensualSecretariaElDia([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // Asumiendo RR002 para todos estos
|
if (!TienePermiso(PermisoVerReportesSecretaria)) return Forbid(); // Asumiendo RR002 para todos estos
|
||||||
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElDiaAsync(fechaDesde, fechaHasta);
|
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElDiaAsync(fechaDesde, fechaHasta);
|
||||||
if (error != null) return BadRequest(new { message = error });
|
if (error != null) return BadRequest(new { message = error });
|
||||||
if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte de ventas 'El Día'." });
|
if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte de ventas 'El Día'." });
|
||||||
@@ -545,7 +541,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> GetVentaMensualSecretariaElDiaPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
public async Task<IActionResult> GetVentaMensualSecretariaElDiaPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid();
|
if (!TienePermiso(PermisoVerReportesSecretaria)) return Forbid();
|
||||||
|
|
||||||
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElDiaAsync(fechaDesde, fechaHasta);
|
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElDiaAsync(fechaDesde, fechaHasta);
|
||||||
|
|
||||||
@@ -582,7 +578,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> GetVentaMensualSecretariaElPlata([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
public async Task<IActionResult> GetVentaMensualSecretariaElPlata([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // Asumiendo RR002
|
if (!TienePermiso(PermisoVerReportesSecretaria)) return Forbid(); // Asumiendo RR002
|
||||||
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElPlataAsync(fechaDesde, fechaHasta);
|
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElPlataAsync(fechaDesde, fechaHasta);
|
||||||
if (error != null) return BadRequest(new { message = error });
|
if (error != null) return BadRequest(new { message = error });
|
||||||
if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte de ventas 'El Plata'." });
|
if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte de ventas 'El Plata'." });
|
||||||
@@ -596,7 +592,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> GetVentaMensualSecretariaElPlataPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
public async Task<IActionResult> GetVentaMensualSecretariaElPlataPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid();
|
if (!TienePermiso(PermisoVerReportesSecretaria)) return Forbid();
|
||||||
|
|
||||||
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElPlataAsync(fechaDesde, fechaHasta);
|
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElPlataAsync(fechaDesde, fechaHasta);
|
||||||
|
|
||||||
@@ -633,7 +629,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> GetVentaMensualSecretariaTirDevo([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
public async Task<IActionResult> GetVentaMensualSecretariaTirDevo([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // Asumiendo RR002
|
if (!TienePermiso(PermisoVerReportesSecretaria)) return Forbid(); // Asumiendo RR002
|
||||||
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaTirDevoAsync(fechaDesde, fechaHasta);
|
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaTirDevoAsync(fechaDesde, fechaHasta);
|
||||||
if (error != null) return BadRequest(new { message = error });
|
if (error != null) return BadRequest(new { message = error });
|
||||||
if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte de tirada/devolución." });
|
if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte de tirada/devolución." });
|
||||||
@@ -647,7 +643,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> GetVentaMensualSecretariaTirDevoPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
public async Task<IActionResult> GetVentaMensualSecretariaTirDevoPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid();
|
if (!TienePermiso(PermisoVerReportesSecretaria)) return Forbid();
|
||||||
|
|
||||||
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaTirDevoAsync(fechaDesde, fechaHasta);
|
var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaTirDevoAsync(fechaDesde, fechaHasta);
|
||||||
|
|
||||||
@@ -682,13 +678,18 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> GetReporteDistribucionCanillasData([FromQuery] DateTime fecha, [FromQuery] int idEmpresa)
|
public async Task<IActionResult> GetReporteDistribucionCanillasData(
|
||||||
|
[FromQuery] DateTime fecha,
|
||||||
|
[FromQuery] int idEmpresa,
|
||||||
|
[FromQuery] bool? esAccionista
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid();
|
if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid();
|
||||||
|
|
||||||
|
// Pasar el nuevo parámetro al servicio
|
||||||
var (canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq,
|
var (canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq,
|
||||||
ctrlDevolucionesRemitos, ctrlDevolucionesParaDistCan, ctrlDevolucionesOtrosDias, error) =
|
ctrlDevolucionesRemitos, ctrlDevolucionesParaDistCan, ctrlDevolucionesOtrosDias, error) =
|
||||||
await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa);
|
await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa, esAccionista);
|
||||||
|
|
||||||
if (error != null) return BadRequest(new { message = error });
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
|
||||||
@@ -723,14 +724,20 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> GetReporteDistribucionCanillasPdf([FromQuery] DateTime fecha, [FromQuery] int idEmpresa, [FromQuery] bool soloTotales = false)
|
public async Task<IActionResult> GetReporteDistribucionCanillasPdf(
|
||||||
|
[FromQuery] DateTime fecha,
|
||||||
|
[FromQuery] int idEmpresa,
|
||||||
|
[FromQuery] bool? esAccionista,
|
||||||
|
[FromQuery] bool soloTotales = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid();
|
if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid();
|
||||||
|
|
||||||
|
// Pasar el nuevo parámetro al servicio
|
||||||
var (
|
var (
|
||||||
canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq,
|
canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq,
|
||||||
remitos, ctrlDevoluciones, _, error
|
remitos, ctrlDevoluciones, _, error
|
||||||
) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa);
|
) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa, esAccionista);
|
||||||
|
|
||||||
if (error != null) return BadRequest(new { message = error });
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
|
||||||
@@ -799,11 +806,11 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
_, // canillasAll
|
_, // canillasAll
|
||||||
_, // canillasFechaLiq
|
_, // canillasFechaLiq
|
||||||
_, // canillasAccFechaLiq
|
_, // canillasAccFechaLiq
|
||||||
ctrlDevolucionesRemitosData, // Para SP_ObtenerCtrlDevoluciones -> DataSet "DSObtenerCtrlDevoluciones"
|
ctrlDevolucionesRemitosData,
|
||||||
ctrlDevolucionesParaDistCanData, // Para SP_DistCanillasCantidadEntradaSalida -> DataSet "DSCtrlDevoluciones"
|
ctrlDevolucionesParaDistCanData,
|
||||||
ctrlDevolucionesOtrosDiasData, // Para SP_DistCanillasCantidadEntradaSalidaOtrosDias -> DataSet "DSCtrlDevolucionesOtrosDias"
|
ctrlDevolucionesOtrosDiasData,
|
||||||
error
|
error
|
||||||
) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); // Reutilizamos este método
|
) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa, null);
|
||||||
|
|
||||||
if (error != null) return BadRequest(new { message = error });
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
|
||||||
@@ -837,7 +844,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
var (
|
var (
|
||||||
_, _, _, _, _, // Datos no utilizados
|
_, _, _, _, _, // Datos no utilizados
|
||||||
remitos, detalles, otrosDias, error
|
remitos, detalles, otrosDias, error
|
||||||
) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa);
|
) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa, null);
|
||||||
|
|
||||||
if (error != null) return BadRequest(new { message = error });
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
|
||||||
@@ -887,11 +894,11 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerBalanceCuentas)) return Forbid();
|
if (!TienePermiso(PermisoVerBalanceCuentas)) return Forbid();
|
||||||
|
|
||||||
var (entradasSalidas, debitosCreditos, pagos, saldos, error) =
|
var (entradasSalidas, debitosCreditos, pagos, saldoInicial, error) =
|
||||||
await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta);
|
await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta);
|
||||||
|
|
||||||
if (error != null) return BadRequest(new { message = error });
|
if (error != null) return BadRequest(new { message = error });
|
||||||
if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any() && !saldos.Any())
|
if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any() && saldoInicial == 0m)
|
||||||
{
|
{
|
||||||
return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." });
|
return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." });
|
||||||
}
|
}
|
||||||
@@ -904,7 +911,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
EntradasSalidas = entradasSalidas ?? Enumerable.Empty<BalanceCuentaDistDto>(),
|
EntradasSalidas = entradasSalidas ?? Enumerable.Empty<BalanceCuentaDistDto>(),
|
||||||
DebitosCreditos = debitosCreditos ?? Enumerable.Empty<BalanceCuentaDebCredDto>(),
|
DebitosCreditos = debitosCreditos ?? Enumerable.Empty<BalanceCuentaDebCredDto>(),
|
||||||
Pagos = pagos ?? Enumerable.Empty<BalanceCuentaPagosDto>(),
|
Pagos = pagos ?? Enumerable.Empty<BalanceCuentaPagosDto>(),
|
||||||
Saldos = saldos ?? Enumerable.Empty<SaldoDto>(),
|
SaldoInicial = saldoInicial,
|
||||||
NombreDistribuidor = distribuidor.Distribuidor?.Nombre,
|
NombreDistribuidor = distribuidor.Distribuidor?.Nombre,
|
||||||
NombreEmpresa = empresa?.Nombre
|
NombreEmpresa = empresa?.Nombre
|
||||||
};
|
};
|
||||||
@@ -925,11 +932,11 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoVerBalanceCuentas)) return Forbid();
|
if (!TienePermiso(PermisoVerBalanceCuentas)) return Forbid();
|
||||||
|
|
||||||
var (entradasSalidas, debitosCreditos, pagos, saldos, error) =
|
var (entradasSalidas, debitosCreditos, pagos, saldoInicial, error) =
|
||||||
await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta);
|
await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta);
|
||||||
|
|
||||||
if (error != null) return BadRequest(new { message = error });
|
if (error != null) return BadRequest(new { message = error });
|
||||||
if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any())
|
if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any() && saldoInicial == 0m)
|
||||||
{
|
{
|
||||||
return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." });
|
return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." });
|
||||||
}
|
}
|
||||||
@@ -943,7 +950,7 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
Movimientos = entradasSalidas,
|
Movimientos = entradasSalidas,
|
||||||
Pagos = pagos,
|
Pagos = pagos,
|
||||||
DebitosCreditos = debitosCreditos,
|
DebitosCreditos = debitosCreditos,
|
||||||
SaldoDeCuenta = saldos.FirstOrDefault()?.Monto ?? 0, // <-- Se asigna a SaldoDeCuenta
|
SaldoInicial = saldoInicial,
|
||||||
NombreDistribuidor = distribuidor.Distribuidor?.Nombre ?? $"Distribuidor ID {idDistribuidor}",
|
NombreDistribuidor = distribuidor.Distribuidor?.Nombre ?? $"Distribuidor ID {idDistribuidor}",
|
||||||
FechaDesde = fechaDesde.ToString("dd/MM/yyyy"),
|
FechaDesde = fechaDesde.ToString("dd/MM/yyyy"),
|
||||||
FechaHasta = fechaHasta.ToString("dd/MM/yyyy"),
|
FechaHasta = fechaHasta.ToString("dd/MM/yyyy"),
|
||||||
@@ -1676,5 +1683,88 @@ namespace GestionIntegral.Api.Controllers
|
|||||||
return StatusCode(500, "Error interno al generar el PDF del reporte.");
|
return StatusCode(500, "Error interno al generar el PDF del reporte.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("suscripciones/facturas-para-publicidad/pdf")]
|
||||||
|
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async Task<IActionResult> GetReporteFacturasPublicidadPdf([FromQuery] int anio, [FromQuery] int mes)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerReporteFacturasPublicidad)) return Forbid();
|
||||||
|
|
||||||
|
var (data, error) = await _reportesService.ObtenerFacturasParaReportePublicidad(anio, mes);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (data == null || !data.Any())
|
||||||
|
{
|
||||||
|
return NotFound(new { message = "No hay facturas pagadas y pendientes de facturar para el período seleccionado." });
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// --- INICIO DE LA LÓGICA DE AGRUPACIÓN ---
|
||||||
|
var datosAgrupados = data
|
||||||
|
.GroupBy(f => f.IdEmpresa)
|
||||||
|
.Select(g => new DatosEmpresaViewModel
|
||||||
|
{
|
||||||
|
NombreEmpresa = g.First().NombreEmpresa,
|
||||||
|
Facturas = g.ToList()
|
||||||
|
})
|
||||||
|
.OrderBy(e => e.NombreEmpresa);
|
||||||
|
|
||||||
|
var viewModel = new FacturasPublicidadViewModel
|
||||||
|
{
|
||||||
|
DatosPorEmpresa = datosAgrupados,
|
||||||
|
Periodo = new DateTime(anio, mes, 1).ToString("MMMM yyyy", new CultureInfo("es-ES")),
|
||||||
|
FechaGeneracion = DateTime.Now.ToString("dd/MM/yyyy HH:mm")
|
||||||
|
};
|
||||||
|
// --- FIN DE LA LÓGICA DE AGRUPACIÓN ---
|
||||||
|
|
||||||
|
var document = new FacturasPublicidadDocument(viewModel);
|
||||||
|
byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document);
|
||||||
|
string fileName = $"ReportePublicidad_Suscripciones_{anio}-{mes:D2}.pdf";
|
||||||
|
return File(pdfBytes, "application/pdf", fileName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al generar PDF para Reporte de Facturas a Publicidad.");
|
||||||
|
return StatusCode(500, "Error interno al generar el PDF del reporte.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("suscripciones/distribucion/pdf")]
|
||||||
|
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
|
||||||
|
public async Task<IActionResult> GetReporteDistribucionSuscripcionesPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerReporteDistSuscripciones)) return Forbid();
|
||||||
|
|
||||||
|
var (altas, bajas, error) = await _reportesService.ObtenerReporteDistribucionSuscripcionesAsync(fechaDesde, fechaHasta);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if ((altas == null || !altas.Any()) && (bajas == null || !bajas.Any()))
|
||||||
|
{
|
||||||
|
return NotFound(new { message = "No se encontraron suscripciones activas ni bajas para el período seleccionado." });
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var viewModel = new DistribucionSuscripcionesViewModel(altas ?? Enumerable.Empty<DistribucionSuscripcionDto>(), bajas ?? Enumerable.Empty<DistribucionSuscripcionDto>())
|
||||||
|
{
|
||||||
|
FechaDesde = fechaDesde.ToString("dd/MM/yyyy"),
|
||||||
|
FechaHasta = fechaHasta.ToString("dd/MM/yyyy"),
|
||||||
|
FechaGeneracion = DateTime.Now.ToString("dd/MM/yyyy HH:mm")
|
||||||
|
};
|
||||||
|
|
||||||
|
var document = new DistribucionSuscripcionesDocument(viewModel);
|
||||||
|
byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document);
|
||||||
|
string fileName = $"ReporteDistribucionSuscripciones_{fechaDesde:yyyyMMdd}_al_{fechaHasta:yyyyMMdd}.pdf";
|
||||||
|
return File(pdfBytes, "application/pdf", fileName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al generar PDF para Reporte de Distribución de Suscripciones.");
|
||||||
|
return StatusCode(500, "Error interno al generar el PDF del reporte.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Suscripciones;
|
||||||
|
using GestionIntegral.Api.Services.Suscripciones;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Suscripciones
|
||||||
|
{
|
||||||
|
[Route("api/ajustes")]
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class AjustesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IAjusteService _ajusteService;
|
||||||
|
private readonly ILogger<AjustesController> _logger;
|
||||||
|
|
||||||
|
// Permiso a crear en BD
|
||||||
|
private const string PermisoGestionarAjustes = "SU011";
|
||||||
|
|
||||||
|
public AjustesController(IAjusteService ajusteService, ILogger<AjustesController> logger)
|
||||||
|
{
|
||||||
|
_ajusteService = ajusteService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
|
||||||
|
private int? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/suscriptores/{idSuscriptor}/ajustes
|
||||||
|
[HttpGet("~/api/suscriptores/{idSuscriptor:int}/ajustes")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<AjusteDto>), StatusCodes.Status200OK)]
|
||||||
|
public async Task<IActionResult> GetAjustesPorSuscriptor(int idSuscriptor, [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarAjustes)) return Forbid();
|
||||||
|
var ajustes = await _ajusteService.ObtenerAjustesPorSuscriptor(idSuscriptor, fechaDesde, fechaHasta);
|
||||||
|
return Ok(ajustes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/ajustes
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(AjusteDto), StatusCodes.Status201Created)]
|
||||||
|
public async Task<IActionResult> CreateAjuste([FromBody] CreateAjusteDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarAjustes)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _ajusteService.CrearAjusteManual(createDto, userId.Value);
|
||||||
|
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(500, "Error al crear el ajuste.");
|
||||||
|
|
||||||
|
// Devolvemos el objeto creado con un 201
|
||||||
|
return StatusCode(201, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/ajustes/{id}/anular
|
||||||
|
[HttpPost("{id:int}/anular")]
|
||||||
|
public async Task<IActionResult> Anular(int id)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarAjustes)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _ajusteService.AnularAjuste(id, userId.Value);
|
||||||
|
if (!exito) return BadRequest(new { message = error });
|
||||||
|
|
||||||
|
return Ok(new { message = "Ajuste anulado correctamente." });
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/ajustes/{id}
|
||||||
|
[HttpPut("{id:int}")]
|
||||||
|
public async Task<IActionResult> UpdateAjuste(int id, [FromBody] UpdateAjusteDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarAjustes)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
|
||||||
|
var (exito, error) = await _ajusteService.ActualizarAjuste(id, updateDto);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error != null && error.Contains("no encontrado")) return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using GestionIntegral.Api.Dtos.Suscripciones;
|
using GestionIntegral.Api.Dtos.Comunicaciones;
|
||||||
|
using GestionIntegral.Api.Services.Comunicaciones;
|
||||||
using GestionIntegral.Api.Services.Suscripciones;
|
using GestionIntegral.Api.Services.Suscripciones;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -13,14 +14,15 @@ namespace GestionIntegral.Api.Controllers.Suscripciones
|
|||||||
{
|
{
|
||||||
private readonly IFacturacionService _facturacionService;
|
private readonly IFacturacionService _facturacionService;
|
||||||
private readonly ILogger<FacturacionController> _logger;
|
private readonly ILogger<FacturacionController> _logger;
|
||||||
|
private readonly IEmailLogService _emailLogService;
|
||||||
|
private const string PermisoGestionarFacturacion = "SU006";
|
||||||
|
private const string PermisoEnviarEmail = "SU009";
|
||||||
|
|
||||||
// Permiso para generar facturación (a crear en la BD)
|
public FacturacionController(IFacturacionService facturacionService, ILogger<FacturacionController> logger, IEmailLogService emailLogService)
|
||||||
private const string PermisoGenerarFacturacion = "SU006";
|
|
||||||
|
|
||||||
public FacturacionController(IFacturacionService facturacionService, ILogger<FacturacionController> logger)
|
|
||||||
{
|
{
|
||||||
_facturacionService = facturacionService;
|
_facturacionService = facturacionService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_emailLogService = emailLogService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
@@ -28,67 +30,97 @@ namespace GestionIntegral.Api.Controllers.Suscripciones
|
|||||||
private int? GetCurrentUserId()
|
private int? GetCurrentUserId()
|
||||||
{
|
{
|
||||||
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
||||||
|
_logger.LogWarning("No se pudo obtener el UserId del token JWT en FacturacionController.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: api/facturacion/{anio}/{mes}
|
[HttpPut("{idFactura:int}/numero-factura")]
|
||||||
[HttpPost("{anio:int}/{mes:int}")]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
public async Task<IActionResult> GenerarFacturacion(int anio, int mes)
|
public async Task<IActionResult> UpdateNumeroFactura(int idFactura, [FromBody] string numeroFactura)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoGenerarFacturacion)) return Forbid();
|
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid();
|
||||||
|
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentUserId();
|
||||||
if (userId == null) return Unauthorized();
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
if (anio < 2020 || mes < 1 || mes > 12)
|
var (exito, error) = await _facturacionService.ActualizarNumeroFactura(idFactura, numeroFactura, userId.Value);
|
||||||
{
|
|
||||||
return BadRequest(new { message = "El año y el mes proporcionados no son válidos." });
|
|
||||||
}
|
|
||||||
|
|
||||||
var (exito, mensaje, facturasGeneradas) = await _facturacionService.GenerarFacturacionMensual(anio, mes, userId.Value);
|
|
||||||
|
|
||||||
if (!exito)
|
if (!exito)
|
||||||
{
|
{
|
||||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = mensaje });
|
if (error != null && error.Contains("no existe")) return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
}
|
}
|
||||||
|
return NoContent();
|
||||||
return Ok(new { message = mensaje, facturasGeneradas });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: api/facturacion/{anio}/{mes}
|
[HttpPost("{idFactura:int}/enviar-factura-pdf")]
|
||||||
[HttpGet("{anio:int}/{mes:int}")]
|
public async Task<IActionResult> EnviarFacturaPdf(int idFactura)
|
||||||
[ProducesResponseType(typeof(IEnumerable<FacturaDto>), StatusCodes.Status200OK)]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
|
||||||
public async Task<IActionResult> GetFacturas(int anio, int mes)
|
|
||||||
{
|
{
|
||||||
// Usamos el permiso de generar facturación también para verlas.
|
if (!TienePermiso(PermisoEnviarEmail)) return Forbid();
|
||||||
if (!TienePermiso(PermisoGenerarFacturacion)) return Forbid();
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
if (anio < 2020 || mes < 1 || mes > 12)
|
var (exito, error, emailDestino) = await _facturacionService.EnviarFacturaPdfPorEmail(idFactura, userId.Value);
|
||||||
{
|
|
||||||
return BadRequest(new { message = "El período no es válido." });
|
|
||||||
}
|
|
||||||
|
|
||||||
var facturas = await _facturacionService.ObtenerFacturasPorPeriodo(anio, mes);
|
|
||||||
return Ok(facturas);
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST: api/facturacion/{idFactura}/enviar-email
|
|
||||||
[HttpPost("{idFactura:int}/enviar-email")]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
||||||
public async Task<IActionResult> EnviarEmail(int idFactura)
|
|
||||||
{
|
|
||||||
// Usaremos un nuevo permiso para esta acción
|
|
||||||
if (!TienePermiso("SU009")) return Forbid();
|
|
||||||
|
|
||||||
var (exito, error) = await _facturacionService.EnviarFacturaPorEmail(idFactura);
|
|
||||||
|
|
||||||
if (!exito)
|
if (!exito)
|
||||||
{
|
{
|
||||||
return BadRequest(new { message = error });
|
return BadRequest(new { message = error });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(new { message = "Email enviado a la cola de procesamiento." });
|
var mensajeExito = $"El email con la factura PDF se ha enviado correctamente a {emailDestino}.";
|
||||||
|
return Ok(new { message = mensajeExito });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{anio:int}/{mes:int}")]
|
||||||
|
public async Task<IActionResult> GetFacturas(
|
||||||
|
int anio, int mes,
|
||||||
|
[FromQuery] string? nombreSuscriptor,
|
||||||
|
[FromQuery] string? estadoPago,
|
||||||
|
[FromQuery] string? estadoFacturacion,
|
||||||
|
[FromQuery] string? tipoFactura)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid();
|
||||||
|
if (anio < 2020 || mes < 1 || mes > 12) return BadRequest(new { message = "El período no es válido." });
|
||||||
|
|
||||||
|
var resumenes = await _facturacionService.ObtenerResumenesDeCuentaPorPeriodo(anio, mes, nombreSuscriptor, estadoPago, estadoFacturacion, tipoFactura);
|
||||||
|
return Ok(resumenes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{anio:int}/{mes:int}")]
|
||||||
|
public async Task<IActionResult> GenerarFacturacion(int anio, int mes)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
if (anio < 2020 || mes < 1 || mes > 12) return BadRequest(new { message = "El año y el mes proporcionados no son válidos." });
|
||||||
|
|
||||||
|
var (exito, mensaje, resultadoEnvio) = await _facturacionService.GenerarFacturacionMensual(anio, mes, userId.Value);
|
||||||
|
|
||||||
|
if (!exito) return StatusCode(StatusCodes.Status500InternalServerError, new { message = mensaje });
|
||||||
|
|
||||||
|
return Ok(new { message = mensaje, resultadoEnvio });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("historial-lotes-envio")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<LoteDeEnvioHistorialDto>), StatusCodes.Status200OK)]
|
||||||
|
public async Task<IActionResult> GetHistorialLotesEnvio([FromQuery] int? anio, [FromQuery] int? mes)
|
||||||
|
{
|
||||||
|
if (!TienePermiso("SU006")) return Forbid();
|
||||||
|
var historial = await _facturacionService.ObtenerHistorialLotesEnvio(anio, mes);
|
||||||
|
return Ok(historial);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint para el historial de envíos de una factura individual
|
||||||
|
[HttpGet("{idFactura:int}/historial-envios")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<EmailLogDto>), StatusCodes.Status200OK)]
|
||||||
|
public async Task<IActionResult> GetHistorialEnvios(int idFactura)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarFacturacion)) return Forbid(); // Reutilizamos el permiso
|
||||||
|
|
||||||
|
// Construimos la referencia que se guarda en el log
|
||||||
|
string referencia = $"Factura-{idFactura}";
|
||||||
|
var historial = await _emailLogService.ObtenerHistorialPorReferencia(referencia);
|
||||||
|
|
||||||
|
return Ok(historial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,15 +112,15 @@ namespace GestionIntegral.Api.Controllers.Suscripciones
|
|||||||
return Ok(promos);
|
return Ok(promos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: api/suscripciones/{idSuscripcion}/promociones/{idPromocion}
|
// POST: api/suscripciones/{idSuscripcion}/promociones
|
||||||
[HttpPost("{idSuscripcion:int}/promociones/{idPromocion:int}")]
|
[HttpPost("{idSuscripcion:int}/promociones")]
|
||||||
public async Task<IActionResult> AsignarPromocion(int idSuscripcion, int idPromocion)
|
public async Task<IActionResult> AsignarPromocion(int idSuscripcion, [FromBody] AsignarPromocionDto dto)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoGestionarSuscripciones)) return Forbid();
|
if (!TienePermiso(PermisoGestionarSuscripciones)) return Forbid();
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentUserId();
|
||||||
if (userId == null) return Unauthorized();
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
var (exito, error) = await _suscripcionService.AsignarPromocion(idSuscripcion, idPromocion, userId.Value);
|
var (exito, error) = await _suscripcionService.AsignarPromocion(idSuscripcion, dto, userId.Value);
|
||||||
if (!exito) return BadRequest(new { message = error });
|
if (!exito) return BadRequest(new { message = error });
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Comunicaciones;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Comunicaciones
|
||||||
|
{
|
||||||
|
public class EmailLogRepository : IEmailLogRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _connectionFactory;
|
||||||
|
private readonly ILogger<EmailLogRepository> _logger;
|
||||||
|
public EmailLogRepository(DbConnectionFactory connectionFactory, ILogger<EmailLogRepository> logger)
|
||||||
|
{
|
||||||
|
_connectionFactory = connectionFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateAsync(EmailLog log)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
INSERT INTO dbo.com_EmailLogs
|
||||||
|
(FechaEnvio, DestinatarioEmail, Asunto, Estado, Error, IdUsuarioDisparo, Origen, ReferenciaId, IdLoteDeEnvio)
|
||||||
|
VALUES
|
||||||
|
(@FechaEnvio, @DestinatarioEmail, @Asunto, @Estado, @Error, @IdUsuarioDisparo, @Origen, @ReferenciaId, @IdLoteDeEnvio);";
|
||||||
|
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
await connection.ExecuteAsync(sql, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<EmailLog>> GetByReferenceAsync(string referenciaId)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT * FROM dbo.com_EmailLogs
|
||||||
|
WHERE ReferenciaId = @ReferenciaId
|
||||||
|
ORDER BY FechaEnvio DESC;";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<EmailLog>(sql, new { ReferenciaId = referenciaId });
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener logs de email por ReferenciaId: {ReferenciaId}", referenciaId);
|
||||||
|
return Enumerable.Empty<EmailLog>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<EmailLog>> GetByLoteIdAsync(int idLoteDeEnvio)
|
||||||
|
{
|
||||||
|
// Ordenamos por Estado descendente para que los 'Fallidos' aparezcan primero
|
||||||
|
const string sql = @"
|
||||||
|
SELECT * FROM dbo.com_EmailLogs
|
||||||
|
WHERE IdLoteDeEnvio = @IdLoteDeEnvio
|
||||||
|
ORDER BY Estado DESC, FechaEnvio DESC;";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<EmailLog>(sql, new { IdLoteDeEnvio = idLoteDeEnvio });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener logs de email por IdLoteDeEnvio: {IdLoteDeEnvio}", idLoteDeEnvio);
|
||||||
|
return Enumerable.Empty<EmailLog>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using GestionIntegral.Api.Models.Comunicaciones;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Comunicaciones
|
||||||
|
{
|
||||||
|
public interface IEmailLogRepository
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Guarda un nuevo registro de log de email en la base de datos.
|
||||||
|
/// </summary>
|
||||||
|
Task CreateAsync(EmailLog log);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene todos los registros de log de email que coinciden con una referencia específica.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="referenciaId">El identificador de la entidad (ej. "Factura-59").</param>
|
||||||
|
/// <returns>Una colección de registros de log de email.</returns>
|
||||||
|
Task<IEnumerable<EmailLog>> GetByReferenceAsync(string referenciaId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene todos los registros de log de email que pertenecen a un lote de envío masivo.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="idLoteDeEnvio">El ID del lote de envío.</param>
|
||||||
|
/// <returns>Una colección de registros de log de email.</returns>
|
||||||
|
Task<IEnumerable<EmailLog>> GetByLoteIdAsync(int idLoteDeEnvio);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using GestionIntegral.Api.Models.Comunicaciones;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Comunicaciones
|
||||||
|
{
|
||||||
|
public interface ILoteDeEnvioRepository
|
||||||
|
{
|
||||||
|
Task<LoteDeEnvio> CreateAsync(LoteDeEnvio lote);
|
||||||
|
Task<bool> UpdateAsync(LoteDeEnvio lote);
|
||||||
|
Task<IEnumerable<LoteDeEnvio>> GetAllAsync(int? anio, int? mes);
|
||||||
|
Task<LoteDeEnvio?> GetByIdAsync(int id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Comunicaciones;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Comunicaciones
|
||||||
|
{
|
||||||
|
public class LoteDeEnvioRepository : ILoteDeEnvioRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _connectionFactory;
|
||||||
|
public LoteDeEnvioRepository(DbConnectionFactory connectionFactory)
|
||||||
|
{
|
||||||
|
_connectionFactory = connectionFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LoteDeEnvio> CreateAsync(LoteDeEnvio lote)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
INSERT INTO dbo.com_LotesDeEnvio (FechaInicio, Periodo, Origen, Estado, IdUsuarioDisparo)
|
||||||
|
OUTPUT INSERTED.*
|
||||||
|
VALUES (@FechaInicio, @Periodo, @Origen, @Estado, @IdUsuarioDisparo);";
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleAsync<LoteDeEnvio>(sql, lote);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(LoteDeEnvio lote)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
UPDATE dbo.com_LotesDeEnvio SET
|
||||||
|
FechaFin = @FechaFin,
|
||||||
|
Estado = @Estado,
|
||||||
|
TotalCorreos = @TotalCorreos,
|
||||||
|
TotalEnviados = @TotalEnviados,
|
||||||
|
TotalFallidos = @TotalFallidos
|
||||||
|
WHERE IdLoteDeEnvio = @IdLoteDeEnvio;";
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
var rows = await connection.ExecuteAsync(sql, lote);
|
||||||
|
return rows == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<LoteDeEnvio>> GetAllAsync(int? anio, int? mes)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder("SELECT * FROM dbo.com_LotesDeEnvio WHERE 1=1");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
|
||||||
|
if (anio.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND YEAR(FechaInicio) = @Anio");
|
||||||
|
parameters.Add("Anio", anio.Value);
|
||||||
|
}
|
||||||
|
if (mes.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND MONTH(FechaInicio) = @Mes");
|
||||||
|
parameters.Add("Mes", mes.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlBuilder.Append(" ORDER BY FechaInicio DESC;");
|
||||||
|
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<LoteDeEnvio>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LoteDeEnvio?> GetByIdAsync(int id)
|
||||||
|
{
|
||||||
|
const string sql = "SELECT * FROM dbo.com_LotesDeEnvio WHERE IdLoteDeEnvio = @Id;";
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<LoteDeEnvio>(sql, new { Id = id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,435 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Dtos.Auditoria;
|
||||||
|
using GestionIntegral.Api.Dtos.Contables;
|
||||||
|
using GestionIntegral.Api.Models.Contables;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Contables
|
||||||
|
{
|
||||||
|
public class CierreCuentaCorrienteRepository : ICierreCuentaCorrienteRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<CierreCuentaCorrienteRepository> _log;
|
||||||
|
|
||||||
|
public CierreCuentaCorrienteRepository(DbConnectionFactory cf, ILogger<CierreCuentaCorrienteRepository> log)
|
||||||
|
{
|
||||||
|
_cf = cf;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aliases SELECT — mapean Id_X (SQL) → IdX (modelo C#).
|
||||||
|
private const string SelectModelBase = @"
|
||||||
|
SELECT
|
||||||
|
Id_Cierre AS IdCierre,
|
||||||
|
Id_Distribuidor AS IdDistribuidor,
|
||||||
|
Id_Empresa AS IdEmpresa,
|
||||||
|
FechaCorte,
|
||||||
|
FechaCierre,
|
||||||
|
SaldoCierre,
|
||||||
|
Estado,
|
||||||
|
Justificacion,
|
||||||
|
Id_Usuario_Cierre AS IdUsuarioCierre,
|
||||||
|
Id_Usuario_Anula AS IdUsuarioAnula,
|
||||||
|
FechaAnulacion,
|
||||||
|
Justificacion_Anulacion AS JustificacionAnulacion
|
||||||
|
FROM dbo.cue_CierresCuentaCorriente";
|
||||||
|
|
||||||
|
public async Task<CierreCuentaCorriente?> GetByIdAsync(int idCierre)
|
||||||
|
{
|
||||||
|
var sql = SelectModelBase + " WHERE Id_Cierre = @IdCierreParam;";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<CierreCuentaCorriente>(sql, new { IdCierreParam = idCierre });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Cierre por ID: {IdCierre}", idCierre);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CierreCuentaCorriente?> GetUltimoCierreVigenteAsync(int idDistribuidor, int idEmpresa, IDbTransaction? transaction = null)
|
||||||
|
{
|
||||||
|
var sql = @"
|
||||||
|
SELECT TOP 1
|
||||||
|
Id_Cierre AS IdCierre,
|
||||||
|
Id_Distribuidor AS IdDistribuidor,
|
||||||
|
Id_Empresa AS IdEmpresa,
|
||||||
|
FechaCorte,
|
||||||
|
FechaCierre,
|
||||||
|
SaldoCierre,
|
||||||
|
Estado,
|
||||||
|
Justificacion,
|
||||||
|
Id_Usuario_Cierre AS IdUsuarioCierre,
|
||||||
|
Id_Usuario_Anula AS IdUsuarioAnula,
|
||||||
|
FechaAnulacion,
|
||||||
|
Justificacion_Anulacion AS JustificacionAnulacion
|
||||||
|
FROM dbo.cue_CierresCuentaCorriente
|
||||||
|
WHERE Id_Distribuidor = @IdDist
|
||||||
|
AND Id_Empresa = @IdEmp
|
||||||
|
AND Estado = 'Activo'
|
||||||
|
ORDER BY FechaCorte DESC, Id_Cierre DESC;";
|
||||||
|
|
||||||
|
var parameters = new { IdDist = idDistribuidor, IdEmp = idEmpresa };
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (transaction != null)
|
||||||
|
{
|
||||||
|
return await transaction.Connection!.QuerySingleOrDefaultAsync<CierreCuentaCorriente>(sql, parameters, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<CierreCuentaCorriente>(sql, parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener último cierre vigente Dist={IdDist} Emp={IdEmp}", idDistribuidor, idEmpresa);
|
||||||
|
if (transaction != null) throw;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CierreCuentaCorriente?> GetCierreVigenteParaFechaAsync(int idDistribuidor, int idEmpresa, DateTime fechaOperacion)
|
||||||
|
{
|
||||||
|
// Cae en período cerrado si existe un cierre Activo cuya FechaCorte sea >= fechaOperacion (la fecha está dentro del período cerrado).
|
||||||
|
// Se devuelve el más reciente (TOP 1 ORDER BY FechaCorte DESC) — el más restrictivo desde la perspectiva de la fecha consultada.
|
||||||
|
const string sql = @"
|
||||||
|
SELECT TOP 1
|
||||||
|
Id_Cierre AS IdCierre,
|
||||||
|
Id_Distribuidor AS IdDistribuidor,
|
||||||
|
Id_Empresa AS IdEmpresa,
|
||||||
|
FechaCorte,
|
||||||
|
FechaCierre,
|
||||||
|
SaldoCierre,
|
||||||
|
Estado,
|
||||||
|
Justificacion,
|
||||||
|
Id_Usuario_Cierre AS IdUsuarioCierre,
|
||||||
|
Id_Usuario_Anula AS IdUsuarioAnula,
|
||||||
|
FechaAnulacion,
|
||||||
|
Justificacion_Anulacion AS JustificacionAnulacion
|
||||||
|
FROM dbo.cue_CierresCuentaCorriente
|
||||||
|
WHERE Id_Distribuidor = @IdDist
|
||||||
|
AND Id_Empresa = @IdEmp
|
||||||
|
AND Estado = 'Activo'
|
||||||
|
AND FechaCorte >= @FechaOp
|
||||||
|
ORDER BY FechaCorte DESC, Id_Cierre DESC;";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<CierreCuentaCorriente>(sql, new
|
||||||
|
{
|
||||||
|
IdDist = idDistribuidor,
|
||||||
|
IdEmp = idEmpresa,
|
||||||
|
FechaOp = fechaOperacion.Date
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en GetCierreVigenteParaFechaAsync Dist={IdDist} Emp={IdEmp} Fecha={Fecha}", idDistribuidor, idEmpresa, fechaOperacion);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExisteCierrePosteriorVigenteAsync(int idDistribuidor, int idEmpresa, DateTime fechaCorte, int? excluirIdCierre = null, IDbTransaction? transaction = null)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT CASE WHEN EXISTS (
|
||||||
|
SELECT 1 FROM dbo.cue_CierresCuentaCorriente
|
||||||
|
WHERE Id_Distribuidor = @IdDist
|
||||||
|
AND Id_Empresa = @IdEmp
|
||||||
|
AND Estado = 'Activo'
|
||||||
|
AND FechaCorte > @FechaCorte
|
||||||
|
AND (@Excluir IS NULL OR Id_Cierre <> @Excluir)
|
||||||
|
) THEN 1 ELSE 0 END;";
|
||||||
|
|
||||||
|
var parameters = new
|
||||||
|
{
|
||||||
|
IdDist = idDistribuidor,
|
||||||
|
IdEmp = idEmpresa,
|
||||||
|
FechaCorte = fechaCorte.Date,
|
||||||
|
Excluir = excluirIdCierre
|
||||||
|
};
|
||||||
|
|
||||||
|
if (transaction != null)
|
||||||
|
{
|
||||||
|
return await transaction.Connection!.ExecuteScalarAsync<bool>(sql, parameters, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.ExecuteScalarAsync<bool>(sql, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CreateAsync(CierreCuentaCorriente cierre, int idUsuarioMod, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsertMaster = @"
|
||||||
|
INSERT INTO dbo.cue_CierresCuentaCorriente
|
||||||
|
(Id_Distribuidor, Id_Empresa, FechaCorte, FechaCierre, SaldoCierre, Estado, Justificacion, Id_Usuario_Cierre)
|
||||||
|
VALUES
|
||||||
|
(@IdDistribuidor, @IdEmpresa, @FechaCorte, @FechaCierre, @SaldoCierre, @Estado, @Justificacion, @IdUsuarioCierre);
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() AS INT);";
|
||||||
|
|
||||||
|
var idCierre = await transaction.Connection!.ExecuteScalarAsync<int>(sqlInsertMaster, new
|
||||||
|
{
|
||||||
|
cierre.IdDistribuidor,
|
||||||
|
cierre.IdEmpresa,
|
||||||
|
FechaCorte = cierre.FechaCorte.Date,
|
||||||
|
cierre.FechaCierre,
|
||||||
|
cierre.SaldoCierre,
|
||||||
|
cierre.Estado,
|
||||||
|
cierre.Justificacion,
|
||||||
|
cierre.IdUsuarioCierre
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
if (idCierre <= 0) throw new DataException("No se pudo crear el cierre — SCOPE_IDENTITY no devolvió valor.");
|
||||||
|
|
||||||
|
const string sqlInsertHist = @"
|
||||||
|
INSERT INTO dbo.cue_CierresCuentaCorriente_H
|
||||||
|
(Id_Cierre, Id_Distribuidor, Id_Empresa, FechaCorte, FechaCierre, SaldoCierre,
|
||||||
|
Estado, Justificacion, Id_Usuario_Cierre,
|
||||||
|
Id_Usuario_Anula, FechaAnulacion, Justificacion_Anulacion,
|
||||||
|
TipoMod, Id_Usuario_Mod, FechaMod)
|
||||||
|
VALUES
|
||||||
|
(@IdCierre, @IdDistribuidor, @IdEmpresa, @FechaCorte, @FechaCierre, @SaldoCierre,
|
||||||
|
@Estado, @Justificacion, @IdUsuarioCierre,
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
@TipoMod, @IdUsuarioMod, @FechaMod);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHist, new
|
||||||
|
{
|
||||||
|
IdCierre = idCierre,
|
||||||
|
cierre.IdDistribuidor,
|
||||||
|
cierre.IdEmpresa,
|
||||||
|
FechaCorte = cierre.FechaCorte.Date,
|
||||||
|
cierre.FechaCierre,
|
||||||
|
cierre.SaldoCierre,
|
||||||
|
cierre.Estado,
|
||||||
|
cierre.Justificacion,
|
||||||
|
cierre.IdUsuarioCierre,
|
||||||
|
TipoMod = "Creacion",
|
||||||
|
IdUsuarioMod = idUsuarioMod,
|
||||||
|
FechaMod = DateTime.Now
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
return idCierre;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AnularAsync(int idCierre, int idUsuarioAnula, string justificacionAnulacion, int idUsuarioMod, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
// UPDATE atómico: solo cambia Estado si está actualmente Activo. Evita doble anulación concurrente.
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.cue_CierresCuentaCorriente
|
||||||
|
SET Estado = 'Anulado',
|
||||||
|
Id_Usuario_Anula = @IdUsuarioAnula,
|
||||||
|
FechaAnulacion = @FechaAnulacion,
|
||||||
|
Justificacion_Anulacion = @JustificacionAnulacion
|
||||||
|
WHERE Id_Cierre = @IdCierre
|
||||||
|
AND Estado = 'Activo';";
|
||||||
|
|
||||||
|
var fechaAnulacion = DateTime.Now;
|
||||||
|
|
||||||
|
int affected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new
|
||||||
|
{
|
||||||
|
IdCierre = idCierre,
|
||||||
|
IdUsuarioAnula = idUsuarioAnula,
|
||||||
|
FechaAnulacion = fechaAnulacion,
|
||||||
|
JustificacionAnulacion = justificacionAnulacion
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
if (affected != 1) return false;
|
||||||
|
|
||||||
|
// Snapshot post-update para el _H. Trae los valores ya actualizados.
|
||||||
|
const string sqlSnapshot = SelectModelBase + " WHERE Id_Cierre = @IdCierre;";
|
||||||
|
var actualizado = await transaction.Connection!.QuerySingleAsync<CierreCuentaCorriente>(sqlSnapshot, new { IdCierre = idCierre }, transaction);
|
||||||
|
|
||||||
|
const string sqlInsertHist = @"
|
||||||
|
INSERT INTO dbo.cue_CierresCuentaCorriente_H
|
||||||
|
(Id_Cierre, Id_Distribuidor, Id_Empresa, FechaCorte, FechaCierre, SaldoCierre,
|
||||||
|
Estado, Justificacion, Id_Usuario_Cierre,
|
||||||
|
Id_Usuario_Anula, FechaAnulacion, Justificacion_Anulacion,
|
||||||
|
TipoMod, Id_Usuario_Mod, FechaMod)
|
||||||
|
VALUES
|
||||||
|
(@IdCierre, @IdDistribuidor, @IdEmpresa, @FechaCorte, @FechaCierre, @SaldoCierre,
|
||||||
|
@Estado, @Justificacion, @IdUsuarioCierre,
|
||||||
|
@IdUsuarioAnula, @FechaAnulacion, @JustificacionAnulacion,
|
||||||
|
@TipoMod, @IdUsuarioMod, @FechaMod);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHist, new
|
||||||
|
{
|
||||||
|
IdCierre = actualizado.IdCierre,
|
||||||
|
actualizado.IdDistribuidor,
|
||||||
|
actualizado.IdEmpresa,
|
||||||
|
FechaCorte = actualizado.FechaCorte.Date,
|
||||||
|
actualizado.FechaCierre,
|
||||||
|
actualizado.SaldoCierre,
|
||||||
|
actualizado.Estado,
|
||||||
|
actualizado.Justificacion,
|
||||||
|
actualizado.IdUsuarioCierre,
|
||||||
|
actualizado.IdUsuarioAnula,
|
||||||
|
actualizado.FechaAnulacion,
|
||||||
|
JustificacionAnulacion = actualizado.JustificacionAnulacion,
|
||||||
|
TipoMod = "Reapertura",
|
||||||
|
IdUsuarioMod = idUsuarioMod,
|
||||||
|
FechaMod = DateTime.Now
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CierreCuentaCorrienteDto>> GetAllAsync(
|
||||||
|
int? idDistribuidor, int? idEmpresa, string? estado,
|
||||||
|
DateTime? fechaCorteDesde, DateTime? fechaCorteHasta)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(@"
|
||||||
|
SELECT
|
||||||
|
c.Id_Cierre AS IdCierre,
|
||||||
|
c.Id_Distribuidor AS IdDistribuidor,
|
||||||
|
d.Nombre AS NombreDistribuidor,
|
||||||
|
c.Id_Empresa AS IdEmpresa,
|
||||||
|
e.Nombre AS NombreEmpresa,
|
||||||
|
CONVERT(varchar(10), c.FechaCorte, 23) AS FechaCorte,
|
||||||
|
c.FechaCierre,
|
||||||
|
c.SaldoCierre,
|
||||||
|
c.Estado,
|
||||||
|
c.Justificacion,
|
||||||
|
c.Id_Usuario_Cierre AS IdUsuarioCierre,
|
||||||
|
(uc.Nombre + ' ' + uc.Apellido) AS NombreUsuarioCierre,
|
||||||
|
c.Id_Usuario_Anula AS IdUsuarioAnula,
|
||||||
|
CASE WHEN ua.Id IS NULL THEN NULL ELSE (ua.Nombre + ' ' + ua.Apellido) END AS NombreUsuarioAnula,
|
||||||
|
c.FechaAnulacion,
|
||||||
|
c.Justificacion_Anulacion AS JustificacionAnulacion,
|
||||||
|
CAST(CASE WHEN c.Estado = 'Activo'
|
||||||
|
AND c.Id_Cierre = (
|
||||||
|
SELECT TOP 1 c2.Id_Cierre FROM dbo.cue_CierresCuentaCorriente c2
|
||||||
|
WHERE c2.Id_Distribuidor = c.Id_Distribuidor
|
||||||
|
AND c2.Id_Empresa = c.Id_Empresa
|
||||||
|
AND c2.Estado = 'Activo'
|
||||||
|
ORDER BY c2.FechaCorte DESC, c2.Id_Cierre DESC)
|
||||||
|
THEN 1 ELSE 0 END AS bit) AS EsUltimoVigente
|
||||||
|
FROM dbo.cue_CierresCuentaCorriente c
|
||||||
|
JOIN dbo.dist_dtDistribuidores d ON d.Id_Distribuidor = c.Id_Distribuidor
|
||||||
|
JOIN dbo.dist_dtEmpresas e ON e.Id_Empresa = c.Id_Empresa
|
||||||
|
JOIN dbo.gral_Usuarios uc ON uc.Id = c.Id_Usuario_Cierre
|
||||||
|
LEFT JOIN dbo.gral_Usuarios ua ON ua.Id = c.Id_Usuario_Anula
|
||||||
|
WHERE 1=1");
|
||||||
|
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
if (idDistribuidor.HasValue) { sqlBuilder.Append(" AND c.Id_Distribuidor = @IdDist"); parameters.Add("IdDist", idDistribuidor.Value); }
|
||||||
|
if (idEmpresa.HasValue) { sqlBuilder.Append(" AND c.Id_Empresa = @IdEmp"); parameters.Add("IdEmp", idEmpresa.Value); }
|
||||||
|
if (!string.IsNullOrWhiteSpace(estado)) { sqlBuilder.Append(" AND c.Estado = @Estado"); parameters.Add("Estado", estado); }
|
||||||
|
if (fechaCorteDesde.HasValue) { sqlBuilder.Append(" AND c.FechaCorte >= @FechaDesde"); parameters.Add("FechaDesde", fechaCorteDesde.Value.Date); }
|
||||||
|
if (fechaCorteHasta.HasValue) { sqlBuilder.Append(" AND c.FechaCorte <= @FechaHasta"); parameters.Add("FechaHasta", fechaCorteHasta.Value.Date); }
|
||||||
|
sqlBuilder.Append(" ORDER BY c.FechaCorte DESC, c.Id_Cierre DESC;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<CierreCuentaCorrienteDto>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en GetAllAsync de CierresCuentaCorriente.");
|
||||||
|
return Enumerable.Empty<CierreCuentaCorrienteDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CierreCuentaCorrienteHistorialDto>> GetHistorialAsync(int idCierre)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
h.Id_Historial,
|
||||||
|
h.Id_Cierre,
|
||||||
|
h.Id_Distribuidor,
|
||||||
|
h.Id_Empresa,
|
||||||
|
h.FechaCorte,
|
||||||
|
h.FechaCierre,
|
||||||
|
h.SaldoCierre,
|
||||||
|
h.Estado,
|
||||||
|
h.Justificacion,
|
||||||
|
h.Id_Usuario_Cierre,
|
||||||
|
h.Id_Usuario_Anula,
|
||||||
|
h.FechaAnulacion,
|
||||||
|
h.Justificacion_Anulacion,
|
||||||
|
h.TipoMod,
|
||||||
|
h.Id_Usuario_Mod,
|
||||||
|
(u.Nombre + ' ' + u.Apellido) AS NombreUsuarioModifico,
|
||||||
|
h.FechaMod
|
||||||
|
FROM dbo.cue_CierresCuentaCorriente_H h
|
||||||
|
JOIN dbo.gral_Usuarios u ON u.Id = h.Id_Usuario_Mod
|
||||||
|
WHERE h.Id_Cierre = @IdCierre
|
||||||
|
ORDER BY h.FechaMod ASC, h.Id_Historial ASC;";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<CierreCuentaCorrienteHistorialDto>(sql, new { IdCierre = idCierre });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en GetHistorialAsync para Cierre ID {IdCierre}", idCierre);
|
||||||
|
return Enumerable.Empty<CierreCuentaCorrienteHistorialDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CierreCuentaCorrienteHistorialDto>> ObtenerHistorialAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
int? idUsuarioModifico, string? tipoModificacion,
|
||||||
|
int? idCierreAfectado)
|
||||||
|
{
|
||||||
|
// FechaMod cubre el rango inclusive: [fechaDesde 00:00, fechaHasta+1día). Patrón consistente con otros ObtenerHistorial del proyecto.
|
||||||
|
var sql = @"
|
||||||
|
SELECT
|
||||||
|
h.Id_Historial,
|
||||||
|
h.Id_Cierre,
|
||||||
|
h.Id_Distribuidor,
|
||||||
|
h.Id_Empresa,
|
||||||
|
h.FechaCorte,
|
||||||
|
h.FechaCierre,
|
||||||
|
h.SaldoCierre,
|
||||||
|
h.Estado,
|
||||||
|
h.Justificacion,
|
||||||
|
h.Id_Usuario_Cierre,
|
||||||
|
h.Id_Usuario_Anula,
|
||||||
|
h.FechaAnulacion,
|
||||||
|
h.Justificacion_Anulacion,
|
||||||
|
h.TipoMod,
|
||||||
|
h.Id_Usuario_Mod,
|
||||||
|
(u.Nombre + ' ' + u.Apellido) AS NombreUsuarioModifico,
|
||||||
|
h.FechaMod
|
||||||
|
FROM dbo.cue_CierresCuentaCorriente_H h
|
||||||
|
JOIN dbo.gral_Usuarios u ON u.Id = h.Id_Usuario_Mod
|
||||||
|
WHERE 1 = 1
|
||||||
|
AND (@FechaDesde IS NULL OR h.FechaMod >= @FechaDesde)
|
||||||
|
AND (@FechaHasta IS NULL OR h.FechaMod < DATEADD(DAY, 1, @FechaHasta))
|
||||||
|
AND (@IdUsuarioMod IS NULL OR h.Id_Usuario_Mod = @IdUsuarioMod)
|
||||||
|
AND (@TipoMod IS NULL OR h.TipoMod = @TipoMod)
|
||||||
|
AND (@IdCierre IS NULL OR h.Id_Cierre = @IdCierre)
|
||||||
|
ORDER BY h.FechaMod DESC, h.Id_Historial DESC;";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<CierreCuentaCorrienteHistorialDto>(sql, new
|
||||||
|
{
|
||||||
|
FechaDesde = fechaDesde?.Date,
|
||||||
|
FechaHasta = fechaHasta?.Date,
|
||||||
|
IdUsuarioMod = idUsuarioModifico,
|
||||||
|
TipoMod = tipoModificacion,
|
||||||
|
IdCierre = idCierreAfectado
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en ObtenerHistorialAsync (auditoría) de Cierres CC.");
|
||||||
|
return Enumerable.Empty<CierreCuentaCorrienteHistorialDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Auditoria;
|
||||||
|
using GestionIntegral.Api.Dtos.Contables;
|
||||||
|
using GestionIntegral.Api.Models.Contables;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Contables
|
||||||
|
{
|
||||||
|
public interface ICierreCuentaCorrienteRepository
|
||||||
|
{
|
||||||
|
Task<CierreCuentaCorriente?> GetByIdAsync(int idCierre);
|
||||||
|
|
||||||
|
// Devuelve el último cierre con Estado = 'Activo' para el par (Distribuidor + Empresa).
|
||||||
|
// Acepta una transacción opcional para usarse dentro del flujo Crear/Reabrir.
|
||||||
|
Task<CierreCuentaCorriente?> GetUltimoCierreVigenteAsync(int idDistribuidor, int idEmpresa, IDbTransaction? transaction = null);
|
||||||
|
|
||||||
|
// Verifica si la fecha cae dentro de un período cerrado: existe un cierre Activo con FechaCorte >= fechaOperacion.
|
||||||
|
Task<CierreCuentaCorriente?> GetCierreVigenteParaFechaAsync(int idDistribuidor, int idEmpresa, DateTime fechaOperacion);
|
||||||
|
|
||||||
|
// True si existe otro cierre Activo con FechaCorte > fechaCorte (excluyendo opcionalmente un cierre puntual).
|
||||||
|
// Se usa al reabrir para forzar la cascada manual.
|
||||||
|
Task<bool> ExisteCierrePosteriorVigenteAsync(int idDistribuidor, int idEmpresa, DateTime fechaCorte, int? excluirIdCierre = null, IDbTransaction? transaction = null);
|
||||||
|
|
||||||
|
// Crea la fila maestra y registra la entrada inicial en _H con TipoMod='Creacion'. Devuelve el Id_Cierre generado.
|
||||||
|
Task<int> CreateAsync(CierreCuentaCorriente cierre, int idUsuarioMod, IDbTransaction transaction);
|
||||||
|
|
||||||
|
// Marca un cierre como Anulado (UPDATE atómico solo si Estado='Activo') y registra entrada en _H con TipoMod='Reapertura'.
|
||||||
|
// Devuelve true si se anuló (Estado pasó de Activo a Anulado), false si ya estaba anulado o no existía.
|
||||||
|
Task<bool> AnularAsync(int idCierre, int idUsuarioAnula, string justificacionAnulacion, int idUsuarioMod, IDbTransaction transaction);
|
||||||
|
|
||||||
|
Task<IEnumerable<CierreCuentaCorrienteDto>> GetAllAsync(
|
||||||
|
int? idDistribuidor, int? idEmpresa, string? estado,
|
||||||
|
DateTime? fechaCorteDesde, DateTime? fechaCorteHasta);
|
||||||
|
|
||||||
|
Task<IEnumerable<CierreCuentaCorrienteHistorialDto>> GetHistorialAsync(int idCierre);
|
||||||
|
|
||||||
|
// Auditoría general: filtra el historial cruzado de todos los cierres por rango de FechaMod, usuario, tipo, y opcional Id_Cierre.
|
||||||
|
Task<IEnumerable<CierreCuentaCorrienteHistorialDto>> ObtenerHistorialAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
int? idUsuarioModifico, string? tipoModificacion,
|
||||||
|
int? idCierreAfectado);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
|||||||
Task<PagoDistribuidor?> CreateAsync(PagoDistribuidor nuevoPago, int idUsuario, IDbTransaction transaction);
|
Task<PagoDistribuidor?> CreateAsync(PagoDistribuidor nuevoPago, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction);
|
Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction);
|
Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null);
|
Task<PagoDistribuidor?> GetByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null);
|
||||||
Task<IEnumerable<(PagoDistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
|
Task<IEnumerable<(PagoDistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
|
||||||
DateTime? fechaDesde, DateTime? fechaHasta,
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
int? idUsuarioModifico, string? tipoModificacion,
|
int? idUsuarioModifico, string? tipoModificacion,
|
||||||
|
|||||||
@@ -70,9 +70,10 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null)
|
public async Task<PagoDistribuidor?> GetByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.cue_PagosDistribuidor WHERE Recibo = @ReciboParam AND TipoMovimiento = @TipoMovParam");
|
var sqlBuilder = new StringBuilder(SelectQueryBase()); // Reutiliza la consulta base
|
||||||
|
sqlBuilder.Append(" WHERE Recibo = @ReciboParam AND TipoMovimiento = @TipoMovParam");
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new DynamicParameters();
|
||||||
parameters.Add("ReciboParam", recibo);
|
parameters.Add("ReciboParam", recibo);
|
||||||
parameters.Add("TipoMovParam", tipoMovimiento);
|
parameters.Add("TipoMovParam", tipoMovimiento);
|
||||||
@@ -85,12 +86,12 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var connection = _cf.CreateConnection();
|
using var connection = _cf.CreateConnection();
|
||||||
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
return await connection.QuerySingleOrDefaultAsync<PagoDistribuidor>(sqlBuilder.ToString(), parameters);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_log.LogError(ex, "Error en ExistsByReciboAndTipoMovimientoAsync. Recibo: {Recibo}, Tipo: {Tipo}", recibo, tipoMovimiento);
|
_log.LogError(ex, "Error en GetByReciboAndTipoMovimientoAsync. Recibo: {Recibo}, Tipo: {Tipo}", recibo, tipoMovimiento);
|
||||||
return true; // Asumir que existe en caso de error para prevenir duplicados
|
throw; // Relanzar para que el servicio lo maneje
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
|||||||
|
|
||||||
public async Task<IEnumerable<int>> GetAllDistribuidorIdsAsync()
|
public async Task<IEnumerable<int>> GetAllDistribuidorIdsAsync()
|
||||||
{
|
{
|
||||||
var sql = "SELECT Id_Distribuidor FROM dbo.dist_dtDistribuidores";
|
var sql = "SELECT Id_Distribuidor FROM dbo.dist_dtDistribuidores WHERE Baja = 0";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var connection = _connectionFactory.CreateConnection())
|
using (var connection = _connectionFactory.CreateConnection())
|
||||||
@@ -138,25 +138,45 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
|||||||
|
|
||||||
public async Task<IEnumerable<Saldo>> GetSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter)
|
public async Task<IEnumerable<Saldo>> GetSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder("SELECT Id_Saldo AS IdSaldo, Destino, Id_Destino AS IdDestino, Monto, Id_Empresa AS IdEmpresa, FechaUltimaModificacion FROM dbo.cue_Saldos WHERE 1=1");
|
var sqlBuilder = new StringBuilder(@"
|
||||||
|
SELECT Id_Saldo AS IdSaldo, Destino, Id_Destino AS IdDestino, Monto, Id_Empresa AS IdEmpresa, FechaUltimaModificacion
|
||||||
|
FROM dbo.cue_Saldos s
|
||||||
|
WHERE 1=1");
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new DynamicParameters();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(destinoFilter))
|
if (!string.IsNullOrWhiteSpace(destinoFilter))
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND Destino = @Destino");
|
sqlBuilder.Append(" AND s.Destino = @Destino");
|
||||||
parameters.Add("Destino", destinoFilter);
|
parameters.Add("Destino", destinoFilter);
|
||||||
|
|
||||||
|
// Filtro para excluir distribuidores de baja si el tipo es Distribuidores
|
||||||
|
// No se aplica a Canillas por requerimiento explícito del usuario
|
||||||
|
if (destinoFilter == "Distribuidores")
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND EXISTS (SELECT 1 FROM dbo.dist_dtDistribuidores d WHERE d.Id_Distribuidor = s.Id_Destino AND d.Baja = 0)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Si no hay filtro de destino, aplicamos el filtro de baja solo para Distribuidores
|
||||||
|
sqlBuilder.Append(@" AND (
|
||||||
|
(s.Destino = 'Distribuidores' AND EXISTS (SELECT 1 FROM dbo.dist_dtDistribuidores d WHERE d.Id_Distribuidor = s.Id_Destino AND d.Baja = 0))
|
||||||
|
OR (s.Destino != 'Distribuidores')
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
if (idDestinoFilter.HasValue)
|
if (idDestinoFilter.HasValue)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND Id_Destino = @IdDestino");
|
sqlBuilder.Append(" AND s.Id_Destino = @IdDestino");
|
||||||
parameters.Add("IdDestino", idDestinoFilter.Value);
|
parameters.Add("IdDestino", idDestinoFilter.Value);
|
||||||
}
|
}
|
||||||
if (idEmpresaFilter.HasValue)
|
if (idEmpresaFilter.HasValue)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND Id_Empresa = @IdEmpresa");
|
sqlBuilder.Append(" AND s.Id_Empresa = @IdEmpresa");
|
||||||
parameters.Add("IdEmpresa", idEmpresaFilter.Value);
|
parameters.Add("IdEmpresa", idEmpresaFilter.Value);
|
||||||
}
|
}
|
||||||
sqlBuilder.Append(" ORDER BY Destino, Id_Empresa, Id_Destino;");
|
sqlBuilder.Append(" ORDER BY s.Destino, s.Id_Empresa, s.Id_Destino;");
|
||||||
|
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.QueryAsync<Saldo>(sqlBuilder.ToString(), parameters);
|
return await connection.QueryAsync<Saldo>(sqlBuilder.ToString(), parameters);
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<(Distribuidor Distribuidor, string? NombreZona)>> GetAllAsync(string? nombreFilter, string? nroDocFilter)
|
public async Task<IEnumerable<(Distribuidor Distribuidor, string? NombreZona)>> GetAllAsync(string? nombreFilter, string? nroDocFilter, bool? soloActivos = true)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder(@"
|
var sqlBuilder = new StringBuilder(@"
|
||||||
SELECT
|
SELECT
|
||||||
d.Id_Distribuidor AS IdDistribuidor, d.Nombre, d.Contacto, d.NroDoc, d.Id_Zona AS IdZona,
|
d.Id_Distribuidor AS IdDistribuidor, d.Nombre, d.Contacto, d.NroDoc, d.Id_Zona AS IdZona,
|
||||||
d.Calle, d.Numero, d.Piso, d.Depto, d.Telefono, d.Email, d.Localidad,
|
d.Calle, d.Numero, d.Piso, d.Depto, d.Telefono, d.Email, d.Localidad, d.Baja, d.FechaBaja,
|
||||||
z.Nombre AS NombreZona
|
z.Nombre AS NombreZona
|
||||||
FROM dbo.dist_dtDistribuidores d
|
FROM dbo.dist_dtDistribuidores d
|
||||||
LEFT JOIN dbo.dist_dtZonas z ON d.Id_Zona = z.Id_Zona
|
LEFT JOIN dbo.dist_dtZonas z ON d.Id_Zona = z.Id_Zona
|
||||||
@@ -44,6 +44,11 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
sqlBuilder.Append(" AND d.NroDoc LIKE @NroDocParam");
|
sqlBuilder.Append(" AND d.NroDoc LIKE @NroDocParam");
|
||||||
parameters.Add("NroDocParam", $"%{nroDocFilter}%");
|
parameters.Add("NroDocParam", $"%{nroDocFilter}%");
|
||||||
}
|
}
|
||||||
|
if (soloActivos.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND d.Baja = @BajaStatus ");
|
||||||
|
parameters.Add("BajaStatus", !soloActivos.Value);
|
||||||
|
}
|
||||||
sqlBuilder.Append(" ORDER BY d.Nombre;");
|
sqlBuilder.Append(" ORDER BY d.Nombre;");
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -63,7 +68,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<DistribuidorDropdownDto?>> GetAllDropdownAsync()
|
public async Task<IEnumerable<DistribuidorDropdownDto?>> GetAllDropdownAsync(bool? soloActivos = true)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder(@"
|
var sqlBuilder = new StringBuilder(@"
|
||||||
SELECT
|
SELECT
|
||||||
@@ -71,6 +76,13 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
FROM dbo.dist_dtDistribuidores
|
FROM dbo.dist_dtDistribuidores
|
||||||
WHERE 1=1");
|
WHERE 1=1");
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new DynamicParameters();
|
||||||
|
|
||||||
|
if (soloActivos.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Baja = @BajaStatus ");
|
||||||
|
parameters.Add("BajaStatus", !soloActivos.Value);
|
||||||
|
}
|
||||||
|
|
||||||
sqlBuilder.Append(" ORDER BY Nombre;");
|
sqlBuilder.Append(" ORDER BY Nombre;");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -92,7 +104,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT
|
SELECT
|
||||||
d.Id_Distribuidor AS IdDistribuidor, d.Nombre, d.Contacto, d.NroDoc, d.Id_Zona AS IdZona,
|
d.Id_Distribuidor AS IdDistribuidor, d.Nombre, d.Contacto, d.NroDoc, d.Id_Zona AS IdZona,
|
||||||
d.Calle, d.Numero, d.Piso, d.Depto, d.Telefono, d.Email, d.Localidad,
|
d.Calle, d.Numero, d.Piso, d.Depto, d.Telefono, d.Email, d.Localidad, d.Baja, d.FechaBaja,
|
||||||
z.Nombre AS NombreZona
|
z.Nombre AS NombreZona
|
||||||
FROM dbo.dist_dtDistribuidores d
|
FROM dbo.dist_dtDistribuidores d
|
||||||
LEFT JOIN dbo.dist_dtZonas z ON d.Id_Zona = z.Id_Zona
|
LEFT JOIN dbo.dist_dtZonas z ON d.Id_Zona = z.Id_Zona
|
||||||
@@ -139,7 +151,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT
|
SELECT
|
||||||
Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona,
|
Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona,
|
||||||
Calle, Numero, Piso, Depto, Telefono, Email, Localidad
|
Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja
|
||||||
FROM dbo.dist_dtDistribuidores
|
FROM dbo.dist_dtDistribuidores
|
||||||
WHERE Id_Distribuidor = @IdParam";
|
WHERE Id_Distribuidor = @IdParam";
|
||||||
try
|
try
|
||||||
@@ -223,10 +235,10 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
public async Task<Distribuidor?> CreateAsync(Distribuidor nuevoDistribuidor, int idUsuario, IDbTransaction transaction)
|
public async Task<Distribuidor?> CreateAsync(Distribuidor nuevoDistribuidor, int idUsuario, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
const string sqlInsert = @"
|
const string sqlInsert = @"
|
||||||
INSERT INTO dbo.dist_dtDistribuidores (Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad)
|
INSERT INTO dbo.dist_dtDistribuidores (Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja)
|
||||||
OUTPUT INSERTED.Id_Distribuidor AS IdDistribuidor, INSERTED.Nombre, INSERTED.Contacto, INSERTED.NroDoc, INSERTED.Id_Zona AS IdZona,
|
OUTPUT INSERTED.Id_Distribuidor AS IdDistribuidor, INSERTED.Nombre, INSERTED.Contacto, INSERTED.NroDoc, INSERTED.Id_Zona AS IdZona,
|
||||||
INSERTED.Calle, INSERTED.Numero, INSERTED.Piso, INSERTED.Depto, INSERTED.Telefono, INSERTED.Email, INSERTED.Localidad
|
INSERTED.Calle, INSERTED.Numero, INSERTED.Piso, INSERTED.Depto, INSERTED.Telefono, INSERTED.Email, INSERTED.Localidad, INSERTED.Baja, INSERTED.FechaBaja
|
||||||
VALUES (@Nombre, @Contacto, @NroDoc, @IdZona, @Calle, @Numero, @Piso, @Depto, @Telefono, @Email, @Localidad);";
|
VALUES (@Nombre, @Contacto, @NroDoc, @IdZona, @Calle, @Numero, @Piso, @Depto, @Telefono, @Email, @Localidad, 0, NULL);";
|
||||||
|
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var inserted = await connection.QuerySingleAsync<Distribuidor>(sqlInsert, nuevoDistribuidor, transaction);
|
var inserted = await connection.QuerySingleAsync<Distribuidor>(sqlInsert, nuevoDistribuidor, transaction);
|
||||||
@@ -234,8 +246,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
|
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_dtDistribuidores_H
|
INSERT INTO dbo.dist_dtDistribuidores_H
|
||||||
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod)
|
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @BajaParam, @FechaBajaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
@@ -251,6 +263,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
TelefonoParam = inserted.Telefono,
|
TelefonoParam = inserted.Telefono,
|
||||||
EmailParam = inserted.Email,
|
EmailParam = inserted.Email,
|
||||||
LocalidadParam = inserted.Localidad,
|
LocalidadParam = inserted.Localidad,
|
||||||
|
BajaParam = inserted.Baja,
|
||||||
|
FechaBajaParam = inserted.FechaBaja,
|
||||||
IdUsuarioParam = idUsuario,
|
IdUsuarioParam = idUsuario,
|
||||||
FechaModParam = DateTime.Now,
|
FechaModParam = DateTime.Now,
|
||||||
TipoModParam = "Creado"
|
TipoModParam = "Creado"
|
||||||
@@ -263,7 +277,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var actual = await connection.QuerySingleOrDefaultAsync<Distribuidor>(
|
var actual = await connection.QuerySingleOrDefaultAsync<Distribuidor>(
|
||||||
@"SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona,
|
@"SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona,
|
||||||
Calle, Numero, Piso, Depto, Telefono, Email, Localidad
|
Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja
|
||||||
FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdDistribuidorParam",
|
FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdDistribuidorParam",
|
||||||
new { IdDistribuidorParam = distribuidorAActualizar.IdDistribuidor }, transaction);
|
new { IdDistribuidorParam = distribuidorAActualizar.IdDistribuidor }, transaction);
|
||||||
if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado.");
|
if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado.");
|
||||||
@@ -275,8 +289,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
WHERE Id_Distribuidor = @IdDistribuidor;";
|
WHERE Id_Distribuidor = @IdDistribuidor;";
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_dtDistribuidores_H
|
INSERT INTO dbo.dist_dtDistribuidores_H
|
||||||
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod)
|
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @BajaParam, @FechaBajaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
@@ -292,6 +306,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
TelefonoParam = actual.Telefono,
|
TelefonoParam = actual.Telefono,
|
||||||
EmailParam = actual.Email,
|
EmailParam = actual.Email,
|
||||||
LocalidadParam = actual.Localidad,
|
LocalidadParam = actual.Localidad,
|
||||||
|
BajaParam = actual.Baja,
|
||||||
|
FechaBajaParam = actual.FechaBaja,
|
||||||
IdUsuarioParam = idUsuario,
|
IdUsuarioParam = idUsuario,
|
||||||
FechaModParam = DateTime.Now,
|
FechaModParam = DateTime.Now,
|
||||||
TipoModParam = "Actualizado"
|
TipoModParam = "Actualizado"
|
||||||
@@ -306,7 +322,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var actual = await connection.QuerySingleOrDefaultAsync<Distribuidor>(
|
var actual = await connection.QuerySingleOrDefaultAsync<Distribuidor>(
|
||||||
@"SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona,
|
@"SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona,
|
||||||
Calle, Numero, Piso, Depto, Telefono, Email, Localidad
|
Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja
|
||||||
FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdParam", new { IdParam = id }, transaction);
|
FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdParam", new { IdParam = id }, transaction);
|
||||||
if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado.");
|
if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado.");
|
||||||
|
|
||||||
@@ -314,8 +330,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
const string sqlDelete = "DELETE FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdParam";
|
const string sqlDelete = "DELETE FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdParam";
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_dtDistribuidores_H
|
INSERT INTO dbo.dist_dtDistribuidores_H
|
||||||
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod)
|
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @BajaParam, @FechaBajaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
@@ -331,6 +347,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
TelefonoParam = actual.Telefono,
|
TelefonoParam = actual.Telefono,
|
||||||
EmailParam = actual.Email,
|
EmailParam = actual.Email,
|
||||||
LocalidadParam = actual.Localidad,
|
LocalidadParam = actual.Localidad,
|
||||||
|
BajaParam = actual.Baja,
|
||||||
|
FechaBajaParam = actual.FechaBaja,
|
||||||
IdUsuarioParam = idUsuario,
|
IdUsuarioParam = idUsuario,
|
||||||
FechaModParam = DateTime.Now,
|
FechaModParam = DateTime.Now,
|
||||||
TipoModParam = "Eliminado"
|
TipoModParam = "Eliminado"
|
||||||
@@ -340,6 +358,47 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var connection = transaction.Connection!;
|
||||||
|
var actual = await connection.QuerySingleOrDefaultAsync<Distribuidor>(
|
||||||
|
@"SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona,
|
||||||
|
Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja
|
||||||
|
FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdDistribuidorParam",
|
||||||
|
new { IdDistribuidorParam = id }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado para dar de baja/alta.");
|
||||||
|
|
||||||
|
const string sqlUpdate = "UPDATE dbo.dist_dtDistribuidores SET Baja = @BajaParam, FechaBaja = @FechaBajaParam WHERE Id_Distribuidor = @IdDistribuidorParam;";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_dtDistribuidores_H
|
||||||
|
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @BajaNuevaParam, @FechaBajaNuevaParam, @IdUsuarioParam, @FechaModParam, @TipoModHistParam);";
|
||||||
|
|
||||||
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdDistribuidorParam = actual.IdDistribuidor,
|
||||||
|
NombreParam = actual.Nombre,
|
||||||
|
ContactoParam = actual.Contacto,
|
||||||
|
NroDocParam = actual.NroDoc,
|
||||||
|
IdZonaParam = actual.IdZona,
|
||||||
|
CalleParam = actual.Calle,
|
||||||
|
NumeroParam = actual.Numero,
|
||||||
|
PisoParam = actual.Piso,
|
||||||
|
DeptoParam = actual.Depto,
|
||||||
|
TelefonoParam = actual.Telefono,
|
||||||
|
EmailParam = actual.Email,
|
||||||
|
LocalidadParam = actual.Localidad,
|
||||||
|
BajaNuevaParam = darDeBaja,
|
||||||
|
FechaBajaNuevaParam = (darDeBaja ? fechaBaja : null),
|
||||||
|
IdUsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModHistParam = (darDeBaja ? "Baja" : "Alta")
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new { BajaParam = darDeBaja, FechaBajaParam = (darDeBaja ? fechaBaja : null), IdDistribuidorParam = id }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<(DistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
|
public async Task<IEnumerable<(DistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
|
||||||
DateTime? fechaDesde, DateTime? fechaHasta,
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
int? idUsuarioModifico, string? tipoModificacion,
|
int? idUsuarioModifico, string? tipoModificacion,
|
||||||
|
|||||||
@@ -72,6 +72,13 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------
|
||||||
|
// Inhabilitada la comprobacion de existencia previa por remito y tipo de movimiento
|
||||||
|
// Pedido por Claudia Acosta el 18/11/2025
|
||||||
|
// Motivo: El ex canillita Sergio Mazza opera como distribuidor y no utiliza remitos.
|
||||||
|
// En el campo de remito se le asigna un numero aleatorio para cumplir con el requisito del sistema.
|
||||||
|
// -------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
public async Task<bool> ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null)
|
public async Task<bool> ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_EntradasSalidas WHERE Remito = @RemitoParam AND TipoMovimiento = @TipoMovimientoParam AND Id_Publicacion = @IdPublicacionParam");
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_EntradasSalidas WHERE Remito = @RemitoParam AND TipoMovimiento = @TipoMovimientoParam AND Id_Publicacion = @IdPublicacionParam");
|
||||||
@@ -96,7 +103,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
return true; // Asumir que existe en caso de error para prevenir duplicados
|
return true; // Asumir que existe en caso de error para prevenir duplicados
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
public async Task<EntradaSalidaDist?> CreateAsync(EntradaSalidaDist nuevaES, int idUsuario, IDbTransaction transaction)
|
public async Task<EntradaSalidaDist?> CreateAsync(EntradaSalidaDist nuevaES, int idUsuario, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,16 +8,17 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
public interface IDistribuidorRepository
|
public interface IDistribuidorRepository
|
||||||
{
|
{
|
||||||
Task<IEnumerable<(Distribuidor Distribuidor, string? NombreZona)>> GetAllAsync(string? nombreFilter, string? nroDocFilter);
|
Task<IEnumerable<(Distribuidor Distribuidor, string? NombreZona)>> GetAllAsync(string? nombreFilter, string? nroDocFilter, bool? soloActivos = true);
|
||||||
Task<(Distribuidor? Distribuidor, string? NombreZona)> GetByIdAsync(int id);
|
Task<(Distribuidor? Distribuidor, string? NombreZona)> GetByIdAsync(int id);
|
||||||
Task<Distribuidor?> GetByIdSimpleAsync(int id); // Para uso interno en el servicio
|
Task<Distribuidor?> GetByIdSimpleAsync(int id); // Para uso interno en el servicio
|
||||||
Task<Distribuidor?> CreateAsync(Distribuidor nuevoDistribuidor, int idUsuario, IDbTransaction transaction);
|
Task<Distribuidor?> CreateAsync(Distribuidor nuevoDistribuidor, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> UpdateAsync(Distribuidor distribuidorAActualizar, int idUsuario, IDbTransaction transaction);
|
Task<bool> UpdateAsync(Distribuidor distribuidorAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> DeleteAsync(int id, int idUsuario, IDbTransaction transaction);
|
Task<bool> DeleteAsync(int id, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> ToggleBajaAsync(int id, bool darDeBaja, DateTime? fechaBaja, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> ExistsByNroDocAsync(string nroDoc, int? excludeIdDistribuidor = null);
|
Task<bool> ExistsByNroDocAsync(string nroDoc, int? excludeIdDistribuidor = null);
|
||||||
Task<bool> ExistsByNameAsync(string nombre, int? excludeIdDistribuidor = null);
|
Task<bool> ExistsByNameAsync(string nombre, int? excludeIdDistribuidor = null);
|
||||||
Task<bool> IsInUseAsync(int id); // Verificar en dist_EntradasSalidas, cue_PagosDistribuidor, dist_PorcPago
|
Task<bool> IsInUseAsync(int id); // Verificar en dist_EntradasSalidas, cue_PagosDistribuidor, dist_PorcPago
|
||||||
Task<IEnumerable<DistribuidorDropdownDto?>> GetAllDropdownAsync();
|
Task<IEnumerable<DistribuidorDropdownDto?>> GetAllDropdownAsync(bool? soloActivos = true);
|
||||||
Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id);
|
Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id);
|
||||||
Task<IEnumerable<(DistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
|
Task<IEnumerable<(DistribuidorHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
|
||||||
DateTime? fechaDesde, DateTime? fechaHasta,
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
Task<EntradaSalidaDist?> CreateAsync(EntradaSalidaDist nuevaES, int idUsuario, IDbTransaction transaction);
|
Task<EntradaSalidaDist?> CreateAsync(EntradaSalidaDist nuevaES, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> UpdateAsync(EntradaSalidaDist esAActualizar, int idUsuario, IDbTransaction transaction);
|
Task<bool> UpdateAsync(EntradaSalidaDist esAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction);
|
Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null);
|
//Task<bool> ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null);
|
||||||
Task<IEnumerable<(EntradaSalidaDistHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
|
Task<IEnumerable<(EntradaSalidaDistHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
|
||||||
DateTime? fechaDesde, DateTime? fechaHasta,
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
int? idUsuarioModifico, string? tipoModificacion,
|
int? idUsuarioModifico, string? tipoModificacion,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
d.Nombre AS NombreDistribuidor
|
d.Nombre AS NombreDistribuidor
|
||||||
FROM dbo.dist_PorcPago pp
|
FROM dbo.dist_PorcPago pp
|
||||||
INNER JOIN dbo.dist_dtDistribuidores d ON pp.Id_Distribuidor = d.Id_Distribuidor
|
INNER JOIN dbo.dist_dtDistribuidores d ON pp.Id_Distribuidor = d.Id_Distribuidor
|
||||||
WHERE pp.Id_Publicacion = @IdPublicacionParam
|
WHERE pp.Id_Publicacion = @IdPublicacionParam AND d.Baja = 0
|
||||||
ORDER BY d.Nombre, pp.VigenciaD DESC";
|
ORDER BY d.Nombre, pp.VigenciaD DESC";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
int? idEstadoBobina,
|
int? idEstadoBobina,
|
||||||
string? remitoFilter,
|
string? remitoFilter,
|
||||||
DateTime? fechaDesde,
|
DateTime? fechaDesde,
|
||||||
DateTime? fechaHasta);
|
DateTime? fechaHasta,
|
||||||
|
DateTime? fechaEstadoDesde,
|
||||||
|
DateTime? fechaEstadoHasta);
|
||||||
|
|
||||||
Task<StockBobina?> GetByIdAsync(int idBobina);
|
Task<StockBobina?> GetByIdAsync(int idBobina);
|
||||||
Task<StockBobina?> GetByNroBobinaAsync(string nroBobina); // Para validar unicidad de NroBobina
|
Task<StockBobina?> GetByNroBobinaAsync(string nroBobina); // Para validar unicidad de NroBobina
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
|
|
||||||
public async Task<IEnumerable<StockBobina>> GetAllAsync(
|
public async Task<IEnumerable<StockBobina>> GetAllAsync(
|
||||||
int? idTipoBobina, string? nroBobinaFilter, int? idPlanta,
|
int? idTipoBobina, string? nroBobinaFilter, int? idPlanta,
|
||||||
int? idEstadoBobina, string? remitoFilter, DateTime? fechaDesde, DateTime? fechaHasta)
|
int? idEstadoBobina, string? remitoFilter, DateTime? fechaDesde, DateTime? fechaHasta, DateTime? fechaEstadoDesde, DateTime? fechaEstadoHasta)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder(@"
|
var sqlBuilder = new StringBuilder(@"
|
||||||
SELECT
|
SELECT
|
||||||
@@ -69,6 +69,16 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
sqlBuilder.Append(" AND sb.FechaRemito <= @FechaHastaParam");
|
sqlBuilder.Append(" AND sb.FechaRemito <= @FechaHastaParam");
|
||||||
parameters.Add("FechaHastaParam", fechaHasta.Value.Date);
|
parameters.Add("FechaHastaParam", fechaHasta.Value.Date);
|
||||||
}
|
}
|
||||||
|
if (fechaEstadoDesde.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND sb.FechaEstado >= @FechaEstadoDesdeParam");
|
||||||
|
parameters.Add("FechaEstadoDesdeParam", fechaEstadoDesde.Value.Date);
|
||||||
|
}
|
||||||
|
if (fechaEstadoHasta.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND sb.FechaEstado <= @FechaEstadoHastaParam");
|
||||||
|
parameters.Add("FechaEstadoHastaParam", fechaEstadoHasta.Value.Date);
|
||||||
|
}
|
||||||
|
|
||||||
sqlBuilder.Append(" ORDER BY sb.FechaRemito DESC, sb.NroBobina;");
|
sqlBuilder.Append(" ORDER BY sb.FechaRemito DESC, sb.NroBobina;");
|
||||||
|
|
||||||
|
|||||||
@@ -45,5 +45,10 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes
|
|||||||
Task<IEnumerable<LiquidacionCanillaGananciaDto>> GetLiquidacionCanillaGananciasAsync(DateTime fecha, int idCanilla);
|
Task<IEnumerable<LiquidacionCanillaGananciaDto>> GetLiquidacionCanillaGananciasAsync(DateTime fecha, int idCanilla);
|
||||||
Task<IEnumerable<ListadoDistCanMensualDiariosDto>> GetReporteMensualDiariosAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista);
|
Task<IEnumerable<ListadoDistCanMensualDiariosDto>> GetReporteMensualDiariosAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista);
|
||||||
Task<IEnumerable<ListadoDistCanMensualPubDto>> GetReporteMensualPorPublicacionAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista);
|
Task<IEnumerable<ListadoDistCanMensualPubDto>> GetReporteMensualPorPublicacionAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista);
|
||||||
|
Task<IEnumerable<FacturasParaReporteDto>> GetDatosReportePublicidadAsync(string periodo);
|
||||||
|
Task<IEnumerable<DistribucionSuscripcionDto>> GetDistribucionSuscripcionesActivasAsync(DateTime fechaDesde, DateTime fechaHasta);
|
||||||
|
Task<IEnumerable<DistribucionSuscripcionDto>> GetDistribucionSuscripcionesBajasAsync(DateTime fechaDesde, DateTime fechaHasta);
|
||||||
|
Task<IEnumerable<DetalleDistribucionCanillaDto>> GetDetalleDistribucionCanillasPubli_AllEmpresasAsync(DateTime fecha);
|
||||||
|
Task<IEnumerable<DetalleDistribucionCanillaDto>> GetDetalleDistribucionCanillasAccPubli_AllEmpresasAsync(DateTime fecha);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -547,5 +547,145 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes
|
|||||||
commandType: CommandType.StoredProcedure, commandTimeout: 120
|
commandType: CommandType.StoredProcedure, commandTimeout: 120
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<FacturasParaReporteDto>> GetDatosReportePublicidadAsync(string periodo)
|
||||||
|
{
|
||||||
|
// Esta consulta une todas las tablas necesarias para obtener los datos del reporte
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
f.IdFactura,
|
||||||
|
f.Periodo,
|
||||||
|
s.NombreCompleto AS NombreSuscriptor,
|
||||||
|
s.TipoDocumento,
|
||||||
|
s.NroDocumento,
|
||||||
|
f.ImporteFinal,
|
||||||
|
e.Id_Empresa AS IdEmpresa,
|
||||||
|
e.Nombre AS NombreEmpresa
|
||||||
|
FROM dbo.susc_Facturas f
|
||||||
|
JOIN dbo.susc_Suscriptores s ON f.IdSuscriptor = s.IdSuscriptor
|
||||||
|
-- Usamos una subconsulta para obtener la empresa de forma segura
|
||||||
|
JOIN (
|
||||||
|
SELECT DISTINCT
|
||||||
|
fd.IdFactura,
|
||||||
|
p.Id_Empresa
|
||||||
|
FROM dbo.susc_FacturaDetalles fd
|
||||||
|
JOIN dbo.susc_Suscripciones sub ON fd.IdSuscripcion = sub.IdSuscripcion
|
||||||
|
JOIN dbo.dist_dtPublicaciones p ON sub.IdPublicacion = p.Id_Publicacion
|
||||||
|
) AS FacturaEmpresa ON f.IdFactura = FacturaEmpresa.IdFactura
|
||||||
|
JOIN dbo.dist_dtEmpresas e ON FacturaEmpresa.Id_Empresa = e.Id_Empresa
|
||||||
|
WHERE
|
||||||
|
f.Periodo = @Periodo
|
||||||
|
AND f.EstadoPago = 'Pagada'
|
||||||
|
AND f.EstadoFacturacion = 'Pendiente de Facturar'
|
||||||
|
ORDER BY
|
||||||
|
e.Nombre, s.NombreCompleto;
|
||||||
|
";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _dbConnectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<FacturasParaReporteDto>(sql, new { Periodo = periodo });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al ejecutar la consulta para el Reporte de Publicidad para el período {Periodo}", periodo);
|
||||||
|
return Enumerable.Empty<FacturasParaReporteDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<DistribucionSuscripcionDto>> GetDistribucionSuscripcionesActivasAsync(DateTime fechaDesde, DateTime fechaHasta)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
e.Nombre AS NombreEmpresa, p.Nombre AS NombrePublicacion,
|
||||||
|
sus.NombreCompleto AS NombreSuscriptor, sus.Direccion, sus.Telefono,
|
||||||
|
s.FechaInicio, s.FechaFin, s.DiasEntrega, s.Observaciones
|
||||||
|
FROM dbo.susc_Suscripciones s
|
||||||
|
JOIN dbo.susc_Suscriptores sus ON s.IdSuscriptor = sus.IdSuscriptor
|
||||||
|
JOIN dbo.dist_dtPublicaciones p ON s.IdPublicacion = p.Id_Publicacion
|
||||||
|
JOIN dbo.dist_dtEmpresas e ON p.Id_Empresa = e.Id_Empresa
|
||||||
|
WHERE
|
||||||
|
-- --- INICIO DE LA CORRECCIÓN ---
|
||||||
|
-- Se asegura de que SOLO se incluyan suscripciones y suscriptores ACTIVOS.
|
||||||
|
s.Estado = 'Activa' AND sus.Activo = 1
|
||||||
|
-- --- FIN DE LA CORRECCIÓN ---
|
||||||
|
AND s.FechaInicio <= @FechaHasta
|
||||||
|
AND (s.FechaFin IS NULL OR s.FechaFin >= @FechaDesde)
|
||||||
|
ORDER BY e.Nombre, p.Nombre, sus.NombreCompleto;";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _dbConnectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<DistribucionSuscripcionDto>(sql, new { FechaDesde = fechaDesde, FechaHasta = fechaHasta });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener datos para Reporte de Distribución (Activas).");
|
||||||
|
return Enumerable.Empty<DistribucionSuscripcionDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<DistribucionSuscripcionDto>> GetDistribucionSuscripcionesBajasAsync(DateTime fechaDesde, DateTime fechaHasta)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
e.Nombre AS NombreEmpresa, p.Nombre AS NombrePublicacion,
|
||||||
|
sus.NombreCompleto AS NombreSuscriptor, sus.Direccion, sus.Telefono,
|
||||||
|
s.FechaInicio, s.FechaFin, s.DiasEntrega, s.Observaciones
|
||||||
|
FROM dbo.susc_Suscripciones s
|
||||||
|
JOIN dbo.susc_Suscriptores sus ON s.IdSuscriptor = sus.IdSuscriptor
|
||||||
|
JOIN dbo.dist_dtPublicaciones p ON s.IdPublicacion = p.Id_Publicacion
|
||||||
|
JOIN dbo.dist_dtEmpresas e ON p.Id_Empresa = e.Id_Empresa
|
||||||
|
WHERE
|
||||||
|
-- La lógica aquí es correcta: buscamos cualquier suscripción cuya fecha de fin
|
||||||
|
-- caiga dentro del rango de fechas seleccionado.
|
||||||
|
s.FechaFin BETWEEN @FechaDesde AND @FechaHasta
|
||||||
|
ORDER BY e.Nombre, p.Nombre, s.FechaFin, sus.NombreCompleto;";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _dbConnectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<DistribucionSuscripcionDto>(sql, new { FechaDesde = fechaDesde, FechaHasta = fechaHasta });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener datos para Reporte de Distribución (Bajas).");
|
||||||
|
return Enumerable.Empty<DistribucionSuscripcionDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<DetalleDistribucionCanillaDto>> GetDetalleDistribucionCanillasPubli_AllEmpresasAsync(DateTime fecha)
|
||||||
|
{
|
||||||
|
const string spName = "dbo.SP_DistCanillasEntradaSalidaPubli_AllEmpresas";
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("@fecha", fecha, DbType.DateTime);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _dbConnectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<DetalleDistribucionCanillaDto>(spName, parameters, commandType: CommandType.StoredProcedure, commandTimeout: 120);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error SP {SPName}", spName);
|
||||||
|
return Enumerable.Empty<DetalleDistribucionCanillaDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<DetalleDistribucionCanillaDto>> GetDetalleDistribucionCanillasAccPubli_AllEmpresasAsync(DateTime fecha)
|
||||||
|
{
|
||||||
|
const string spName = "dbo.SP_DistCanillasAccEntradaSalidaPubli_AllEmpresas";
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("@fecha", fecha, DbType.DateTime);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _dbConnectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<DetalleDistribucionCanillaDto>(spName, parameters, commandType: CommandType.StoredProcedure, commandTimeout: 120);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error SP {SPName}", spName);
|
||||||
|
return Enumerable.Empty<DetalleDistribucionCanillaDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Suscripciones;
|
||||||
|
using System.Data;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
||||||
|
{
|
||||||
|
public class AjusteRepository : IAjusteRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _connectionFactory;
|
||||||
|
private readonly ILogger<AjusteRepository> _logger;
|
||||||
|
|
||||||
|
public AjusteRepository(DbConnectionFactory factory, ILogger<AjusteRepository> logger)
|
||||||
|
{
|
||||||
|
_connectionFactory = factory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(Ajuste ajuste, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
UPDATE dbo.susc_Ajustes SET
|
||||||
|
IdEmpresa = @IdEmpresa,
|
||||||
|
FechaAjuste = @FechaAjuste,
|
||||||
|
TipoAjuste = @TipoAjuste,
|
||||||
|
Monto = @Monto,
|
||||||
|
Motivo = @Motivo
|
||||||
|
WHERE IdAjuste = @IdAjuste AND Estado = 'Pendiente';";
|
||||||
|
if (transaction?.Connection == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(transaction.Connection), "La conexión de la transacción no puede ser nula.");
|
||||||
|
}
|
||||||
|
var rows = await transaction.Connection.ExecuteAsync(sql, ajuste, transaction);
|
||||||
|
return rows == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Ajuste?> CreateAsync(Ajuste nuevoAjuste, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
INSERT INTO dbo.susc_Ajustes (IdSuscriptor, IdEmpresa, FechaAjuste, TipoAjuste, Monto, Motivo, Estado, IdUsuarioAlta, FechaAlta)
|
||||||
|
OUTPUT INSERTED.*
|
||||||
|
VALUES (@IdSuscriptor, @IdEmpresa, @FechaAjuste, @TipoAjuste, @Monto, @Motivo, 'Pendiente', @IdUsuarioAlta, GETDATE());";
|
||||||
|
if (transaction?.Connection == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(transaction.Connection), "La conexión de la transacción no puede ser nula.");
|
||||||
|
}
|
||||||
|
return await transaction.Connection.QuerySingleOrDefaultAsync<Ajuste>(sql, nuevoAjuste, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Ajuste>> GetAjustesPorSuscriptorAsync(int idSuscriptor, DateTime? fechaDesde, DateTime? fechaHasta)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder("SELECT * FROM dbo.susc_Ajustes WHERE IdSuscriptor = @IdSuscriptor");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("IdSuscriptor", idSuscriptor);
|
||||||
|
|
||||||
|
if (fechaDesde.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND FechaAjuste >= @FechaDesde");
|
||||||
|
parameters.Add("FechaDesde", fechaDesde.Value.Date);
|
||||||
|
}
|
||||||
|
if (fechaHasta.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND FechaAjuste <= @FechaHasta");
|
||||||
|
parameters.Add("FechaHasta", fechaHasta.Value.Date);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlBuilder.Append(" ORDER BY FechaAlta DESC;");
|
||||||
|
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<Ajuste>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Ajuste>> GetAjustesPendientesHastaFechaAsync(int idSuscriptor, int idEmpresa, DateTime fechaHasta, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT * FROM dbo.susc_Ajustes
|
||||||
|
WHERE IdSuscriptor = @IdSuscriptor
|
||||||
|
AND IdEmpresa = @IdEmpresa
|
||||||
|
AND Estado = 'Pendiente'
|
||||||
|
AND FechaAjuste <= @FechaHasta;";
|
||||||
|
|
||||||
|
if (transaction?.Connection == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(transaction.Connection), "La conexión de la transacción no puede ser nula.");
|
||||||
|
}
|
||||||
|
return await transaction.Connection.QueryAsync<Ajuste>(sql, new { idSuscriptor, idEmpresa, FechaHasta = fechaHasta }, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> MarcarAjustesComoAplicadosAsync(IEnumerable<int> idsAjustes, int idFactura, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
if (!idsAjustes.Any()) return true;
|
||||||
|
|
||||||
|
const string sql = @"
|
||||||
|
UPDATE dbo.susc_Ajustes SET
|
||||||
|
Estado = 'Aplicado',
|
||||||
|
IdFacturaAplicado = @IdFactura
|
||||||
|
WHERE IdAjuste IN @IdsAjustes;";
|
||||||
|
|
||||||
|
if (transaction?.Connection == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(transaction.Connection), "La conexión de la transacción no puede ser nula.");
|
||||||
|
}
|
||||||
|
var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { IdsAjustes = idsAjustes, IdFactura = idFactura }, transaction);
|
||||||
|
return rowsAffected == idsAjustes.Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Ajuste?> GetByIdAsync(int idAjuste)
|
||||||
|
{
|
||||||
|
const string sql = "SELECT * FROM dbo.susc_Ajustes WHERE IdAjuste = @IdAjuste;";
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<Ajuste>(sql, new { idAjuste });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> AnularAjusteAsync(int idAjuste, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
UPDATE dbo.susc_Ajustes SET
|
||||||
|
Estado = 'Anulado',
|
||||||
|
IdUsuarioAnulo = @IdUsuario,
|
||||||
|
FechaAnulacion = GETDATE()
|
||||||
|
WHERE IdAjuste = @IdAjuste AND Estado = 'Pendiente';";
|
||||||
|
|
||||||
|
if (transaction?.Connection == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(transaction.Connection), "La conexión de la transacción no puede ser nula.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = await transaction.Connection.ExecuteAsync(sql, new { IdAjuste = idAjuste, IdUsuario = idUsuario }, transaction);
|
||||||
|
return rows == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Ajuste>> GetAjustesPorIdFacturaAsync(int idFactura)
|
||||||
|
{
|
||||||
|
const string sql = "SELECT * FROM dbo.susc_Ajustes WHERE IdFacturaAplicado = @IdFactura;";
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<Ajuste>(sql, new { IdFactura = idFactura });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using Dapper;
|
||||||
|
using System.Data;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
||||||
|
{
|
||||||
|
public class FacturaDetalleRepository : IFacturaDetalleRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _connectionFactory;
|
||||||
|
private readonly ILogger<FacturaDetalleRepository> _logger;
|
||||||
|
|
||||||
|
public FacturaDetalleRepository(DbConnectionFactory connectionFactory, ILogger<FacturaDetalleRepository> logger)
|
||||||
|
{
|
||||||
|
_connectionFactory = connectionFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FacturaDetalle?> CreateAsync(FacturaDetalle nuevoDetalle, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
if (transaction == null || transaction.Connection == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
|
}
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.susc_FacturaDetalles (IdFactura, IdSuscripcion, Descripcion, ImporteBruto, DescuentoAplicado, ImporteNeto)
|
||||||
|
OUTPUT INSERTED.*
|
||||||
|
VALUES (@IdFactura, @IdSuscripcion, @Descripcion, @ImporteBruto, @DescuentoAplicado, @ImporteNeto);";
|
||||||
|
|
||||||
|
return await transaction.Connection.QuerySingleOrDefaultAsync<FacturaDetalle>(sqlInsert, nuevoDetalle, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<FacturaDetalle>> GetDetallesPorFacturaIdAsync(int idFactura)
|
||||||
|
{
|
||||||
|
const string sql = "SELECT * FROM dbo.susc_FacturaDetalles WHERE IdFactura = @IdFactura;";
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<FacturaDetalle>(sql, new { IdFactura = idFactura });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<FacturaDetalle>> GetDetallesPorPeriodoAsync(string periodo)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT fd.*
|
||||||
|
FROM dbo.susc_FacturaDetalles fd
|
||||||
|
JOIN dbo.susc_Facturas f ON fd.IdFactura = f.IdFactura
|
||||||
|
WHERE f.Periodo = @Periodo;";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<FacturaDetalle>(sql, new { Periodo = periodo });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener los detalles de factura para el período {Periodo}", periodo);
|
||||||
|
return Enumerable.Empty<FacturaDetalle>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
// Archivo: GestionIntegral.Api/Data/Repositories/Suscripciones/FacturaRepository.cs
|
|
||||||
|
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Data;
|
||||||
using GestionIntegral.Api.Models.Suscripciones;
|
using GestionIntegral.Api.Models.Suscripciones;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
||||||
{
|
{
|
||||||
@@ -19,7 +24,7 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
|
|
||||||
public async Task<Factura?> GetByIdAsync(int idFactura)
|
public async Task<Factura?> GetByIdAsync(int idFactura)
|
||||||
{
|
{
|
||||||
const string sql = "SELECT * FROM dbo.susc_Facturas WHERE IdFactura = @IdFactura;";
|
const string sql = "SELECT * FROM dbo.susc_Facturas WHERE IdFactura = @idFactura;";
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.QuerySingleOrDefaultAsync<Factura>(sql, new { idFactura });
|
return await connection.QuerySingleOrDefaultAsync<Factura>(sql, new { idFactura });
|
||||||
}
|
}
|
||||||
@@ -31,14 +36,21 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
return await connection.QueryAsync<Factura>(sql, new { Periodo = periodo });
|
return await connection.QueryAsync<Factura>(sql, new { Periodo = periodo });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Factura?> GetBySuscripcionYPeriodoAsync(int idSuscripcion, string periodo, IDbTransaction transaction)
|
public async Task<Factura?> GetBySuscriptorYPeriodoAsync(int idSuscriptor, string periodo, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
const string sql = "SELECT TOP 1 * FROM dbo.susc_Facturas WHERE IdSuscripcion = @IdSuscripcion AND Periodo = @Periodo;";
|
const string sql = "SELECT TOP 1 * FROM dbo.susc_Facturas WHERE IdSuscriptor = @IdSuscriptor AND Periodo = @Periodo;";
|
||||||
if (transaction == null || transaction.Connection == null)
|
if (transaction == null || transaction.Connection == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
}
|
}
|
||||||
return await transaction.Connection.QuerySingleOrDefaultAsync<Factura>(sql, new { IdSuscripcion = idSuscripcion, Periodo = periodo }, transaction);
|
return await transaction.Connection.QuerySingleOrDefaultAsync<Factura>(sql, new { idSuscriptor, Periodo = periodo }, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Factura>> GetListBySuscriptorYPeriodoAsync(int idSuscriptor, string periodo)
|
||||||
|
{
|
||||||
|
const string sql = "SELECT * FROM dbo.susc_Facturas WHERE IdSuscriptor = @IdSuscriptor AND Periodo = @Periodo;";
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<Factura>(sql, new { idSuscriptor, Periodo = periodo });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Factura?> CreateAsync(Factura nuevaFactura, IDbTransaction transaction)
|
public async Task<Factura?> CreateAsync(Factura nuevaFactura, IDbTransaction transaction)
|
||||||
@@ -47,26 +59,27 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const string sqlInsert = @"
|
const string sqlInsert = @"
|
||||||
INSERT INTO dbo.susc_Facturas
|
INSERT INTO dbo.susc_Facturas
|
||||||
(IdSuscripcion, Periodo, FechaEmision, FechaVencimiento, ImporteBruto,
|
(IdSuscriptor, Periodo, FechaEmision, FechaVencimiento, ImporteBruto,
|
||||||
DescuentoAplicado, ImporteFinal, Estado)
|
DescuentoAplicado, ImporteFinal, EstadoPago, EstadoFacturacion, TipoFactura)
|
||||||
OUTPUT INSERTED.*
|
OUTPUT INSERTED.*
|
||||||
VALUES
|
VALUES
|
||||||
(@IdSuscripcion, @Periodo, @FechaEmision, @FechaVencimiento, @ImporteBruto,
|
(@IdSuscriptor, @Periodo, @FechaEmision, @FechaVencimiento, @ImporteBruto,
|
||||||
@DescuentoAplicado, @ImporteFinal, @Estado);";
|
@DescuentoAplicado, @ImporteFinal, @EstadoPago, @EstadoFacturacion, @TipoFactura);";
|
||||||
|
|
||||||
return await transaction.Connection.QuerySingleAsync<Factura>(sqlInsert, nuevaFactura, transaction);
|
return await transaction.Connection.QuerySingleAsync<Factura>(sqlInsert, nuevaFactura, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateEstadoAsync(int idFactura, string nuevoEstado, IDbTransaction transaction)
|
public async Task<bool> UpdateEstadoPagoAsync(int idFactura, string nuevoEstadoPago, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
if (transaction == null || transaction.Connection == null)
|
if (transaction == null || transaction.Connection == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
}
|
}
|
||||||
const string sql = "UPDATE dbo.susc_Facturas SET Estado = @NuevoEstado WHERE IdFactura = @IdFactura;";
|
const string sql = "UPDATE dbo.susc_Facturas SET EstadoPago = @NuevoEstadoPago WHERE IdFactura = @IdFactura;";
|
||||||
var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { NuevoEstado = nuevoEstado, IdFactura = idFactura }, transaction);
|
var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { NuevoEstadoPago = nuevoEstadoPago, idFactura }, transaction);
|
||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +89,12 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
}
|
}
|
||||||
const string sql = "UPDATE dbo.susc_Facturas SET NumeroFactura = @NumeroFactura, Estado = 'Pendiente de Cobro' WHERE IdFactura = @IdFactura;";
|
const string sql = @"
|
||||||
var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { NumeroFactura = numeroFactura, IdFactura = idFactura }, transaction);
|
UPDATE dbo.susc_Facturas SET
|
||||||
|
NumeroFactura = @NumeroFactura,
|
||||||
|
EstadoFacturacion = 'Facturado'
|
||||||
|
WHERE IdFactura = @IdFactura;";
|
||||||
|
var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { NumeroFactura = numeroFactura, idFactura }, transaction);
|
||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,59 +104,168 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
}
|
}
|
||||||
const string sql = "UPDATE dbo.susc_Facturas SET IdLoteDebito = @IdLoteDebito, Estado = 'Enviada a Débito' WHERE IdFactura IN @IdsFacturas;";
|
const string sql = "UPDATE dbo.susc_Facturas SET IdLoteDebito = @IdLoteDebito WHERE IdFactura IN @IdsFacturas;";
|
||||||
var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { IdLoteDebito = idLoteDebito, IdsFacturas = idsFacturas }, transaction);
|
var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { IdLoteDebito = idLoteDebito, IdsFacturas = idsFacturas }, transaction);
|
||||||
return rowsAffected == idsFacturas.Count();
|
return rowsAffected == idsFacturas.Count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<(Factura Factura, string NombreSuscriptor, string NombrePublicacion)>> GetByPeriodoEnrichedAsync(string periodo)
|
public async Task<IEnumerable<(Factura Factura, string NombreSuscriptor, int IdEmpresa, decimal TotalPagado)>> GetByPeriodoEnrichedAsync(
|
||||||
|
string periodo, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion, string? tipoFactura)
|
||||||
{
|
{
|
||||||
const string sql = @"
|
var sqlBuilder = new StringBuilder(@"
|
||||||
SELECT f.*, s.NombreCompleto AS NombreSuscriptor, p.Nombre AS NombrePublicacion
|
WITH FacturaConEmpresa AS (
|
||||||
FROM dbo.susc_Facturas f
|
-- Esta subconsulta obtiene el IdEmpresa para cada factura basándose en la primera suscripción que encuentra en sus detalles.
|
||||||
JOIN dbo.susc_Suscripciones sc ON f.IdSuscripcion = sc.IdSuscripcion
|
-- Esto es seguro porque nuestra lógica de negocio asegura que todos los detalles de una factura pertenecen a la misma empresa.
|
||||||
JOIN dbo.susc_Suscriptores s ON sc.IdSuscriptor = s.IdSuscriptor
|
SELECT
|
||||||
JOIN dbo.dist_dtPublicaciones p ON sc.IdPublicacion = p.Id_Publicacion
|
f.IdFactura,
|
||||||
WHERE f.Periodo = @Periodo
|
(SELECT TOP 1 p.Id_Empresa
|
||||||
ORDER BY s.NombreCompleto;
|
FROM dbo.susc_FacturaDetalles fd
|
||||||
";
|
JOIN dbo.susc_Suscripciones s ON fd.IdSuscripcion = s.IdSuscripcion
|
||||||
|
JOIN dbo.dist_dtPublicaciones p ON s.IdPublicacion = p.Id_Publicacion
|
||||||
|
WHERE fd.IdFactura = f.IdFactura) AS IdEmpresa
|
||||||
|
FROM dbo.susc_Facturas f
|
||||||
|
WHERE f.Periodo = @Periodo
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
f.*,
|
||||||
|
s.NombreCompleto AS NombreSuscriptor,
|
||||||
|
fce.IdEmpresa,
|
||||||
|
(SELECT ISNULL(SUM(Monto), 0) FROM dbo.susc_Pagos pg WHERE pg.IdFactura = f.IdFactura AND pg.Estado = 'Aprobado') AS TotalPagado
|
||||||
|
FROM dbo.susc_Facturas f
|
||||||
|
JOIN dbo.susc_Suscriptores s ON f.IdSuscriptor = s.IdSuscriptor
|
||||||
|
JOIN FacturaConEmpresa fce ON f.IdFactura = fce.IdFactura
|
||||||
|
WHERE f.Periodo = @Periodo");
|
||||||
|
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("Periodo", periodo);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(nombreSuscriptor))
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND s.NombreCompleto LIKE @NombreSuscriptor");
|
||||||
|
parameters.Add("NombreSuscriptor", $"%{nombreSuscriptor}%");
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(estadoPago))
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND f.EstadoPago = @EstadoPago");
|
||||||
|
parameters.Add("EstadoPago", estadoPago);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(estadoFacturacion))
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND f.EstadoFacturacion = @EstadoFacturacion");
|
||||||
|
parameters.Add("EstadoFacturacion", estadoFacturacion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(tipoFactura))
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND f.TipoFactura = @TipoFactura");
|
||||||
|
parameters.Add("TipoFactura", tipoFactura);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlBuilder.Append(" ORDER BY s.NombreCompleto, f.IdFactura;");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
var result = await connection.QueryAsync<Factura, string, string, (Factura, string, string)>(
|
var result = await connection.QueryAsync<Factura, string, int, decimal, (Factura, string, int, decimal)>(
|
||||||
sql,
|
sqlBuilder.ToString(),
|
||||||
(factura, suscriptor, publicacion) => (factura, suscriptor, publicacion),
|
(factura, suscriptor, idEmpresa, totalPagado) => (factura, suscriptor, idEmpresa, totalPagado),
|
||||||
new { Periodo = periodo },
|
parameters,
|
||||||
splitOn: "NombreSuscriptor,NombrePublicacion"
|
splitOn: "NombreSuscriptor,IdEmpresa,TotalPagado"
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error al obtener facturas enriquecidas para el período {Periodo}", periodo);
|
_logger.LogError(ex, "Error al obtener facturas enriquecidas para el período {Periodo}", periodo);
|
||||||
return Enumerable.Empty<(Factura, string, string)>();
|
return Enumerable.Empty<(Factura, string, int, decimal)>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateEstadoYMotivoAsync(int idFactura, string nuevoEstado, string? motivoRechazo, IDbTransaction transaction)
|
public async Task<bool> UpdateEstadoYMotivoAsync(int idFactura, string nuevoEstadoPago, string? motivoRechazo, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
if (transaction == null || transaction.Connection == null)
|
if (transaction == null || transaction.Connection == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
UPDATE dbo.susc_Facturas SET
|
UPDATE dbo.susc_Facturas SET
|
||||||
Estado = @NuevoEstado,
|
EstadoPago = @NuevoEstadoPago,
|
||||||
MotivoRechazo = @MotivoRechazo
|
MotivoRechazo = @MotivoRechazo
|
||||||
WHERE IdFactura = @IdFactura;";
|
WHERE IdFactura = @IdFactura;";
|
||||||
|
var rowsAffected = await transaction.Connection.ExecuteAsync(sql, new { NuevoEstadoPago = nuevoEstadoPago, MotivoRechazo = motivoRechazo, idFactura }, transaction);
|
||||||
var rowsAffected = await transaction.Connection.ExecuteAsync(
|
|
||||||
sql,
|
|
||||||
new { NuevoEstado = nuevoEstado, MotivoRechazo = motivoRechazo, IdFactura = idFactura },
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetUltimoPeriodoFacturadoAsync()
|
||||||
|
{
|
||||||
|
const string sql = "SELECT TOP 1 Periodo FROM dbo.susc_Facturas ORDER BY Periodo DESC;";
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<string>(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<(Factura Factura, string NombreEmpresa)>> GetFacturasConEmpresaAsync(int idSuscriptor, string periodo)
|
||||||
|
{
|
||||||
|
// Esta consulta es más robusta y eficiente. Obtiene la factura y el nombre de la empresa en una sola llamada.
|
||||||
|
const string sql = @"
|
||||||
|
SELECT f.*, e.Nombre AS NombreEmpresa
|
||||||
|
FROM dbo.susc_Facturas f
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP 1 emp.Nombre
|
||||||
|
FROM dbo.susc_FacturaDetalles fd
|
||||||
|
JOIN dbo.susc_Suscripciones s ON fd.IdSuscripcion = s.IdSuscripcion
|
||||||
|
JOIN dbo.dist_dtPublicaciones p ON s.IdPublicacion = p.Id_Publicacion
|
||||||
|
JOIN dbo.dist_dtEmpresas emp ON p.Id_Empresa = emp.Id_Empresa
|
||||||
|
WHERE fd.IdFactura = f.IdFactura
|
||||||
|
) e
|
||||||
|
WHERE f.IdSuscriptor = @IdSuscriptor AND f.Periodo = @Periodo;";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
var result = await connection.QueryAsync<Factura, string, (Factura, string)>(
|
||||||
|
sql,
|
||||||
|
(factura, nombreEmpresa) => (factura, nombreEmpresa ?? "N/A"), // Asignamos "N/A" si no encuentra empresa
|
||||||
|
new { IdSuscriptor = idSuscriptor, Periodo = periodo },
|
||||||
|
splitOn: "NombreEmpresa"
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener facturas con empresa para suscriptor {IdSuscriptor} y período {Periodo}", idSuscriptor, periodo);
|
||||||
|
return Enumerable.Empty<(Factura, string)>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Factura>> GetFacturasPagadasPendientesDeFacturar(string periodo)
|
||||||
|
{
|
||||||
|
// Consulta simplificada pero robusta.
|
||||||
|
const string sql = @"
|
||||||
|
SELECT * FROM dbo.susc_Facturas
|
||||||
|
WHERE Periodo = @Periodo
|
||||||
|
AND EstadoPago = 'Pagada'
|
||||||
|
AND EstadoFacturacion = 'Pendiente de Facturar';";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<Factura>(sql, new { Periodo = periodo });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener facturas pagadas pendientes de facturar para el período {Periodo}", periodo);
|
||||||
|
return Enumerable.Empty<Factura>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Factura>> GetByIdsAsync(IEnumerable<int> ids)
|
||||||
|
{
|
||||||
|
if (ids == null || !ids.Any())
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<Factura>();
|
||||||
|
}
|
||||||
|
const string sql = "SELECT * FROM dbo.susc_Facturas WHERE IdFactura IN @Ids;";
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<Factura>(sql, new { Ids = ids });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Archivo: GestionIntegral.Api/Data/Repositories/Suscripciones/IAjusteRepository.cs
|
||||||
|
|
||||||
|
using GestionIntegral.Api.Models.Suscripciones;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
||||||
|
{
|
||||||
|
public interface IAjusteRepository
|
||||||
|
{
|
||||||
|
Task<Ajuste?> GetByIdAsync(int idAjuste);
|
||||||
|
Task<Ajuste?> CreateAsync(Ajuste nuevoAjuste, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(Ajuste ajuste, IDbTransaction transaction);
|
||||||
|
Task<bool> AnularAjusteAsync(int idAjuste, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<IEnumerable<Ajuste>> GetAjustesPorSuscriptorAsync(int idSuscriptor, DateTime? fechaDesde, DateTime? fechaHasta);
|
||||||
|
Task<IEnumerable<Ajuste>> GetAjustesPendientesHastaFechaAsync(int idSuscriptor, int idEmpresa, DateTime fechaHasta, IDbTransaction transaction);
|
||||||
|
Task<bool> MarcarAjustesComoAplicadosAsync(IEnumerable<int> idsAjustes, int idFactura, IDbTransaction transaction);
|
||||||
|
Task<IEnumerable<Ajuste>> GetAjustesPorIdFacturaAsync(int idFactura);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Data;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
||||||
|
{
|
||||||
|
public interface IFacturaDetalleRepository
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un nuevo registro de detalle de factura.
|
||||||
|
/// </summary>
|
||||||
|
Task<FacturaDetalle?> CreateAsync(FacturaDetalle nuevoDetalle, IDbTransaction transaction);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene todos los detalles de una factura específica.
|
||||||
|
/// </summary>
|
||||||
|
Task<IEnumerable<FacturaDetalle>> GetDetallesPorFacturaIdAsync(int idFactura);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene de forma eficiente todos los detalles de todas las facturas de un período específico.
|
||||||
|
/// </summary>
|
||||||
|
Task<IEnumerable<FacturaDetalle>> GetDetallesPorPeriodoAsync(string periodo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,13 +6,19 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
public interface IFacturaRepository
|
public interface IFacturaRepository
|
||||||
{
|
{
|
||||||
Task<Factura?> GetByIdAsync(int idFactura);
|
Task<Factura?> GetByIdAsync(int idFactura);
|
||||||
|
Task<IEnumerable<Factura>> GetByIdsAsync(IEnumerable<int> ids);
|
||||||
Task<IEnumerable<Factura>> GetByPeriodoAsync(string periodo);
|
Task<IEnumerable<Factura>> GetByPeriodoAsync(string periodo);
|
||||||
Task<Factura?> GetBySuscripcionYPeriodoAsync(int idSuscripcion, string periodo, IDbTransaction transaction);
|
Task<Factura?> GetBySuscriptorYPeriodoAsync(int idSuscriptor, string periodo, IDbTransaction transaction);
|
||||||
|
Task<IEnumerable<Factura>> GetListBySuscriptorYPeriodoAsync(int idSuscriptor, string periodo);
|
||||||
|
Task<IEnumerable<(Factura Factura, string NombreEmpresa)>> GetFacturasConEmpresaAsync(int idSuscriptor, string periodo);
|
||||||
Task<Factura?> CreateAsync(Factura nuevaFactura, IDbTransaction transaction);
|
Task<Factura?> CreateAsync(Factura nuevaFactura, IDbTransaction transaction);
|
||||||
Task<bool> UpdateEstadoAsync(int idFactura, string nuevoEstado, IDbTransaction transaction);
|
Task<bool> UpdateEstadoPagoAsync(int idFactura, string nuevoEstadoPago, IDbTransaction transaction);
|
||||||
Task<bool> UpdateNumeroFacturaAsync(int idFactura, string numeroFactura, IDbTransaction transaction);
|
Task<bool> UpdateNumeroFacturaAsync(int idFactura, string numeroFactura, IDbTransaction transaction);
|
||||||
Task<bool> UpdateLoteDebitoAsync(IEnumerable<int> idsFacturas, int idLoteDebito, IDbTransaction transaction);
|
Task<bool> UpdateLoteDebitoAsync(IEnumerable<int> idsFacturas, int idLoteDebito, IDbTransaction transaction);
|
||||||
Task<IEnumerable<(Factura Factura, string NombreSuscriptor, string NombrePublicacion)>> GetByPeriodoEnrichedAsync(string periodo);
|
Task<IEnumerable<(Factura Factura, string NombreSuscriptor, int IdEmpresa, decimal TotalPagado)>> GetByPeriodoEnrichedAsync(
|
||||||
Task<bool> UpdateEstadoYMotivoAsync(int idFactura, string nuevoEstado, string? motivoRechazo, IDbTransaction transaction);
|
string periodo, string? nombreSuscriptor, string? estadoPago, string? estadoFacturacion, string? tipoFactura);
|
||||||
|
Task<bool> UpdateEstadoYMotivoAsync(int idFactura, string nuevoEstadoPago, string? motivoRechazo, IDbTransaction transaction);
|
||||||
|
Task<string?> GetUltimoPeriodoFacturadoAsync();
|
||||||
|
Task<IEnumerable<Factura>> GetFacturasPagadasPendientesDeFacturar(string periodo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,5 +7,6 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
{
|
{
|
||||||
Task<IEnumerable<Pago>> GetByFacturaIdAsync(int idFactura);
|
Task<IEnumerable<Pago>> GetByFacturaIdAsync(int idFactura);
|
||||||
Task<Pago?> CreateAsync(Pago nuevoPago, IDbTransaction transaction);
|
Task<Pago?> CreateAsync(Pago nuevoPago, IDbTransaction transaction);
|
||||||
|
Task<decimal> GetTotalPagadoAprobadoAsync(int idFactura, IDbTransaction transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,5 +10,6 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
Task<Promocion?> CreateAsync(Promocion nuevaPromocion, IDbTransaction transaction);
|
Task<Promocion?> CreateAsync(Promocion nuevaPromocion, IDbTransaction transaction);
|
||||||
Task<bool> UpdateAsync(Promocion promocion, IDbTransaction transaction);
|
Task<bool> UpdateAsync(Promocion promocion, IDbTransaction transaction);
|
||||||
Task<IEnumerable<Promocion>> GetPromocionesActivasParaSuscripcion(int idSuscripcion, DateTime fechaPeriodo, IDbTransaction transaction);
|
Task<IEnumerable<Promocion>> GetPromocionesActivasParaSuscripcion(int idSuscripcion, DateTime fechaPeriodo, IDbTransaction transaction);
|
||||||
|
Task<IEnumerable<Promocion>> GetPromocionesActivasParaSuscripcion(int idSuscripcion, DateTime fechaPeriodo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,13 +5,13 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
{
|
{
|
||||||
public interface ISuscripcionRepository
|
public interface ISuscripcionRepository
|
||||||
{
|
{
|
||||||
Task<IEnumerable<Suscripcion>> GetBySuscriptorIdAsync(int idSuscriptor);
|
|
||||||
Task<Suscripcion?> GetByIdAsync(int idSuscripcion);
|
Task<Suscripcion?> GetByIdAsync(int idSuscripcion);
|
||||||
|
Task<IEnumerable<Suscripcion>> GetBySuscriptorIdAsync(int idSuscriptor);
|
||||||
|
Task<IEnumerable<Suscripcion>> GetAllActivasParaFacturacion(string periodo, IDbTransaction transaction);
|
||||||
Task<Suscripcion?> CreateAsync(Suscripcion nuevaSuscripcion, IDbTransaction transaction);
|
Task<Suscripcion?> CreateAsync(Suscripcion nuevaSuscripcion, IDbTransaction transaction);
|
||||||
Task<bool> UpdateAsync(Suscripcion suscripcionAActualizar, IDbTransaction transaction);
|
Task<bool> UpdateAsync(Suscripcion suscripcionAActualizar, IDbTransaction transaction);
|
||||||
Task<IEnumerable<Suscripcion>> GetAllActivasParaFacturacion(string periodo, IDbTransaction transaction);
|
Task<IEnumerable<(SuscripcionPromocion Asignacion, Promocion Promocion)>> GetPromocionesAsignadasBySuscripcionIdAsync(int idSuscripcion);
|
||||||
Task<IEnumerable<Promocion>> GetPromocionesBySuscripcionIdAsync(int idSuscripcion);
|
Task AsignarPromocionAsync(SuscripcionPromocion asignacion, IDbTransaction transaction);
|
||||||
Task AsignarPromocionAsync(int idSuscripcion, int idPromocion, int idUsuario, IDbTransaction transaction);
|
|
||||||
Task<bool> QuitarPromocionAsync(int idSuscripcion, int idPromocion, IDbTransaction transaction);
|
Task<bool> QuitarPromocionAsync(int idSuscripcion, int idPromocion, IDbTransaction transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
OUTPUT INSERTED.*
|
OUTPUT INSERTED.*
|
||||||
VALUES
|
VALUES
|
||||||
(@IdFactura, @FechaPago, @IdFormaPago, @Monto, @Estado, @Referencia, @Observaciones, @IdUsuarioRegistro);";
|
(@IdFactura, @FechaPago, @IdFormaPago, @Monto, @Estado, @Referencia, @Observaciones, @IdUsuarioRegistro);";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await transaction.Connection.QuerySingleAsync<Pago>(sqlInsert, nuevoPago, transaction);
|
return await transaction.Connection.QuerySingleAsync<Pago>(sqlInsert, nuevoPago, transaction);
|
||||||
@@ -54,5 +54,15 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<decimal> GetTotalPagadoAprobadoAsync(int idFactura, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
if (transaction == null || transaction.Connection == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
|
}
|
||||||
|
const string sql = "SELECT ISNULL(SUM(Monto), 0) FROM dbo.susc_Pagos WHERE IdFactura = @IdFactura AND Estado = 'Aprobado';";
|
||||||
|
return await transaction.Connection.ExecuteScalarAsync<decimal>(sql, new { idFactura }, transaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
public async Task<IEnumerable<Promocion>> GetAllAsync(bool soloActivas)
|
public async Task<IEnumerable<Promocion>> GetAllAsync(bool soloActivas)
|
||||||
{
|
{
|
||||||
var sql = new StringBuilder("SELECT * FROM dbo.susc_Promociones");
|
var sql = new StringBuilder("SELECT * FROM dbo.susc_Promociones");
|
||||||
if(soloActivas)
|
if (soloActivas)
|
||||||
{
|
{
|
||||||
sql.Append(" WHERE Activa = 1");
|
sql.Append(" WHERE Activa = 1");
|
||||||
}
|
}
|
||||||
@@ -39,10 +39,12 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
public async Task<Promocion?> CreateAsync(Promocion nuevaPromocion, IDbTransaction transaction)
|
public async Task<Promocion?> CreateAsync(Promocion nuevaPromocion, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
INSERT INTO dbo.susc_Promociones (Descripcion, TipoPromocion, Valor, FechaInicio, FechaFin, Activa, IdUsuarioAlta, FechaAlta)
|
INSERT INTO dbo.susc_Promociones
|
||||||
|
(Descripcion, TipoEfecto, ValorEfecto, TipoCondicion, ValorCondicion,
|
||||||
|
FechaInicio, FechaFin, Activa, IdUsuarioAlta, FechaAlta)
|
||||||
OUTPUT INSERTED.*
|
OUTPUT INSERTED.*
|
||||||
VALUES (@Descripcion, @TipoPromocion, @Valor, @FechaInicio, @FechaFin, @Activa, @IdUsuarioAlta, GETDATE());";
|
VALUES (@Descripcion, @TipoEfecto, @ValorEfecto, @TipoCondicion,
|
||||||
|
@ValorCondicion, @FechaInicio, @FechaFin, @Activa, @IdUsuarioAlta, GETDATE());";
|
||||||
if (transaction?.Connection == null)
|
if (transaction?.Connection == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(transaction.Connection), "La conexión de la transacción no puede ser nula.");
|
throw new ArgumentNullException(nameof(transaction.Connection), "La conexión de la transacción no puede ser nula.");
|
||||||
@@ -74,20 +76,43 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
|
|
||||||
public async Task<IEnumerable<Promocion>> GetPromocionesActivasParaSuscripcion(int idSuscripcion, DateTime fechaPeriodo, IDbTransaction transaction)
|
public async Task<IEnumerable<Promocion>> GetPromocionesActivasParaSuscripcion(int idSuscripcion, DateTime fechaPeriodo, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
|
// Esta consulta ahora es más compleja para respetar ambas vigencias.
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT p.* FROM dbo.susc_Promociones p
|
SELECT p.*
|
||||||
|
FROM dbo.susc_Promociones p
|
||||||
JOIN dbo.susc_SuscripcionPromociones sp ON p.IdPromocion = sp.IdPromocion
|
JOIN dbo.susc_SuscripcionPromociones sp ON p.IdPromocion = sp.IdPromocion
|
||||||
WHERE sp.IdSuscripcion = @IdSuscripcion
|
WHERE sp.IdSuscripcion = @IdSuscripcion
|
||||||
AND p.Activa = 1
|
AND p.Activa = 1
|
||||||
AND p.FechaInicio <= @FechaPeriodo
|
-- 1. La promoción general debe estar activa en el período
|
||||||
AND (p.FechaFin IS NULL OR p.FechaFin >= @FechaPeriodo);";
|
AND p.FechaInicio <= @FechaPeriodo
|
||||||
|
AND (p.FechaFin IS NULL OR p.FechaFin >= @FechaPeriodo)
|
||||||
|
-- 2. La asignación específica al cliente debe estar activa en el período
|
||||||
|
AND sp.VigenciaDesde <= @FechaPeriodo
|
||||||
|
AND (sp.VigenciaHasta IS NULL OR sp.VigenciaHasta >= @FechaPeriodo);";
|
||||||
if (transaction?.Connection == null)
|
if (transaction?.Connection == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(transaction.Connection), "La conexión de la transacción no puede ser nula.");
|
throw new ArgumentNullException(nameof(transaction.Connection), "La conexión de la transacción no puede ser nula.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return await transaction.Connection.QueryAsync<Promocion>(sql, new { IdSuscripcion = idSuscripcion, FechaPeriodo = fechaPeriodo }, transaction);
|
return await transaction.Connection.QueryAsync<Promocion>(sql, new { IdSuscripcion = idSuscripcion, FechaPeriodo = fechaPeriodo }, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Versión SIN transacción, para solo lectura
|
||||||
|
public async Task<IEnumerable<Promocion>> GetPromocionesActivasParaSuscripcion(int idSuscripcion, DateTime fechaPeriodo)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT p.*
|
||||||
|
FROM dbo.susc_Promociones p
|
||||||
|
JOIN dbo.susc_SuscripcionPromociones sp ON p.IdPromocion = sp.IdPromocion
|
||||||
|
WHERE sp.IdSuscripcion = @IdSuscripcion
|
||||||
|
AND p.Activa = 1
|
||||||
|
-- 1. La promoción general debe estar activa en el período
|
||||||
|
AND p.FechaInicio <= @FechaPeriodo
|
||||||
|
AND (p.FechaFin IS NULL OR p.FechaFin >= @FechaPeriodo)
|
||||||
|
-- 2. La asignación específica al cliente debe estar activa en el período
|
||||||
|
AND sp.VigenciaDesde <= @FechaPeriodo
|
||||||
|
AND (sp.VigenciaHasta IS NULL OR sp.VigenciaHasta >= @FechaPeriodo);";
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<Promocion>(sql, new { idSuscripcion, FechaPeriodo = fechaPeriodo });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,10 +44,9 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
return Enumerable.Empty<Suscripcion>();
|
return Enumerable.Empty<Suscripcion>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Suscripcion>> GetAllActivasParaFacturacion(string periodo, IDbTransaction transaction)
|
public async Task<IEnumerable<Suscripcion>> GetAllActivasParaFacturacion(string periodo, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
// Lógica para determinar el rango del período (ej. '2023-11')
|
|
||||||
var year = int.Parse(periodo.Split('-')[0]);
|
var year = int.Parse(periodo.Split('-')[0]);
|
||||||
var month = int.Parse(periodo.Split('-')[1]);
|
var month = int.Parse(periodo.Split('-')[1]);
|
||||||
var primerDiaMes = new DateTime(year, month, 1);
|
var primerDiaMes = new DateTime(year, month, 1);
|
||||||
@@ -61,7 +60,7 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
AND su.Activo = 1
|
AND su.Activo = 1
|
||||||
AND s.FechaInicio <= @UltimoDiaMes
|
AND s.FechaInicio <= @UltimoDiaMes
|
||||||
AND (s.FechaFin IS NULL OR s.FechaFin >= @PrimerDiaMes);";
|
AND (s.FechaFin IS NULL OR s.FechaFin >= @PrimerDiaMes);";
|
||||||
|
|
||||||
if (transaction == null || transaction.Connection == null)
|
if (transaction == null || transaction.Connection == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
@@ -85,7 +84,7 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
VALUES
|
VALUES
|
||||||
(@IdSuscriptor, @IdPublicacion, @FechaInicio, @FechaFin, @Estado, @DiasEntrega,
|
(@IdSuscriptor, @IdPublicacion, @FechaInicio, @FechaFin, @Estado, @DiasEntrega,
|
||||||
@Observaciones, @IdUsuarioAlta, GETDATE());";
|
@Observaciones, @IdUsuarioAlta, GETDATE());";
|
||||||
|
|
||||||
return await transaction.Connection.QuerySingleAsync<Suscripcion>(sqlInsert, nuevaSuscripcion, transaction);
|
return await transaction.Connection.QuerySingleAsync<Suscripcion>(sqlInsert, nuevaSuscripcion, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,30 +111,35 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Promocion>> GetPromocionesBySuscripcionIdAsync(int idSuscripcion)
|
public async Task<IEnumerable<(SuscripcionPromocion Asignacion, Promocion Promocion)>> GetPromocionesAsignadasBySuscripcionIdAsync(int idSuscripcion)
|
||||||
{
|
{
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT p.* FROM dbo.susc_Promociones p
|
SELECT sp.*, p.*
|
||||||
JOIN dbo.susc_SuscripcionPromociones sp ON p.IdPromocion = sp.IdPromocion
|
FROM dbo.susc_SuscripcionPromociones sp
|
||||||
WHERE sp.IdSuscripcion = @IdSuscripcion;";
|
JOIN dbo.susc_Promociones p ON sp.IdPromocion = p.IdPromocion
|
||||||
|
WHERE sp.IdSuscripcion = @IdSuscripcion;";
|
||||||
|
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.QueryAsync<Promocion>(sql, new { IdSuscripcion = idSuscripcion });
|
var result = await connection.QueryAsync<SuscripcionPromocion, Promocion, (SuscripcionPromocion, Promocion)>(
|
||||||
|
sql,
|
||||||
|
(asignacion, promocion) => (asignacion, promocion),
|
||||||
|
new { IdSuscripcion = idSuscripcion },
|
||||||
|
splitOn: "IdPromocion"
|
||||||
|
);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AsignarPromocionAsync(int idSuscripcion, int idPromocion, int idUsuario, IDbTransaction transaction)
|
public async Task AsignarPromocionAsync(SuscripcionPromocion asignacion, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
if (transaction == null || transaction.Connection == null)
|
if (transaction == null || transaction.Connection == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
}
|
}
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
INSERT INTO dbo.susc_SuscripcionPromociones (IdSuscripcion, IdPromocion, IdUsuarioAsigno)
|
INSERT INTO dbo.susc_SuscripcionPromociones (IdSuscripcion, IdPromocion, IdUsuarioAsigno, VigenciaDesde, VigenciaHasta, FechaAsignacion)
|
||||||
VALUES (@IdSuscripcion, @IdPromocion, @IdUsuario);";
|
VALUES (@IdSuscripcion, @IdPromocion, @IdUsuarioAsigno, @VigenciaDesde, @VigenciaHasta, GETDATE());";
|
||||||
|
|
||||||
await transaction.Connection.ExecuteAsync(sql,
|
await transaction.Connection.ExecuteAsync(sql, asignacion, transaction);
|
||||||
new { IdSuscripcion = idSuscripcion, IdPromocion = idPromocion, IdUsuario = idUsuario },
|
|
||||||
transaction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> QuitarPromocionAsync(int idSuscripcion, int idPromocion, IDbTransaction transaction)
|
public async Task<bool> QuitarPromocionAsync(int idSuscripcion, int idPromocion, IDbTransaction transaction)
|
||||||
@@ -145,7 +149,7 @@ namespace GestionIntegral.Api.Data.Repositories.Suscripciones
|
|||||||
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
throw new ArgumentNullException(nameof(transaction), "La transacción o su conexión no pueden ser nulas.");
|
||||||
}
|
}
|
||||||
const string sql = "DELETE FROM dbo.susc_SuscripcionPromociones WHERE IdSuscripcion = @IdSuscripcion AND IdPromocion = @IdPromocion;";
|
const string sql = "DELETE FROM dbo.susc_SuscripcionPromociones WHERE IdSuscripcion = @IdSuscripcion AND IdPromocion = @IdPromocion;";
|
||||||
var rows = await transaction.Connection.ExecuteAsync(sql, new { IdSuscripcion = idSuscripcion, IdPromocion = idPromocion }, transaction);
|
var rows = await transaction.Connection.ExecuteAsync(sql, new { idSuscripcion, idPromocion }, transaction);
|
||||||
return rows == 1;
|
return rows == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Archivo: GestionIntegral.Api/Data/Repositories/Usuarios/IUsuarioRepository.cs
|
||||||
|
|
||||||
using GestionIntegral.Api.Models.Usuarios; // Para Usuario
|
using GestionIntegral.Api.Models.Usuarios; // Para Usuario
|
||||||
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
|
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -10,6 +12,7 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
{
|
{
|
||||||
Task<IEnumerable<Usuario>> GetAllAsync(string? userFilter, string? nombreFilter);
|
Task<IEnumerable<Usuario>> GetAllAsync(string? userFilter, string? nombreFilter);
|
||||||
Task<Usuario?> GetByIdAsync(int id);
|
Task<Usuario?> GetByIdAsync(int id);
|
||||||
|
Task<IEnumerable<Usuario>> GetByIdsAsync(IEnumerable<int> ids);
|
||||||
Task<Usuario?> GetByUsernameAsync(string username); // Ya existe en IAuthRepository, pero lo duplicamos para cohesión del CRUD
|
Task<Usuario?> GetByUsernameAsync(string username); // Ya existe en IAuthRepository, pero lo duplicamos para cohesión del CRUD
|
||||||
Task<Usuario?> CreateAsync(Usuario nuevoUsuario, int idUsuarioCreador, IDbTransaction transaction);
|
Task<Usuario?> CreateAsync(Usuario nuevoUsuario, int idUsuarioCreador, IDbTransaction transaction);
|
||||||
Task<bool> UpdateAsync(Usuario usuarioAActualizar, int idUsuarioModificador, IDbTransaction transaction);
|
Task<bool> UpdateAsync(Usuario usuarioAActualizar, int idUsuarioModificador, IDbTransaction transaction);
|
||||||
@@ -17,7 +20,6 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
// Task<bool> DeleteAsync(int id, int idUsuarioModificador, IDbTransaction transaction);
|
// Task<bool> DeleteAsync(int id, int idUsuarioModificador, IDbTransaction transaction);
|
||||||
Task<bool> SetPasswordAsync(int userId, string newHash, string newSalt, bool debeCambiarClave, int idUsuarioModificador, IDbTransaction transaction);
|
Task<bool> SetPasswordAsync(int userId, string newHash, string newSalt, bool debeCambiarClave, int idUsuarioModificador, IDbTransaction transaction);
|
||||||
Task<bool> UserExistsAsync(string username, int? excludeId = null);
|
Task<bool> UserExistsAsync(string username, int? excludeId = null);
|
||||||
// Para el DTO de listado
|
|
||||||
Task<IEnumerable<(Usuario Usuario, string NombrePerfil)>> GetAllWithProfileNameAsync(string? userFilter, string? nombreFilter);
|
Task<IEnumerable<(Usuario Usuario, string NombrePerfil)>> GetAllWithProfileNameAsync(string? userFilter, string? nombreFilter);
|
||||||
Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id);
|
Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id);
|
||||||
Task<IEnumerable<UsuarioHistorialDto>> GetHistorialByUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta);
|
Task<IEnumerable<UsuarioHistorialDto>> GetHistorialByUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta);
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using GestionIntegral.Api.Models.Usuarios;
|
using GestionIntegral.Api.Models.Usuarios;
|
||||||
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
|
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
||||||
{
|
{
|
||||||
@@ -88,7 +84,6 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<Usuario?> GetByIdAsync(int id)
|
public async Task<Usuario?> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
const string sql = "SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id";
|
const string sql = "SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id";
|
||||||
@@ -103,6 +98,33 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Usuario>> GetByIdsAsync(IEnumerable<int> ids)
|
||||||
|
{
|
||||||
|
// 1. Validar si la lista de IDs está vacía para evitar una consulta innecesaria a la BD.
|
||||||
|
if (ids == null || !ids.Any())
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<Usuario>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Definir la consulta. Dapper manejará la expansión de la cláusula IN de forma segura.
|
||||||
|
const string sql = "SELECT * FROM dbo.gral_Usuarios WHERE Id IN @Ids";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 3. Crear conexión y ejecutar la consulta.
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
// 4. Pasar la colección de IDs como parámetro. El nombre 'Ids' debe coincidir con el placeholder '@Ids'.
|
||||||
|
return await connection.QueryAsync<Usuario>(sql, new { Ids = ids });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 5. Registrar el error y devolver una lista vacía en caso de fallo para no romper la aplicación.
|
||||||
|
_logger.LogError(ex, "Error al obtener Usuarios por lista de IDs.");
|
||||||
|
return Enumerable.Empty<Usuario>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id)
|
public async Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id)
|
||||||
{
|
{
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
@@ -128,7 +150,6 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<Usuario?> GetByUsernameAsync(string username)
|
public async Task<Usuario?> GetByUsernameAsync(string username)
|
||||||
{
|
{
|
||||||
// Esta es la misma que en AuthRepository, si se unifican, se puede eliminar una.
|
// Esta es la misma que en AuthRepository, si se unifican, se puede eliminar una.
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>4923d7ee-0944-456c-abcd-d6ce13ba8485</UserSecretsId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using GestionIntegral.Api.Services.Contables;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Middleware
|
||||||
|
{
|
||||||
|
// Centraliza el mapeo de excepciones semánticas a HTTP responses con cuerpo JSON estandarizado.
|
||||||
|
// Va PRIMERO en el pipeline para catchear cualquier excepción que escape de los controllers/services.
|
||||||
|
public class ExceptionHandlerMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILogger<ExceptionHandlerMiddleware> _logger;
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
};
|
||||||
|
|
||||||
|
public ExceptionHandlerMiddleware(RequestDelegate next, ILogger<ExceptionHandlerMiddleware> logger)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
catch (BloqueoPorPeriodoCerradoException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"Bloqueo por período cerrado: cierre #{IdCierre} FechaCorte={FechaCorte:yyyy-MM-dd}. Path={Path}",
|
||||||
|
ex.IdCierre, ex.FechaCorte, context.Request.Path);
|
||||||
|
|
||||||
|
await WriteJsonAsync(context, StatusCodes.Status409Conflict, new
|
||||||
|
{
|
||||||
|
codigo = "PERIODO_CERRADO_BLOQUEO_OPERACION",
|
||||||
|
mensaje = ex.Message,
|
||||||
|
idCierre = ex.IdCierre,
|
||||||
|
fechaCorte = ex.FechaCorte.ToString("yyyy-MM-dd")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Excepción no manejada. Path={Path}", context.Request.Path);
|
||||||
|
|
||||||
|
await WriteJsonAsync(context, StatusCodes.Status500InternalServerError, new
|
||||||
|
{
|
||||||
|
codigo = "ERROR_INTERNO",
|
||||||
|
mensaje = "Ocurrió un error inesperado al procesar la solicitud."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task WriteJsonAsync(HttpContext context, int statusCode, object body)
|
||||||
|
{
|
||||||
|
if (context.Response.HasStarted)
|
||||||
|
{
|
||||||
|
// Si los headers ya se enviaron no podemos re-escribir el response. Solo loguear y salir.
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
context.Response.Clear();
|
||||||
|
context.Response.StatusCode = statusCode;
|
||||||
|
context.Response.ContentType = "application/json; charset=utf-8";
|
||||||
|
return context.Response.WriteAsync(JsonSerializer.Serialize(body, JsonOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace GestionIntegral.Api.Models.Comunicaciones
|
||||||
|
{
|
||||||
|
public class EmailLog
|
||||||
|
{
|
||||||
|
public int IdEmailLog { get; set; }
|
||||||
|
public DateTime FechaEnvio { get; set; }
|
||||||
|
public string DestinatarioEmail { get; set; } = string.Empty;
|
||||||
|
public string Asunto { get; set; } = string.Empty;
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
public string? Error { get; set; }
|
||||||
|
public int? IdUsuarioDisparo { get; set; }
|
||||||
|
public string? Origen { get; set; }
|
||||||
|
public string? ReferenciaId { get; set; }
|
||||||
|
public int? IdLoteDeEnvio { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace GestionIntegral.Api.Models.Comunicaciones
|
||||||
|
{
|
||||||
|
public class LoteDeEnvio
|
||||||
|
{
|
||||||
|
public int IdLoteDeEnvio { get; set; }
|
||||||
|
public DateTime FechaInicio { get; set; }
|
||||||
|
public DateTime? FechaFin { get; set; }
|
||||||
|
public string Periodo { get; set; } = string.Empty;
|
||||||
|
public string Origen { get; set; } = string.Empty;
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
public int TotalCorreos { get; set; }
|
||||||
|
public int TotalEnviados { get; set; }
|
||||||
|
public int TotalFallidos { get; set; }
|
||||||
|
public int IdUsuarioDisparo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
namespace GestionIntegral.Api.Models.Contables
|
||||||
|
{
|
||||||
|
public class CierreCuentaCorriente // Corresponde a cue_CierresCuentaCorriente
|
||||||
|
{
|
||||||
|
public int IdCierre { get; set; }
|
||||||
|
public int IdDistribuidor { get; set; }
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
public DateTime FechaCorte { get; set; }
|
||||||
|
public DateTime FechaCierre { get; set; }
|
||||||
|
public decimal SaldoCierre { get; set; } // money en SQL, decimal en C#
|
||||||
|
public string Estado { get; set; } = "Activo"; // 'Activo' | 'Anulado'
|
||||||
|
public string? Justificacion { get; set; }
|
||||||
|
public int IdUsuarioCierre { get; set; }
|
||||||
|
public int? IdUsuarioAnula { get; set; }
|
||||||
|
public DateTime? FechaAnulacion { get; set; }
|
||||||
|
public string? JustificacionAnulacion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
namespace GestionIntegral.Api.Models.Contables
|
||||||
|
{
|
||||||
|
public class CierreCuentaCorrienteHistorico // Corresponde a cue_CierresCuentaCorriente_H
|
||||||
|
{
|
||||||
|
public int Id_Historial { get; set; }
|
||||||
|
public int Id_Cierre { get; set; }
|
||||||
|
public int Id_Distribuidor { get; set; }
|
||||||
|
public int Id_Empresa { get; set; }
|
||||||
|
public DateTime FechaCorte { get; set; }
|
||||||
|
public DateTime FechaCierre { get; set; }
|
||||||
|
public decimal SaldoCierre { get; set; }
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
public string? Justificacion { get; set; }
|
||||||
|
public int Id_Usuario_Cierre { get; set; }
|
||||||
|
public int? Id_Usuario_Anula { get; set; }
|
||||||
|
public DateTime? FechaAnulacion { get; set; }
|
||||||
|
public string? Justificacion_Anulacion { get; set; }
|
||||||
|
public string TipoMod { get; set; } = string.Empty; // 'Creacion' | 'Reapertura' | 'Modificacion'
|
||||||
|
public int Id_Usuario_Mod { get; set; }
|
||||||
|
public DateTime FechaMod { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,5 +14,7 @@ namespace GestionIntegral.Api.Models.Distribucion
|
|||||||
public string? Telefono { get; set; }
|
public string? Telefono { get; set; }
|
||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
public string? Localidad { get; set; }
|
public string? Localidad { get; set; }
|
||||||
|
public bool Baja { get; set; } // Baja (bit, NOT NULL, DEFAULT 0)
|
||||||
|
public DateTime? FechaBaja { get; set; } // FechaBaja (datetime2(0), NULL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,8 @@ namespace GestionIntegral.Api.Models.Distribucion
|
|||||||
public string? Telefono { get; set; }
|
public string? Telefono { get; set; }
|
||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
public string? Localidad { get; set; }
|
public string? Localidad { get; set; }
|
||||||
|
public bool? Baja { get; set; }
|
||||||
|
public DateTime? FechaBaja { get; set; }
|
||||||
public int Id_Usuario { get; set; }
|
public int Id_Usuario { get; set; }
|
||||||
public DateTime FechaMod { get; set; }
|
public DateTime FechaMod { get; set; }
|
||||||
public string TipoMod { get; set; } = string.Empty;
|
public string TipoMod { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Auditoria
|
||||||
|
{
|
||||||
|
public class CierreCuentaCorrienteHistorialDto
|
||||||
|
{
|
||||||
|
public int Id_Historial { get; set; }
|
||||||
|
public int Id_Cierre { get; set; }
|
||||||
|
public int Id_Distribuidor { get; set; }
|
||||||
|
public int Id_Empresa { get; set; }
|
||||||
|
public DateTime FechaCorte { get; set; }
|
||||||
|
public DateTime FechaCierre { get; set; }
|
||||||
|
public decimal SaldoCierre { get; set; }
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
public string? Justificacion { get; set; }
|
||||||
|
public int Id_Usuario_Cierre { get; set; }
|
||||||
|
public int? Id_Usuario_Anula { get; set; }
|
||||||
|
public DateTime? FechaAnulacion { get; set; }
|
||||||
|
public string? Justificacion_Anulacion { get; set; }
|
||||||
|
|
||||||
|
public string TipoMod { get; set; } = string.Empty; // 'Creacion' | 'Reapertura' | 'Modificacion'
|
||||||
|
public int Id_Usuario_Mod { get; set; }
|
||||||
|
public string NombreUsuarioModifico { get; set; } = string.Empty;
|
||||||
|
public DateTime FechaMod { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Comunicaciones
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Representa un registro de historial de envío de correo para ser mostrado en la interfaz de usuario.
|
||||||
|
/// </summary>
|
||||||
|
public class EmailLogDto
|
||||||
|
{
|
||||||
|
public DateTime FechaEnvio { get; set; }
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
public string Asunto { get; set; } = string.Empty;
|
||||||
|
public string DestinatarioEmail { get; set; } = string.Empty;
|
||||||
|
public string? Error { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nombre del usuario que inició la acción de envío (ej. "Juan Pérez").
|
||||||
|
/// Puede ser "Sistema" si el envío fue automático (ej. Cierre Mensual).
|
||||||
|
/// </summary>
|
||||||
|
public string? NombreUsuarioDisparo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Comunicaciones
|
||||||
|
{
|
||||||
|
// DTO para el feedback inmediato
|
||||||
|
public class LoteDeEnvioResumenDto
|
||||||
|
{
|
||||||
|
public int IdLoteDeEnvio { get; set; }
|
||||||
|
public string Periodo { get; set; } = string.Empty;
|
||||||
|
public int TotalCorreos { get; set; }
|
||||||
|
public int TotalEnviados { get; set; }
|
||||||
|
public int TotalFallidos { get; set; }
|
||||||
|
public List<EmailLogDto> ErroresDetallados { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DTO para la tabla de historial
|
||||||
|
public class LoteDeEnvioHistorialDto
|
||||||
|
{
|
||||||
|
public int IdLoteDeEnvio { get; set; }
|
||||||
|
public DateTime FechaInicio { get; set; }
|
||||||
|
public string Periodo { get; set; } = string.Empty;
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
public int TotalCorreos { get; set; }
|
||||||
|
public int TotalEnviados { get; set; }
|
||||||
|
public int TotalFallidos { get; set; }
|
||||||
|
public string NombreUsuarioDisparo { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Comunicaciones;
|
||||||
|
|
||||||
|
public class LoteDeEnvioResumenDto
|
||||||
|
{
|
||||||
|
public int IdLoteDeEnvio { get; set; }
|
||||||
|
public required string Periodo { get; set; }
|
||||||
|
public int TotalCorreos { get; set; }
|
||||||
|
public int TotalEnviados { get; set; }
|
||||||
|
public int TotalFallidos { get; set; }
|
||||||
|
public List<EmailLogDto> ErroresDetallados { get; set; } = new();
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Dtos.Contables
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
@@ -19,10 +20,16 @@ namespace GestionIntegral.Api.Dtos.Contables
|
|||||||
[Required(ErrorMessage = "El monto del ajuste es obligatorio.")]
|
[Required(ErrorMessage = "El monto del ajuste es obligatorio.")]
|
||||||
// Permitir montos negativos para disminuir deuda o positivos para aumentarla
|
// Permitir montos negativos para disminuir deuda o positivos para aumentarla
|
||||||
// No se usa Range aquí para permitir ambos signos. La validación de que no sea cero se puede hacer en el servicio.
|
// No se usa Range aquí para permitir ambos signos. La validación de que no sea cero se puede hacer en el servicio.
|
||||||
public decimal MontoAjuste { get; set; }
|
public decimal MontoAjuste { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "La justificación del ajuste es obligatoria.")]
|
[Required(ErrorMessage = "La justificación del ajuste es obligatoria.")]
|
||||||
[StringLength(250, MinimumLength = 5, ErrorMessage = "La justificación debe tener entre 5 y 250 caracteres.")]
|
[StringLength(250, MinimumLength = 5, ErrorMessage = "La justificación debe tener entre 5 y 250 caracteres.")]
|
||||||
public string Justificacion { get; set; } = string.Empty;
|
public string Justificacion { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// Fecha lógica de la operación. Se valida contra el último cierre vigente
|
||||||
|
// del par (Distribuidor + Empresa) para bloquear ajustes en períodos cerrados.
|
||||||
|
// Distinta de FechaAjuste, que es el momento de ejecución del ajuste en el sistema.
|
||||||
|
[Required(ErrorMessage = "La fecha de operación es obligatoria.")]
|
||||||
|
public DateTime FechaOperacion { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
|
{
|
||||||
|
public class CierreCuentaCorrienteDto
|
||||||
|
{
|
||||||
|
public int IdCierre { get; set; }
|
||||||
|
public int IdDistribuidor { get; set; }
|
||||||
|
public string NombreDistribuidor { get; set; } = string.Empty;
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
public string NombreEmpresa { get; set; } = string.Empty;
|
||||||
|
public string FechaCorte { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public DateTime FechaCierre { get; set; }
|
||||||
|
public decimal SaldoCierre { get; set; }
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
public string? Justificacion { get; set; }
|
||||||
|
public int IdUsuarioCierre { get; set; }
|
||||||
|
public string NombreUsuarioCierre { get; set; } = string.Empty;
|
||||||
|
public int? IdUsuarioAnula { get; set; }
|
||||||
|
public string? NombreUsuarioAnula { get; set; }
|
||||||
|
public DateTime? FechaAnulacion { get; set; }
|
||||||
|
public string? JustificacionAnulacion { get; set; }
|
||||||
|
public bool EsUltimoVigente { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
|
{
|
||||||
|
public class CrearCierreDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "El distribuidor es obligatorio.")]
|
||||||
|
[Range(1, int.MaxValue, ErrorMessage = "ID de Distribuidor inválido.")]
|
||||||
|
public int IdDistribuidor { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La empresa es obligatoria.")]
|
||||||
|
[Range(1, int.MaxValue, ErrorMessage = "ID de Empresa inválido.")]
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La fecha de corte es obligatoria.")]
|
||||||
|
public DateTime FechaCorte { get; set; }
|
||||||
|
|
||||||
|
[StringLength(500, ErrorMessage = "La justificación no puede superar los 500 caracteres.")]
|
||||||
|
public string? Justificacion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
|
{
|
||||||
|
public class ReabrirCierreDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "La justificación es obligatoria al reabrir un cierre.")]
|
||||||
|
[StringLength(500, MinimumLength = 10, ErrorMessage = "La justificación debe tener entre 10 y 500 caracteres.")]
|
||||||
|
public string Justificacion { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
|
{
|
||||||
|
public class UltimoCierreDto
|
||||||
|
{
|
||||||
|
public int IdCierre { get; set; }
|
||||||
|
public string FechaCorte { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public decimal SaldoCierre { get; set; }
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,5 +15,7 @@ namespace GestionIntegral.Api.Dtos.Distribucion
|
|||||||
public string? Telefono { get; set; }
|
public string? Telefono { get; set; }
|
||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
public string? Localidad { get; set; }
|
public string? Localidad { get; set; }
|
||||||
|
public bool Baja { get; set; }
|
||||||
|
public DateTime? FechaBaja { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class ToggleBajaDistribuidorDto
|
||||||
|
{
|
||||||
|
public bool DarDeBaja { get; set; }
|
||||||
|
public DateTime? FechaBaja { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Backend/GestionIntegral.Api/Models/Dtos/Impresion/BobinaLoteDetalleDto.cs
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Impresion
|
||||||
|
{
|
||||||
|
public class BobinaLoteDetalleDto
|
||||||
|
{
|
||||||
|
public int IdTipoBobina { get; set; }
|
||||||
|
public string NroBobina { get; set; } = string.Empty;
|
||||||
|
public int Peso { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Backend/GestionIntegral.Api/Models/Dtos/Impresion/CreateStockBobinaLoteDto.cs
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Impresion
|
||||||
|
{
|
||||||
|
public class CreateStockBobinaLoteDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdPlanta { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(15)]
|
||||||
|
public string Remito { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public DateTime FechaRemito { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MinLength(1, ErrorMessage = "Debe ingresar al menos una bobina.")]
|
||||||
|
public List<BobinaLoteDetalleDto> Bobinas { get; set; } = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Backend/GestionIntegral.Api/Models/Dtos/Impresion/UpdateFechaRemitoLoteDto.cs
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Impresion
|
||||||
|
{
|
||||||
|
public class UpdateFechaRemitoLoteDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdPlanta { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public required string Remito { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public DateTime FechaRemitoActual { get; set; } // Para seguridad, nos aseguramos de cambiar el lote correcto
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public DateTime NuevaFechaRemito { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Reportes
|
||||||
|
{
|
||||||
|
public class DistribucionSuscripcionDto
|
||||||
|
{
|
||||||
|
public string NombreEmpresa { get; set; } = string.Empty;
|
||||||
|
public string NombrePublicacion { get; set; } = string.Empty;
|
||||||
|
public string NombreSuscriptor { get; set; } = string.Empty;
|
||||||
|
public string Direccion { get; set; } = string.Empty;
|
||||||
|
public string? Telefono { get; set; }
|
||||||
|
public DateTime FechaInicio { get; set; }
|
||||||
|
public DateTime? FechaFin { get; set; }
|
||||||
|
public string DiasEntrega { get; set; } = string.Empty;
|
||||||
|
public string? Observaciones { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Reportes
|
||||||
|
{
|
||||||
|
public class FacturasParaReporteDto
|
||||||
|
{
|
||||||
|
public int IdFactura { get; set; }
|
||||||
|
public string Periodo { get; set; } = string.Empty;
|
||||||
|
public string NombreSuscriptor { get; set; } = string.Empty;
|
||||||
|
public string TipoDocumento { get; set; } = string.Empty;
|
||||||
|
public string NroDocumento { get; set; } = string.Empty;
|
||||||
|
public decimal ImporteFinal { get; set; }
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
public string NombreEmpresa { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,12 @@ namespace GestionIntegral.Api.Dtos.Reportes
|
|||||||
public IEnumerable<BalanceCuentaDistDto> EntradasSalidas { get; set; } = new List<BalanceCuentaDistDto>();
|
public IEnumerable<BalanceCuentaDistDto> EntradasSalidas { get; set; } = new List<BalanceCuentaDistDto>();
|
||||||
public IEnumerable<BalanceCuentaDebCredDto> DebitosCreditos { get; set; } = new List<BalanceCuentaDebCredDto>();
|
public IEnumerable<BalanceCuentaDebCredDto> DebitosCreditos { get; set; } = new List<BalanceCuentaDebCredDto>();
|
||||||
public IEnumerable<BalanceCuentaPagosDto> Pagos { get; set; } = new List<BalanceCuentaPagosDto>();
|
public IEnumerable<BalanceCuentaPagosDto> Pagos { get; set; } = new List<BalanceCuentaPagosDto>();
|
||||||
public IEnumerable<SaldoDto> Saldos { get; set; } = new List<SaldoDto>(); // O podría ser SaldoDto SaldoActual si siempre es uno
|
public string? NombreDistribuidor { get; set; }
|
||||||
public string? NombreDistribuidor { get; set; } // Para el título del reporte
|
public string? NombreEmpresa { get; set; }
|
||||||
public string? NombreEmpresa { get; set; } // Para el título del reporte
|
|
||||||
|
// Saldo a la fecha desde elegida en el filtro:
|
||||||
|
// - Si existe cierre con FechaCorte < FechaDesde: SaldoCierre + movimientos netos entre fechaCierre+1 y fechaDesde-1.
|
||||||
|
// - Sin cierres previos: 0.
|
||||||
|
public decimal SaldoInicial { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,9 +10,10 @@ namespace GestionIntegral.Api.Dtos.Reportes.ViewModels
|
|||||||
public IEnumerable<BalanceCuentaDistDto> Movimientos { get; set; } = new List<BalanceCuentaDistDto>();
|
public IEnumerable<BalanceCuentaDistDto> Movimientos { get; set; } = new List<BalanceCuentaDistDto>();
|
||||||
public IEnumerable<BalanceCuentaPagosDto> Pagos { get; set; } = new List<BalanceCuentaPagosDto>();
|
public IEnumerable<BalanceCuentaPagosDto> Pagos { get; set; } = new List<BalanceCuentaPagosDto>();
|
||||||
public IEnumerable<BalanceCuentaDebCredDto> DebitosCreditos { get; set; } = new List<BalanceCuentaDebCredDto>();
|
public IEnumerable<BalanceCuentaDebCredDto> DebitosCreditos { get; set; } = new List<BalanceCuentaDebCredDto>();
|
||||||
|
|
||||||
// Saldo real de la cuenta, se muestra al final sin usarse en cálculos intermedios.
|
// Saldo inicial del período: snapshot del último cierre + movimientos netos hasta fechaDesde.
|
||||||
public decimal SaldoDeCuenta { get; set; }
|
// 0 si no hay cierre previo.
|
||||||
|
public decimal SaldoInicial { get; set; }
|
||||||
|
|
||||||
// --- Parámetros del reporte ---
|
// --- Parámetros del reporte ---
|
||||||
public string NombreDistribuidor { get; set; } = string.Empty;
|
public string NombreDistribuidor { get; set; } = string.Empty;
|
||||||
@@ -24,6 +25,8 @@ namespace GestionIntegral.Api.Dtos.Reportes.ViewModels
|
|||||||
public decimal TotalMovimientos => Movimientos.Sum(m => m.Debe - m.Haber);
|
public decimal TotalMovimientos => Movimientos.Sum(m => m.Debe - m.Haber);
|
||||||
public decimal TotalPagos => Pagos.Sum(p => p.Debe - p.Haber);
|
public decimal TotalPagos => Pagos.Sum(p => p.Debe - p.Haber);
|
||||||
public decimal TotalDebitosCreditos => DebitosCreditos.Sum(d => d.Debe - d.Haber);
|
public decimal TotalDebitosCreditos => DebitosCreditos.Sum(d => d.Debe - d.Haber);
|
||||||
public decimal TotalPeriodo => TotalMovimientos + TotalPagos + TotalDebitosCreditos;
|
|
||||||
|
// Saldo Final = Saldo Inicial + suma neta del período (Debe - Haber por sección).
|
||||||
|
public decimal SaldoFinal => SaldoInicial + TotalMovimientos + TotalPagos + TotalDebitosCreditos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Reportes.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Representa una agrupación de suscripciones por publicación para el reporte.
|
||||||
|
/// </summary>
|
||||||
|
public class GrupoPublicacion
|
||||||
|
{
|
||||||
|
public string NombrePublicacion { get; set; } = string.Empty;
|
||||||
|
public IEnumerable<DistribucionSuscripcionDto> Suscripciones { get; set; } = Enumerable.Empty<DistribucionSuscripcionDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Representa una agrupación de publicaciones por empresa para el reporte.
|
||||||
|
/// </summary>
|
||||||
|
public class GrupoEmpresa
|
||||||
|
{
|
||||||
|
public string NombreEmpresa { get; set; } = string.Empty;
|
||||||
|
public IEnumerable<GrupoPublicacion> Publicaciones { get; set; } = Enumerable.Empty<GrupoPublicacion>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DistribucionSuscripcionesViewModel
|
||||||
|
{
|
||||||
|
public IEnumerable<GrupoEmpresa> DatosAgrupadosAltas { get; }
|
||||||
|
public IEnumerable<GrupoEmpresa> DatosAgrupadosBajas { get; }
|
||||||
|
public string FechaDesde { get; set; } = string.Empty;
|
||||||
|
public string FechaHasta { get; set; } = string.Empty;
|
||||||
|
public string FechaGeneracion { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public DistribucionSuscripcionesViewModel(IEnumerable<DistribucionSuscripcionDto> altas, IEnumerable<DistribucionSuscripcionDto> bajas)
|
||||||
|
{
|
||||||
|
// Función local para evitar repetir el código de agrupación
|
||||||
|
Func<IEnumerable<DistribucionSuscripcionDto>, IEnumerable<GrupoEmpresa>> agruparDatos = (suscripciones) =>
|
||||||
|
{
|
||||||
|
return suscripciones
|
||||||
|
.GroupBy(s => s.NombreEmpresa)
|
||||||
|
.Select(gEmpresa => new GrupoEmpresa
|
||||||
|
{
|
||||||
|
NombreEmpresa = gEmpresa.Key,
|
||||||
|
Publicaciones = gEmpresa
|
||||||
|
.GroupBy(s => s.NombrePublicacion)
|
||||||
|
.Select(gPub => new GrupoPublicacion
|
||||||
|
{
|
||||||
|
NombrePublicacion = gPub.Key,
|
||||||
|
Suscripciones = gPub.OrderBy(s => s.NombreSuscriptor).ToList()
|
||||||
|
})
|
||||||
|
.OrderBy(p => p.NombrePublicacion)
|
||||||
|
})
|
||||||
|
.OrderBy(e => e.NombreEmpresa);
|
||||||
|
};
|
||||||
|
|
||||||
|
DatosAgrupadosAltas = agruparDatos(altas);
|
||||||
|
DatosAgrupadosBajas = agruparDatos(bajas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Reportes.ViewModels
|
||||||
|
{
|
||||||
|
// Esta clase anidada representará los datos de una empresa
|
||||||
|
public class DatosEmpresaViewModel
|
||||||
|
{
|
||||||
|
public string NombreEmpresa { get; set; } = string.Empty;
|
||||||
|
public IEnumerable<FacturasParaReporteDto> Facturas { get; set; } = new List<FacturasParaReporteDto>();
|
||||||
|
public decimal TotalEmpresa => Facturas.Sum(f => f.ImporteFinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FacturasPublicidadViewModel
|
||||||
|
{
|
||||||
|
public IEnumerable<DatosEmpresaViewModel> DatosPorEmpresa { get; set; } = new List<DatosEmpresaViewModel>();
|
||||||
|
public string Periodo { get; set; } = string.Empty;
|
||||||
|
public string FechaGeneracion { get; set; } = string.Empty;
|
||||||
|
public decimal TotalGeneral => DatosPorEmpresa.Sum(e => e.TotalEmpresa);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ namespace GestionIntegral.Api.Dtos.Reportes.ViewModels
|
|||||||
public string FechaDesde { get; set; } = string.Empty;
|
public string FechaDesde { get; set; } = string.Empty;
|
||||||
public string FechaHasta { get; set; } = string.Empty;
|
public string FechaHasta { get; set; } = string.Empty;
|
||||||
public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy");
|
public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy");
|
||||||
|
|
||||||
public ListadoDistribucionCanillasSimpleDto TotalesDetalleDiario
|
public ListadoDistribucionCanillasSimpleDto TotalesDetalleDiario
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -29,7 +29,23 @@ namespace GestionIntegral.Api.Dtos.Reportes.ViewModels
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public decimal PorcentajeDevolucionGeneral
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (PromediosPorDia == null || !PromediosPorDia.Any()) return 0;
|
||||||
|
|
||||||
|
var totalPonderadoLlevados = PromediosPorDia.Sum(p => p.Promedio_Llevados * p.Cant);
|
||||||
|
var totalPonderadoDevueltos = PromediosPorDia.Sum(p => p.Promedio_Devueltos * p.Cant);
|
||||||
|
|
||||||
|
if (totalPonderadoLlevados == 0) return 0;
|
||||||
|
|
||||||
|
// Calculamos el porcentaje usando los totales ponderados para máxima precisión como lo hace el frontend.
|
||||||
|
return (decimal)totalPonderadoDevueltos * 100 / totalPonderadoLlevados;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- PROPIEDAD PARA LA FILA "GENERAL" ---
|
// --- PROPIEDAD PARA LA FILA "GENERAL" ---
|
||||||
public ListadoDistribucionCanillasPromedioDiaDto? PromedioGeneral
|
public ListadoDistribucionCanillasPromedioDiaDto? PromedioGeneral
|
||||||
{
|
{
|
||||||
@@ -37,20 +53,27 @@ namespace GestionIntegral.Api.Dtos.Reportes.ViewModels
|
|||||||
{
|
{
|
||||||
if (PromediosPorDia == null || !PromediosPorDia.Any()) return null;
|
if (PromediosPorDia == null || !PromediosPorDia.Any()) return null;
|
||||||
|
|
||||||
// Sumamos los totales, no promediamos los promedios
|
// Sumamos los totales ponderados para cada columna
|
||||||
var totalLlevados = PromediosPorDia.Sum(p => p.Llevados);
|
var totalPonderadoLlevados = PromediosPorDia.Sum(p => p.Promedio_Llevados * p.Cant);
|
||||||
var totalDevueltos = PromediosPorDia.Sum(p => p.Devueltos);
|
var totalPonderadoDevueltos = PromediosPorDia.Sum(p => p.Promedio_Devueltos * p.Cant);
|
||||||
|
var totalPonderadoVentas = PromediosPorDia.Sum(p => p.Promedio_Ventas * p.Cant);
|
||||||
var totalDias = PromediosPorDia.Sum(p => p.Cant);
|
var totalDias = PromediosPorDia.Sum(p => p.Cant);
|
||||||
|
|
||||||
if (totalDias == 0) return null;
|
if (totalDias == 0) return null;
|
||||||
|
|
||||||
|
// Usamos Math.Round para un redondeo matemático estándar antes de la conversión.
|
||||||
|
// MidpointRounding.AwayFromZero asegura que .5 se redondee hacia arriba, igual que en JavaScript.
|
||||||
|
var promGeneralLlevados = (int)Math.Round((decimal)totalPonderadoLlevados / totalDias, MidpointRounding.AwayFromZero);
|
||||||
|
var promGeneralDevueltos = (int)Math.Round((decimal)totalPonderadoDevueltos / totalDias, MidpointRounding.AwayFromZero);
|
||||||
|
var promGeneralVentas = (int)Math.Round((decimal)totalPonderadoVentas / totalDias, MidpointRounding.AwayFromZero);
|
||||||
|
|
||||||
return new ListadoDistribucionCanillasPromedioDiaDto
|
return new ListadoDistribucionCanillasPromedioDiaDto
|
||||||
{
|
{
|
||||||
Dia = "General",
|
Dia = "General",
|
||||||
Cant = totalDias,
|
Cant = totalDias,
|
||||||
Promedio_Llevados = totalLlevados / totalDias,
|
Promedio_Llevados = promGeneralLlevados,
|
||||||
Promedio_Devueltos = totalDevueltos / totalDias,
|
Promedio_Devueltos = promGeneralDevueltos,
|
||||||
Promedio_Ventas = (totalLlevados - totalDevueltos) / totalDias
|
Promedio_Ventas = promGeneralVentas
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,26 +20,26 @@ namespace GestionIntegral.Api.Dtos.Reportes.ViewModels
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (DetalleDiario == null || !DetalleDiario.Any())
|
if (PromediosPorDia == null || !PromediosPorDia.Any())
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
var promediosValidos = PromediosPorDia.Where(p => p.Dia != "General").ToList();
|
||||||
var diasConDatos = DetalleDiario.Count(d => (d.Llevados ?? 0) > 0);
|
if (!promediosValidos.Any()) return null;
|
||||||
if (diasConDatos == 0) return null;
|
var countPromedios = promediosValidos.Count;
|
||||||
|
var sumPromLlevados = promediosValidos.Sum(p => p.Promedio_Llevados ?? 0);
|
||||||
var totalLlevados = DetalleDiario.Sum(d => d.Llevados ?? 0);
|
var sumPromDevueltos = promediosValidos.Sum(p => p.Promedio_Devueltos ?? 0);
|
||||||
var totalDevueltos = DetalleDiario.Sum(d => d.Devueltos ?? 0);
|
var sumPromVentas = promediosValidos.Sum(p => p.Promedio_Ventas ?? 0);
|
||||||
|
|
||||||
return new ListadoDistribucionDistPromedioDiaDto
|
return new ListadoDistribucionDistPromedioDiaDto
|
||||||
{
|
{
|
||||||
Dia = "General",
|
Dia = "General",
|
||||||
Cant = diasConDatos,
|
Cant = promediosValidos.Sum(p => p.Cant ?? 0),
|
||||||
Promedio_Llevados = totalLlevados / diasConDatos,
|
Promedio_Llevados = (int)Math.Round((decimal)sumPromLlevados / countPromedios, MidpointRounding.AwayFromZero),
|
||||||
Promedio_Devueltos = totalDevueltos / diasConDatos,
|
Promedio_Devueltos = (int)Math.Round((decimal)sumPromDevueltos / countPromedios, MidpointRounding.AwayFromZero),
|
||||||
Promedio_Ventas = (totalLlevados - totalDevueltos) / diasConDatos,
|
Promedio_Ventas = (int)Math.Round((decimal)sumPromVentas / countPromedios, MidpointRounding.AwayFromZero),
|
||||||
Llevados = totalLlevados, // Guardamos el total para el cálculo del %
|
Llevados = (int)Math.Round((decimal)sumPromLlevados / countPromedios, MidpointRounding.AwayFromZero),
|
||||||
Devueltos = totalDevueltos // Guardamos el total para el cálculo del %
|
Devueltos = (int)Math.Round((decimal)sumPromDevueltos / countPromedios, MidpointRounding.AwayFromZero)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Suscripciones
|
||||||
|
{
|
||||||
|
public class AjusteDto
|
||||||
|
{
|
||||||
|
public int IdAjuste { get; set; }
|
||||||
|
public int IdSuscriptor { get; set; }
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
public string? NombreEmpresa { get; set; }
|
||||||
|
public string FechaAjuste { get; set; } = string.Empty;
|
||||||
|
public string TipoAjuste { get; set; } = string.Empty;
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
public string Motivo { get; set; } = string.Empty;
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
public int? IdFacturaAplicado { get; set; }
|
||||||
|
public string? NumeroFacturaAplicado { get; set; }
|
||||||
|
public string FechaAlta { get; set; } = string.Empty;
|
||||||
|
public string NombreUsuarioAlta { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Suscripciones
|
||||||
|
{
|
||||||
|
public class AsignarPromocionDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdPromocion { get; set; }
|
||||||
|
[Required]
|
||||||
|
public DateTime VigenciaDesde { get; set; }
|
||||||
|
public DateTime? VigenciaHasta { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Suscripciones
|
||||||
|
{
|
||||||
|
public class CreateAjusteDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdSuscriptor { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public DateTime FechaAjuste { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[RegularExpression("^(Credito|Debito)$", ErrorMessage = "El tipo de ajuste debe ser 'Credito' o 'Debito'.")]
|
||||||
|
public string TipoAjuste { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[Range(0.01, 999999.99, ErrorMessage = "El monto debe ser un valor positivo.")]
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El motivo es obligatorio.")]
|
||||||
|
[StringLength(250)]
|
||||||
|
public string Motivo { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,22 +7,25 @@ namespace GestionIntegral.Api.Dtos.Suscripciones
|
|||||||
{
|
{
|
||||||
public class CreatePromocionDto
|
public class CreatePromocionDto
|
||||||
{
|
{
|
||||||
[Required(ErrorMessage = "La descripción es obligatoria.")]
|
[Required]
|
||||||
[StringLength(200)]
|
[StringLength(200)]
|
||||||
public string Descripcion { get; set; } = string.Empty;
|
public string Descripcion { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required(ErrorMessage = "El tipo de promoción es obligatorio.")]
|
[Required]
|
||||||
public string TipoPromocion { get; set; } = string.Empty;
|
public string TipoEfecto { get; set; } = string.Empty; // Corregido
|
||||||
|
|
||||||
[Required(ErrorMessage = "El valor es obligatorio.")]
|
[Required]
|
||||||
[Range(0.01, 99999999.99, ErrorMessage = "El valor debe ser positivo.")]
|
[Range(0, 99999999.99)] // Se permite 0 para bonificaciones
|
||||||
public decimal Valor { get; set; }
|
public decimal ValorEfecto { get; set; } // Corregido
|
||||||
|
|
||||||
[Required(ErrorMessage = "La fecha de inicio es obligatoria.")]
|
[Required]
|
||||||
public DateTime FechaInicio { get; set; }
|
public string TipoCondicion { get; set; } = string.Empty;
|
||||||
|
|
||||||
public DateTime? FechaFin { get; set; }
|
|
||||||
|
|
||||||
|
public int? ValorCondicion { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public DateTime FechaInicio { get; set; }
|
||||||
|
public DateTime? FechaFin { get; set; }
|
||||||
public bool Activa { get; set; } = true;
|
public bool Activa { get; set; } = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Archivo: GestionIntegral.Api/Dtos/Suscripciones/CreateSuscriptorDto.cs
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Dtos.Suscripciones
|
namespace GestionIntegral.Api.Dtos.Suscripciones
|
||||||
@@ -13,6 +15,7 @@ namespace GestionIntegral.Api.Dtos.Suscripciones
|
|||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
|
|
||||||
[StringLength(50)]
|
[StringLength(50)]
|
||||||
|
[RegularExpression(@"^[0-9\s\+\-\(\)]*$", ErrorMessage = "El teléfono solo puede contener números y los símbolos +, -, () y espacios.")]
|
||||||
public string? Telefono { get; set; }
|
public string? Telefono { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "La dirección es obligatoria.")]
|
[Required(ErrorMessage = "La dirección es obligatoria.")]
|
||||||
@@ -25,9 +28,11 @@ namespace GestionIntegral.Api.Dtos.Suscripciones
|
|||||||
|
|
||||||
[Required(ErrorMessage = "El número de documento es obligatorio.")]
|
[Required(ErrorMessage = "El número de documento es obligatorio.")]
|
||||||
[StringLength(11)]
|
[StringLength(11)]
|
||||||
|
[RegularExpression("^[0-9]*$", ErrorMessage = "El número de documento solo puede contener números.")]
|
||||||
public string NroDocumento { get; set; } = string.Empty;
|
public string NroDocumento { get; set; } = string.Empty;
|
||||||
|
|
||||||
[StringLength(22, MinimumLength = 22, ErrorMessage = "El CBU debe tener 22 dígitos.")]
|
[StringLength(22, MinimumLength = 22, ErrorMessage = "El CBU debe tener 22 dígitos.")]
|
||||||
|
[RegularExpression("^[0-9]*$", ErrorMessage = "El CBU solo puede contener números.")]
|
||||||
public string? CBU { get; set; }
|
public string? CBU { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "La forma de pago es obligatoria.")]
|
[Required(ErrorMessage = "La forma de pago es obligatoria.")]
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Suscripciones
|
||||||
|
{
|
||||||
|
public class FacturaConsolidadaDto
|
||||||
|
{
|
||||||
|
public int IdFactura { get; set; }
|
||||||
|
public string NombreEmpresa { get; set; } = string.Empty;
|
||||||
|
public decimal ImporteFinal { get; set; }
|
||||||
|
public string EstadoPago { get; set; } = string.Empty;
|
||||||
|
public string EstadoFacturacion { get; set; } = string.Empty;
|
||||||
|
public string? NumeroFactura { get; set; }
|
||||||
|
public decimal TotalPagado { get; set; }
|
||||||
|
public string TipoFactura { get; set; } = string.Empty;
|
||||||
|
public int IdSuscriptor { get; set; }
|
||||||
|
public List<FacturaDetalleDto> Detalles { get; set; } = new List<FacturaDetalleDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
namespace GestionIntegral.Api.Dtos.Suscripciones
|
namespace GestionIntegral.Api.Dtos.Suscripciones
|
||||||
{
|
{
|
||||||
/// <summary>
|
public class FacturaDetalleDto
|
||||||
/// DTO para enviar la información de una factura generada al frontend.
|
{
|
||||||
/// Incluye datos enriquecidos como nombres para facilitar su visualización en la UI.
|
public string Descripcion { get; set; } = string.Empty;
|
||||||
/// </summary>
|
public decimal ImporteNeto { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class FacturaDto
|
public class FacturaDto
|
||||||
{
|
{
|
||||||
public int IdFactura { get; set; }
|
public int IdFactura { get; set; }
|
||||||
public int IdSuscripcion { get; set; }
|
public int IdSuscriptor { get; set; }
|
||||||
public string Periodo { get; set; } = string.Empty; // Formato "YYYY-MM"
|
public string Periodo { get; set; } = string.Empty;
|
||||||
public string FechaEmision { get; set; } = string.Empty; // Formato "yyyy-MM-dd"
|
public string FechaEmision { get; set; } = string.Empty;
|
||||||
public string FechaVencimiento { get; set; } = string.Empty; // Formato "yyyy-MM-dd"
|
public string FechaVencimiento { get; set; } = string.Empty;
|
||||||
public decimal ImporteFinal { get; set; }
|
public decimal ImporteFinal { get; set; }
|
||||||
public string Estado { get; set; } = string.Empty;
|
public decimal TotalPagado { get; set; }
|
||||||
|
public decimal SaldoPendiente { get; set; }
|
||||||
|
public string EstadoPago { get; set; } = string.Empty;
|
||||||
|
public string EstadoFacturacion { get; set; } = string.Empty;
|
||||||
public string? NumeroFactura { get; set; }
|
public string? NumeroFactura { get; set; }
|
||||||
|
|
||||||
// Datos enriquecidos para la UI, poblados por el servicio
|
|
||||||
public string NombreSuscriptor { get; set; } = string.Empty;
|
public string NombreSuscriptor { get; set; } = string.Empty;
|
||||||
public string NombrePublicacion { get; set; } = string.Empty;
|
public List<FacturaDetalleDto> Detalles { get; set; } = new List<FacturaDetalleDto>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Suscripciones
|
||||||
|
{
|
||||||
|
public class PromocionAsignadaDto : PromocionDto
|
||||||
|
{
|
||||||
|
public string VigenciaDesdeAsignacion { get; set; } = string.Empty;
|
||||||
|
public string? VigenciaHastaAsignacion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,11 @@ namespace GestionIntegral.Api.Dtos.Suscripciones
|
|||||||
{
|
{
|
||||||
public int IdPromocion { get; set; }
|
public int IdPromocion { get; set; }
|
||||||
public string Descripcion { get; set; } = string.Empty;
|
public string Descripcion { get; set; } = string.Empty;
|
||||||
public string TipoPromocion { get; set; } = string.Empty;
|
public string TipoEfecto { get; set; } = string.Empty;
|
||||||
public decimal Valor { get; set; }
|
public decimal ValorEfecto { get; set; }
|
||||||
public string FechaInicio { get; set; } = string.Empty; // yyyy-MM-dd
|
public string TipoCondicion { get; set; } = string.Empty;
|
||||||
|
public int? ValorCondicion { get; set; }
|
||||||
|
public string FechaInicio { get; set; } = string.Empty;
|
||||||
public string? FechaFin { get; set; }
|
public string? FechaFin { get; set; }
|
||||||
public bool Activa { get; set; }
|
public bool Activa { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Suscripciones
|
||||||
|
{
|
||||||
|
public class ResumenCuentaSuscriptorDto
|
||||||
|
{
|
||||||
|
public int IdSuscriptor { get; set; }
|
||||||
|
public string NombreSuscriptor { get; set; } = string.Empty;
|
||||||
|
public decimal SaldoPendienteTotal { get; set; }
|
||||||
|
public decimal ImporteTotal { get; set; }
|
||||||
|
public List<FacturaConsolidadaDto> Facturas { get; set; } = new List<FacturaConsolidadaDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Suscripciones;
|
||||||
|
public class UpdateAjusteDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public DateTime FechaAjuste { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[RegularExpression("^(Credito|Debito)$")]
|
||||||
|
public string TipoAjuste { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[Range(0.01, 999999.99)]
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[StringLength(250)]
|
||||||
|
public string Motivo { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
|
|
||||||
namespace GestionIntegral.Api.Dtos.Suscripciones
|
namespace GestionIntegral.Api.Dtos.Suscripciones
|
||||||
{
|
{
|
||||||
// Es idéntico al CreateDto, pero se mantiene separado por si las reglas de validación cambian.
|
|
||||||
public class UpdateSuscriptorDto
|
public class UpdateSuscriptorDto
|
||||||
{
|
{
|
||||||
[Required(ErrorMessage = "El nombre completo es obligatorio.")]
|
[Required(ErrorMessage = "El nombre completo es obligatorio.")]
|
||||||
@@ -14,6 +13,7 @@ namespace GestionIntegral.Api.Dtos.Suscripciones
|
|||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
|
|
||||||
[StringLength(50)]
|
[StringLength(50)]
|
||||||
|
[RegularExpression(@"^[0-9\s\+\-\(\)]*$", ErrorMessage = "El teléfono solo puede contener números y los símbolos +, -, () y espacios.")]
|
||||||
public string? Telefono { get; set; }
|
public string? Telefono { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "La dirección es obligatoria.")]
|
[Required(ErrorMessage = "La dirección es obligatoria.")]
|
||||||
@@ -26,9 +26,11 @@ namespace GestionIntegral.Api.Dtos.Suscripciones
|
|||||||
|
|
||||||
[Required(ErrorMessage = "El número de documento es obligatorio.")]
|
[Required(ErrorMessage = "El número de documento es obligatorio.")]
|
||||||
[StringLength(11)]
|
[StringLength(11)]
|
||||||
|
[RegularExpression("^[0-9]*$", ErrorMessage = "El número de documento solo puede contener números.")]
|
||||||
public string NroDocumento { get; set; } = string.Empty;
|
public string NroDocumento { get; set; } = string.Empty;
|
||||||
|
|
||||||
[StringLength(22, MinimumLength = 22, ErrorMessage = "El CBU debe tener 22 dígitos.")]
|
[StringLength(22, MinimumLength = 22, ErrorMessage = "El CBU debe tener 22 dígitos.")]
|
||||||
|
[RegularExpression("^[0-9]*$", ErrorMessage = "El CBU solo puede contener números.")]
|
||||||
public string? CBU { get; set; }
|
public string? CBU { get; set; }
|
||||||
|
|
||||||
[Required(ErrorMessage = "La forma de pago es obligatoria.")]
|
[Required(ErrorMessage = "La forma de pago es obligatoria.")]
|
||||||
|
|||||||
19
Backend/GestionIntegral.Api/Models/Suscripciones/Ajuste.cs
Normal file
19
Backend/GestionIntegral.Api/Models/Suscripciones/Ajuste.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace GestionIntegral.Api.Models.Suscripciones
|
||||||
|
{
|
||||||
|
public class Ajuste
|
||||||
|
{
|
||||||
|
public int IdAjuste { get; set; }
|
||||||
|
public int IdSuscriptor { get; set; }
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
public DateTime FechaAjuste { get; set; }
|
||||||
|
public string TipoAjuste { get; set; } = string.Empty;
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
public string Motivo { get; set; } = string.Empty;
|
||||||
|
public string Estado { get; set; } = string.Empty;
|
||||||
|
public int? IdFacturaAplicado { get; set; }
|
||||||
|
public int IdUsuarioAlta { get; set; }
|
||||||
|
public DateTime FechaAlta { get; set; }
|
||||||
|
public int? IdUsuarioAnulo { get; set; }
|
||||||
|
public DateTime? FechaAnulacion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,16 +3,18 @@ namespace GestionIntegral.Api.Models.Suscripciones
|
|||||||
public class Factura
|
public class Factura
|
||||||
{
|
{
|
||||||
public int IdFactura { get; set; }
|
public int IdFactura { get; set; }
|
||||||
public int IdSuscripcion { get; set; }
|
public int IdSuscriptor { get; set; }
|
||||||
public string Periodo { get; set; } = string.Empty;
|
public string Periodo { get; set; } = string.Empty;
|
||||||
public DateTime FechaEmision { get; set; }
|
public DateTime FechaEmision { get; set; }
|
||||||
public DateTime FechaVencimiento { get; set; }
|
public DateTime FechaVencimiento { get; set; }
|
||||||
public decimal ImporteBruto { get; set; }
|
public decimal ImporteBruto { get; set; }
|
||||||
public decimal DescuentoAplicado { get; set; }
|
public decimal DescuentoAplicado { get; set; }
|
||||||
public decimal ImporteFinal { get; set; }
|
public decimal ImporteFinal { get; set; }
|
||||||
public string Estado { get; set; } = string.Empty;
|
public string EstadoPago { get; set; } = string.Empty;
|
||||||
|
public string EstadoFacturacion { get; set; } = string.Empty;
|
||||||
public string? NumeroFactura { get; set; }
|
public string? NumeroFactura { get; set; }
|
||||||
public int? IdLoteDebito { get; set; }
|
public int? IdLoteDebito { get; set; }
|
||||||
public string? MotivoRechazo { get; set; }
|
public string? MotivoRechazo { get; set; }
|
||||||
|
public string TipoFactura { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
public class FacturaDetalle {
|
||||||
|
public int IdFacturaDetalle { get; set; }
|
||||||
|
public int IdFactura { get; set; }
|
||||||
|
public int IdSuscripcion { get; set; }
|
||||||
|
public string Descripcion { get; set; } = string.Empty;
|
||||||
|
public decimal ImporteBruto { get; set; }
|
||||||
|
public decimal DescuentoAplicado { get; set; }
|
||||||
|
public decimal ImporteNeto { get; set; }
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ namespace GestionIntegral.Api.Models.Suscripciones
|
|||||||
{
|
{
|
||||||
public int IdPromocion { get; set; }
|
public int IdPromocion { get; set; }
|
||||||
public string Descripcion { get; set; } = string.Empty;
|
public string Descripcion { get; set; } = string.Empty;
|
||||||
public string TipoPromocion { get; set; } = string.Empty;
|
public string TipoEfecto { get; set; } = string.Empty; // Nuevo nombre
|
||||||
public decimal Valor { get; set; }
|
public decimal ValorEfecto { get; set; } // Nuevo nombre
|
||||||
|
public string TipoCondicion { get; set; } = string.Empty; // Nueva propiedad
|
||||||
|
public int? ValorCondicion { get; set; } // Nueva propiedad (nullable)
|
||||||
public DateTime FechaInicio { get; set; }
|
public DateTime FechaInicio { get; set; }
|
||||||
public DateTime? FechaFin { get; set; }
|
public DateTime? FechaFin { get; set; }
|
||||||
public bool Activa { get; set; }
|
public bool Activa { get; set; }
|
||||||
|
|||||||
@@ -6,5 +6,7 @@ namespace GestionIntegral.Api.Models.Suscripciones
|
|||||||
public int IdPromocion { get; set; }
|
public int IdPromocion { get; set; }
|
||||||
public DateTime FechaAsignacion { get; set; }
|
public DateTime FechaAsignacion { get; set; }
|
||||||
public int IdUsuarioAsigno { get; set; }
|
public int IdUsuarioAsigno { get; set; }
|
||||||
|
public DateTime VigenciaDesde { get; set; }
|
||||||
|
public DateTime? VigenciaHasta { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,8 @@ using GestionIntegral.Api.Data.Repositories.Suscripciones;
|
|||||||
using GestionIntegral.Api.Services.Suscripciones;
|
using GestionIntegral.Api.Services.Suscripciones;
|
||||||
using GestionIntegral.Api.Models.Comunicaciones;
|
using GestionIntegral.Api.Models.Comunicaciones;
|
||||||
using GestionIntegral.Api.Services.Comunicaciones;
|
using GestionIntegral.Api.Services.Comunicaciones;
|
||||||
|
using GestionIntegral.Api.Data.Repositories.Comunicaciones;
|
||||||
|
using GestionIntegral.Api.Middleware;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -95,6 +97,12 @@ builder.Services.AddScoped<ICambioParadaRepository, CambioParadaRepository>();
|
|||||||
builder.Services.AddScoped<ICambioParadaService, CambioParadaService>();
|
builder.Services.AddScoped<ICambioParadaService, CambioParadaService>();
|
||||||
// Servicio de Saldos
|
// Servicio de Saldos
|
||||||
builder.Services.AddScoped<ISaldoService, SaldoService>();
|
builder.Services.AddScoped<ISaldoService, SaldoService>();
|
||||||
|
// Cierre de Cuenta Corriente de Distribuidor
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
|
builder.Services.AddScoped<ICierreCuentaCorrienteRepository, CierreCuentaCorrienteRepository>();
|
||||||
|
builder.Services.AddScoped<ICierreCuentaCorrienteService, CierreCuentaCorrienteService>();
|
||||||
|
// Validador de período cerrado: SINGLETON porque mantiene cache en memoria de IMemoryCache que debe ser compartido entre requests.
|
||||||
|
builder.Services.AddSingleton<IPeriodoCerradoValidator, PeriodoCerradoValidator>();
|
||||||
// Repositorios de Reportes
|
// Repositorios de Reportes
|
||||||
builder.Services.AddScoped<IReportesRepository, ReportesRepository>();
|
builder.Services.AddScoped<IReportesRepository, ReportesRepository>();
|
||||||
// Servicios de Reportes
|
// Servicios de Reportes
|
||||||
@@ -112,6 +120,8 @@ builder.Services.AddScoped<IFacturaRepository, FacturaRepository>();
|
|||||||
builder.Services.AddScoped<ILoteDebitoRepository, LoteDebitoRepository>();
|
builder.Services.AddScoped<ILoteDebitoRepository, LoteDebitoRepository>();
|
||||||
builder.Services.AddScoped<IPagoRepository, PagoRepository>();
|
builder.Services.AddScoped<IPagoRepository, PagoRepository>();
|
||||||
builder.Services.AddScoped<IPromocionRepository, PromocionRepository>();
|
builder.Services.AddScoped<IPromocionRepository, PromocionRepository>();
|
||||||
|
builder.Services.AddScoped<IAjusteRepository, AjusteRepository>();
|
||||||
|
builder.Services.AddScoped<IFacturaDetalleRepository, FacturaDetalleRepository>();
|
||||||
|
|
||||||
builder.Services.AddScoped<IFormaPagoService, FormaPagoService>();
|
builder.Services.AddScoped<IFormaPagoService, FormaPagoService>();
|
||||||
builder.Services.AddScoped<ISuscriptorService, SuscriptorService>();
|
builder.Services.AddScoped<ISuscriptorService, SuscriptorService>();
|
||||||
@@ -120,10 +130,14 @@ builder.Services.AddScoped<IFacturacionService, FacturacionService>();
|
|||||||
builder.Services.AddScoped<IDebitoAutomaticoService, DebitoAutomaticoService>();
|
builder.Services.AddScoped<IDebitoAutomaticoService, DebitoAutomaticoService>();
|
||||||
builder.Services.AddScoped<IPagoService, PagoService>();
|
builder.Services.AddScoped<IPagoService, PagoService>();
|
||||||
builder.Services.AddScoped<IPromocionService, PromocionService>();
|
builder.Services.AddScoped<IPromocionService, PromocionService>();
|
||||||
|
builder.Services.AddScoped<IAjusteService, AjusteService>();
|
||||||
|
|
||||||
// --- Comunicaciones ---
|
// --- Comunicaciones ---
|
||||||
builder.Services.Configure<MailSettings>(builder.Configuration.GetSection("MailSettings"));
|
builder.Services.Configure<MailSettings>(builder.Configuration.GetSection("MailSettings"));
|
||||||
builder.Services.AddTransient<IEmailService, EmailService>();
|
builder.Services.AddTransient<IEmailService, EmailService>();
|
||||||
|
builder.Services.AddScoped<IEmailLogRepository, EmailLogRepository>();
|
||||||
|
builder.Services.AddScoped<IEmailLogService, EmailLogService>();
|
||||||
|
builder.Services.AddScoped<ILoteDeEnvioRepository, LoteDeEnvioRepository>();
|
||||||
|
|
||||||
// --- SERVICIO DE HEALTH CHECKS ---
|
// --- SERVICIO DE HEALTH CHECKS ---
|
||||||
// Añadimos una comprobación específica para SQL Server.
|
// Añadimos una comprobación específica para SQL Server.
|
||||||
@@ -262,6 +276,10 @@ if (app.Environment.IsDevelopment())
|
|||||||
// Comenta o elimina la siguiente línea si SÓLO usas http://localhost:5183
|
// Comenta o elimina la siguiente línea si SÓLO usas http://localhost:5183
|
||||||
//app.UseHttpsRedirection();
|
//app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
// Middleware global de excepciones — debe ir TEMPRANO en el pipeline para catchear cualquier excepción
|
||||||
|
// que escape de los controllers/services. Mapea BloqueoPorPeriodoCerradoException → 409 con cuerpo JSON estandarizado.
|
||||||
|
app.UseMiddleware<ExceptionHandlerMiddleware>();
|
||||||
|
|
||||||
app.UseCors(MyAllowSpecificOrigins);
|
app.UseCors(MyAllowSpecificOrigins);
|
||||||
|
|
||||||
app.UseAuthentication(); // Debe ir ANTES de UseAuthorization
|
app.UseAuthentication(); // Debe ir ANTES de UseAuthorization
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user