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:
@@ -0,0 +1,112 @@
|
||||
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);
|
||||
foreach (var empresa in Model.DatosAgrupados)
|
||||
{
|
||||
column.Item().Element(c => ComposeEmpresa(c, empresa));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ComposeEmpresa(IContainer container, GrupoEmpresa empresa)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Item().Background(Colors.Grey.Lighten2).Padding(5).Text(empresa.NombreEmpresa).Bold().FontSize(12);
|
||||
column.Item().Column(colPub =>
|
||||
{
|
||||
colPub.Spacing(10);
|
||||
foreach (var publicacion in empresa.Publicaciones)
|
||||
{
|
||||
colPub.Item().Element(c => ComposePublicacion(c, publicacion));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void ComposePublicacion(IContainer container, GrupoPublicacion publicacion)
|
||||
{
|
||||
container.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn(2.5f); // Nombre
|
||||
columns.RelativeColumn(3); // Dirección
|
||||
columns.RelativeColumn(1.5f); // Teléfono
|
||||
columns.RelativeColumn(1.5f); // Días
|
||||
columns.RelativeColumn(2.5f); // Observaciones
|
||||
});
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
header.Cell().ColumnSpan(5).Background(Colors.Grey.Lighten4).Padding(3)
|
||||
.Text(publicacion.NombrePublicacion).SemiBold().FontSize(10);
|
||||
|
||||
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("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 ?? "-");
|
||||
table.Cell().Padding(2).Text(item.DiasEntrega);
|
||||
table.Cell().Padding(2).Text(item.Observaciones ?? "-");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user