Feat: Implementa Reporte de Distribución de Suscripciones y Refactoriza Gestión de Ajustes
Se introduce una nueva funcionalidad de reporte crucial para la logística y se realiza una refactorización mayor del sistema de ajustes para garantizar la correcta imputación contable. ### ✨ Nuevas Características - **Nuevo Reporte de Distribución de Suscripciones (RR011):** - Se añade un nuevo reporte en PDF que genera un listado de todas las suscripciones activas en un rango de fechas. - El reporte está diseñado para el equipo de reparto, incluyendo datos clave como nombre del suscriptor, dirección, teléfono, días de entrega y observaciones. - Se implementa el endpoint, la lógica de servicio, la consulta a la base de datos y el template de QuestPDF en el backend. - Se crea la página correspondiente en el frontend (React) con su selector de fechas y se añade la ruta y el enlace en el menú de reportes. ### 🔄 Refactorización Mayor - **Asociación de Ajustes a Empresas:** - Se refactoriza por completo la entidad `Ajuste` para incluir una referencia obligatoria a una `IdEmpresa`. - **Motivo:** Corregir un error crítico en la lógica de negocio donde los ajustes de un suscriptor se aplicaban a la primera factura generada, sin importar a qué empresa correspondía el ajuste. - Se provee un script de migración SQL para actualizar el esquema de la base de datos (`susc_Ajustes`). - Se actualizan todos los DTOs, repositorios y servicios (backend) para manejar la nueva relación. - Se modifica el `FacturacionService` para que ahora aplique los ajustes pendientes correspondientes a cada empresa dentro de su bucle de facturación. - Se actualiza el formulario de creación/edición de ajustes en el frontend (React) para incluir un selector de empresa obligatorio. ### ⚡️ Optimizaciones de Rendimiento - **Solución de N+1 Queries:** - Se optimiza el método `ObtenerTodos` en `SuscriptorService` para obtener todas las formas de pago en una única consulta en lugar de una por cada suscriptor. - Se optimiza el método `ObtenerAjustesPorSuscriptor` en `AjusteService` para obtener todos los nombres de usuarios y empresas en dos consultas masivas, en lugar de N consultas individuales. - Se añade el método `GetByIdsAsync` al `IUsuarioRepository` y su implementación para soportar esta optimización. ### 🐛 Corrección de Errores - Se corrigen múltiples errores en el script de migración de base de datos (uso de `GO` dentro de transacciones, error de "columna inválida"). - Se soluciona un error de SQL (`INSERT` statement) en `AjusteRepository` que impedía la creación de nuevos ajustes. - Se corrige un bug en el `AjusteService` que causaba que el nombre de la empresa apareciera como "N/A" en la UI debido a un mapeo incompleto en el método optimizado. - Se corrige la lógica de generación de emails en `FacturacionService` para mostrar correctamente el nombre de la empresa en cada sección del resumen de cuenta.
This commit is contained in:
@@ -4,6 +4,7 @@ using GestionIntegral.Api.Data.Repositories.Usuarios;
|
||||
using GestionIntegral.Api.Dtos.Suscripciones;
|
||||
using GestionIntegral.Api.Models.Suscripciones;
|
||||
using System.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Suscripciones
|
||||
{
|
||||
@@ -12,6 +13,7 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
private readonly IAjusteRepository _ajusteRepository;
|
||||
private readonly ISuscriptorRepository _suscriptorRepository;
|
||||
private readonly IUsuarioRepository _usuarioRepository;
|
||||
private readonly IEmpresaRepository _empresaRepository;
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<AjusteService> _logger;
|
||||
|
||||
@@ -19,12 +21,14 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
IAjusteRepository ajusteRepository,
|
||||
ISuscriptorRepository suscriptorRepository,
|
||||
IUsuarioRepository usuarioRepository,
|
||||
IEmpresaRepository empresaRepository,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<AjusteService> logger)
|
||||
{
|
||||
_ajusteRepository = ajusteRepository;
|
||||
_suscriptorRepository = suscriptorRepository;
|
||||
_usuarioRepository = usuarioRepository;
|
||||
_empresaRepository = empresaRepository;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -33,10 +37,13 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
{
|
||||
if (ajuste == null) return null;
|
||||
var usuario = await _usuarioRepository.GetByIdAsync(ajuste.IdUsuarioAlta);
|
||||
var empresa = await _empresaRepository.GetByIdAsync(ajuste.IdEmpresa);
|
||||
return new AjusteDto
|
||||
{
|
||||
IdAjuste = ajuste.IdAjuste,
|
||||
IdSuscriptor = ajuste.IdSuscriptor,
|
||||
IdEmpresa = ajuste.IdEmpresa,
|
||||
NombreEmpresa = empresa?.Nombre ?? "N/A",
|
||||
FechaAjuste = ajuste.FechaAjuste.ToString("yyyy-MM-dd"),
|
||||
TipoAjuste = ajuste.TipoAjuste,
|
||||
Monto = ajuste.Monto,
|
||||
@@ -51,9 +58,50 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
public async Task<IEnumerable<AjusteDto>> ObtenerAjustesPorSuscriptor(int idSuscriptor, DateTime? fechaDesde, DateTime? fechaHasta)
|
||||
{
|
||||
var ajustes = await _ajusteRepository.GetAjustesPorSuscriptorAsync(idSuscriptor, fechaDesde, fechaHasta);
|
||||
var dtosTasks = ajustes.Select(a => MapToDto(a));
|
||||
var dtos = await Task.WhenAll(dtosTasks);
|
||||
return dtos.Where(dto => dto != null)!;
|
||||
if (!ajustes.Any())
|
||||
{
|
||||
return Enumerable.Empty<AjusteDto>();
|
||||
}
|
||||
|
||||
// 1. Recolectar IDs únicos de usuarios Y empresas de la lista de ajustes
|
||||
var idsUsuarios = ajustes.Select(a => a.IdUsuarioAlta).Distinct().ToList();
|
||||
var idsEmpresas = ajustes.Select(a => a.IdEmpresa).Distinct().ToList();
|
||||
|
||||
// 2. Obtener todos los usuarios y empresas necesarios en dos consultas masivas.
|
||||
var usuariosTask = _usuarioRepository.GetByIdsAsync(idsUsuarios);
|
||||
var empresasTask = _empresaRepository.GetAllAsync(null, null); // Asumiendo que GetAllAsync es suficiente o crear un GetByIds.
|
||||
|
||||
// Esperamos a que ambas consultas terminen
|
||||
await Task.WhenAll(usuariosTask, empresasTask);
|
||||
|
||||
// Convertimos los resultados a diccionarios para búsqueda rápida
|
||||
var usuariosDict = (await usuariosTask).ToDictionary(u => u.Id);
|
||||
var empresasDict = (await empresasTask).ToDictionary(e => e.IdEmpresa);
|
||||
|
||||
// 3. Mapear en memoria, ahora con toda la información disponible.
|
||||
var dtos = ajustes.Select(ajuste =>
|
||||
{
|
||||
usuariosDict.TryGetValue(ajuste.IdUsuarioAlta, out var usuario);
|
||||
empresasDict.TryGetValue(ajuste.IdEmpresa, out var empresa);
|
||||
|
||||
return new AjusteDto
|
||||
{
|
||||
IdAjuste = ajuste.IdAjuste,
|
||||
IdSuscriptor = ajuste.IdSuscriptor,
|
||||
IdEmpresa = ajuste.IdEmpresa,
|
||||
NombreEmpresa = empresa?.Nombre ?? "N/A",
|
||||
FechaAjuste = ajuste.FechaAjuste.ToString("yyyy-MM-dd"),
|
||||
TipoAjuste = ajuste.TipoAjuste,
|
||||
Monto = ajuste.Monto,
|
||||
Motivo = ajuste.Motivo,
|
||||
Estado = ajuste.Estado,
|
||||
IdFacturaAplicado = ajuste.IdFacturaAplicado,
|
||||
FechaAlta = ajuste.FechaAlta.ToString("yyyy-MM-dd HH:mm"),
|
||||
NombreUsuarioAlta = usuario != null ? $"{usuario.Nombre} {usuario.Apellido}" : "N/A"
|
||||
};
|
||||
});
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public async Task<(AjusteDto? Ajuste, string? Error)> CrearAjusteManual(CreateAjusteDto createDto, int idUsuario)
|
||||
@@ -63,10 +111,16 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
{
|
||||
return (null, "El suscriptor especificado no existe.");
|
||||
}
|
||||
var empresa = await _empresaRepository.GetByIdAsync(createDto.IdEmpresa);
|
||||
if (empresa == null)
|
||||
{
|
||||
return (null, "La empresa especificada no existe.");
|
||||
}
|
||||
|
||||
var nuevoAjuste = new Ajuste
|
||||
{
|
||||
IdSuscriptor = createDto.IdSuscriptor,
|
||||
IdEmpresa = createDto.IdEmpresa,
|
||||
FechaAjuste = createDto.FechaAjuste.Date,
|
||||
TipoAjuste = createDto.TipoAjuste,
|
||||
Monto = createDto.Monto,
|
||||
@@ -132,7 +186,11 @@ namespace GestionIntegral.Api.Services.Suscripciones
|
||||
var ajuste = await _ajusteRepository.GetByIdAsync(idAjuste);
|
||||
if (ajuste == null) return (false, "Ajuste no encontrado.");
|
||||
if (ajuste.Estado != "Pendiente") return (false, $"No se puede modificar un ajuste en estado '{ajuste.Estado}'.");
|
||||
|
||||
var empresa = await _empresaRepository.GetByIdAsync(updateDto.IdEmpresa);
|
||||
if (empresa == null) return (false, "La empresa especificada no existe.");
|
||||
|
||||
ajuste.IdEmpresa = updateDto.IdEmpresa;
|
||||
ajuste.FechaAjuste = updateDto.FechaAjuste;
|
||||
ajuste.TipoAjuste = updateDto.TipoAjuste;
|
||||
ajuste.Monto = updateDto.Monto;
|
||||
|
||||
Reference in New Issue
Block a user