From 229eb937f50c1a1ccced3d65e38f61e408fcc3d8 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Tue, 24 Jun 2025 12:52:37 -0300 Subject: [PATCH] QuestPdf Implementado en la totalidad de reportes. --- .../ComparativaConsumoBobinasDocument.cs | 114 +++ .../ConsumoBobinasPublicacionDocument.cs | 98 +++ .../ConsumoBobinasSeccionDocument.cs | 139 +++ .../ControlDevolucionesDocument.cs | 126 +++ .../CuentasDistribuidorDocument.cs | 260 ++++++ .../DistribucionCanillasDocument.cs | 243 ++++++ .../DistribucionCanillasTotalesDocument.cs | 126 +++ .../ExistenciaPapelConsolidadoDocument.cs | 2 +- .../PdfTemplates/ExistenciaPapelDocument.cs | 2 +- .../LiquidacionCanillaDocument.cs | 136 +++ .../ListadoDistCanMensualDiariosDocument.cs | 99 +++ .../ListadoDistCanMensualDocument.cs | 113 +++ .../ListadoDistCanillasImporteDocument.cs | 103 +++ .../ListadoDistribucionCanillasDocument.cs | 179 ++++ ...stadoDistribucionDistribuidoresDocument.cs | 178 ++++ .../ListadoDistribucionGeneralDocument.cs | 179 ++++ .../PdfTemplates/MovimientoBobinasDocument.cs | 122 +++ .../MovimientoBobinasEstadoDocument.cs | 129 +++ .../PdfTemplates/NovedadesCanillasDocument.cs | 144 ++++ .../TiradasPublicacionesSeccionesDocument.cs | 99 +++ .../VentaMensualSecretariaElDiaDocument.cs | 89 ++ .../VentaMensualSecretariaElPlataDocument.cs | 89 ++ .../VentaMensualSecretariaTirDevoDocument.cs | 116 +++ .../Reportes/ReportesController.cs | 810 ++++++++---------- .../GestionIntegral.Api.csproj | 25 - .../ComparativaConsumoBobinasViewModel.cs | 17 + .../ConsumoBobinasPublicacionViewModel.cs | 14 + .../ConsumoBobinasSeccionViewModel.cs | 17 + .../ControlDevolucionesViewModel.cs | 31 + .../CuentasDistribuidorViewModel.cs | 29 + .../DistribucionCanillasViewModel.cs | 27 + .../ViewModels/LiquidacionCanillaViewModel.cs | 26 + .../ListadoDistCanMensualDiariosViewModel.cs | 15 + .../ListadoDistCanMensualViewModel.cs | 17 + .../ListadoDistCanillasImporteViewModel.cs | 16 + .../ListadoDistribucionCanillasViewModel.cs | 58 ++ ...tadoDistribucionDistribuidoresViewModel.cs | 47 + .../ListadoDistribucionGeneralViewModel.cs | 46 + .../MovimientoBobinasEstadoViewModel.cs | 15 + .../ViewModels/MovimientoBobinasViewModel.cs | 14 + .../ViewModels/NovedadesCanillasViewModel.cs | 20 + .../TiradasPublicacionesSeccionesViewModel.cs | 20 + .../VentaMensualSecretariaElDiaViewModel.cs | 13 + .../VentaMensualSecretariaElPlataViewModel.cs | 13 + .../VentaMensualSecretariaTirDevoViewModel.cs | 13 + .../Services/Reportes/ReportesService.cs | 25 +- .../ReporteListadoDistMensualPage.tsx | 1 - .../ReporteListadoDistribucionGeneralPage.tsx | 530 +++--------- .../SeleccionaReporteListadoDistMensual.tsx | 92 +- ...orteListadoDistribucionCanillasImporte.tsx | 126 +-- ...cionaReporteListadoDistribucionGeneral.tsx | 1 - 51 files changed, 4009 insertions(+), 954 deletions(-) create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ComparativaConsumoBobinasDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ConsumoBobinasPublicacionDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ConsumoBobinasSeccionDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ControlDevolucionesDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/CuentasDistribuidorDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/DistribucionCanillasDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/DistribucionCanillasTotalesDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/LiquidacionCanillaDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanMensualDiariosDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanMensualDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanillasImporteDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionCanillasDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionDistribuidoresDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionGeneralDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/MovimientoBobinasDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/MovimientoBobinasEstadoDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/NovedadesCanillasDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/TiradasPublicacionesSeccionesDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaElDiaDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaElPlataDocument.cs create mode 100644 Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaTirDevoDocument.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ComparativaConsumoBobinasViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ConsumoBobinasPublicacionViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ConsumoBobinasSeccionViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ControlDevolucionesViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/CuentasDistribuidorViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/DistribucionCanillasViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/LiquidacionCanillaViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanMensualDiariosViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanMensualViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanillasImporteViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionCanillasViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionDistribuidoresViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionGeneralViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/MovimientoBobinasEstadoViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/MovimientoBobinasViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/NovedadesCanillasViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/TiradasPublicacionesSeccionesViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaElDiaViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaElPlataViewModel.cs create mode 100644 Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaTirDevoViewModel.cs diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ComparativaConsumoBobinasDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ComparativaConsumoBobinasDocument.cs new file mode 100644 index 0000000..2362bd6 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ComparativaConsumoBobinasDocument.cs @@ -0,0 +1,114 @@ +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 ComparativaConsumoBobinasDocument : IDocument + { + public ComparativaConsumoBobinasViewModel Model { get; } + + public ComparativaConsumoBobinasDocument(ComparativaConsumoBobinasViewModel 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.Spacing(5); + column.Item().AlignCenter().Text("Reporte de Consumo de Bobinas - Comparativa Mensual").SemiBold().FontSize(14); + + string subTitle = Model.EsConsolidado ? "Consolidado" : $"Planta: {Model.NombrePlanta}"; + column.Item().AlignCenter().Text(subTitle).FontSize(12); + + column.Item().PaddingTop(5, Unit.Millimetre).Row(row => + { + // Le damos una proporción menor al texto de la izquierda (que es corto y fijo). + row.RelativeItem(1).Text(text => + { + text.Span("Fecha del Reporte: ").SemiBold().FontSize(12); + text.Span(Model.FechaReporte); + }); + + // Le damos una proporción mayor al texto de la derecha (que es largo y variable). + // El factor "2" significa que tendrá el doble de espacio disponible que el item con factor "1". + row.RelativeItem(2).AlignRight().Text(text => + { + text.Span("Meses: ").SemiBold().FontSize(12); + text.Span($"{Model.MesA} (Mes A) contra {Model.MesB} (Mes B)").FontSize(12); + }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(5, Unit.Millimetre).Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(3); // Tipo Bobina + columns.RelativeColumn(1); columns.RelativeColumn(1); columns.RelativeColumn(1); // Grupo Cantidad + columns.RelativeColumn(1); columns.RelativeColumn(1); columns.RelativeColumn(1); // Grupo Kilos + }); + + table.Header(header => + { + // Fila 1 del Encabezado + header.Cell().RowSpan(2).Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignMiddle().Text("Tipo Bobina").SemiBold(); + header.Cell().ColumnSpan(3).Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Cantidad Bobinas").SemiBold(); + header.Cell().ColumnSpan(3).Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Kilos Utilizados").SemiBold(); + + // Fila 2 del Encabezado + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Mes A").SemiBold(); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Mes B").SemiBold(); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Diferencia").SemiBold(); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Kg Mes A").SemiBold(); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Kg Mes B").SemiBold(); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Diferencia").SemiBold(); + }); + + foreach (var item in Model.Detalles.OrderBy(x => x.TipoBobina)) + { + table.Cell().Border(1).Padding(3).Text(item.TipoBobina); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.BobinasUtilizadasMesA.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.BobinasUtilizadasMesB.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.DiferenciaBobinasUtilizadas.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.KilosUtilizadosMesA.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.KilosUtilizadosMesB.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.DiferenciaKilosUtilizados.ToString("N0")); + } + + // Fila de Totales + var style = TextStyle.Default.SemiBold(); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span("Totales").Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.BobinasUtilizadasMesA).ToString("N0")).Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.BobinasUtilizadasMesB).ToString("N0")).Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.DiferenciaBobinasUtilizadas).ToString("N0")).Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.KilosUtilizadosMesA).ToString("N0")).Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.KilosUtilizadosMesB).ToString("N0")).Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.DiferenciaKilosUtilizados).ToString("N0")).Style(style)); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ConsumoBobinasPublicacionDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ConsumoBobinasPublicacionDocument.cs new file mode 100644 index 0000000..b0d672a --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ConsumoBobinasPublicacionDocument.cs @@ -0,0 +1,98 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class ConsumoBobinasPublicacionDocument : IDocument + { + public ConsumoBobinasPublicacionViewModel Model { get; } + + public ConsumoBobinasPublicacionDocument(ConsumoBobinasPublicacionViewModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + + public void Compose(IDocumentContainer container) + { + container.Page(page => + { + page.Margin(1.5f, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10)); + + 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.Spacing(5); + column.Item().AlignCenter().Text("Reporte de Consumo de Bobinas por Publicaciones").SemiBold().FontSize(14); + + column.Item().PaddingTop(5, Unit.Millimetre).Row(row => + { + row.RelativeItem().Text(text => { text.Span("Fecha del Reporte: ").SemiBold(); text.Span(Model.FechaReporte); }); + row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(1, Unit.Centimetre).Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(2); // Planta + columns.RelativeColumn(3); // Publicación + columns.RelativeColumn(1.5f); // Kilos + columns.RelativeColumn(1.5f); // Cant. Bobinas + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Planta"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Publicación"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Kilos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cant. Bobinas"); + }); + + var gruposPorPlanta = Model.Detalles.GroupBy(p => p.NombrePlanta); + + foreach (var grupoPlanta in gruposPorPlanta) + { + var primeraFilaDePlanta = true; + foreach (var detalle in grupoPlanta.OrderBy(d => d.NombrePublicacion)) + { + // Celda de Planta (solo en la primera fila del grupo) + if (primeraFilaDePlanta) + { + table.Cell().RowSpan((uint)grupoPlanta.Count()).Border(1).Padding(3).AlignTop().Text(grupoPlanta.Key).SemiBold(); + primeraFilaDePlanta = false; + } + + table.Cell().Border(1).Padding(3).Text(detalle.NombrePublicacion); + table.Cell().Border(1).Padding(3).AlignRight().Text(detalle.TotalKilos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(detalle.CantidadBobinas.ToString("N0")); + } + } + + // Fila de Totales Generales + var totalGeneralKilos = Model.Detalles.Sum(d => d.TotalKilos); + var totalGeneralBobinas = Model.Detalles.Sum(d => d.CantidadBobinas); + + table.Cell().ColumnSpan(2).BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).AlignRight().Text("Totales").ExtraBold(); + table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).AlignRight().Text(t => t.Span(totalGeneralKilos.ToString("N0")).ExtraBold()); + table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).Padding(3).AlignRight().Text(t => t.Span(totalGeneralBobinas.ToString("N0")).ExtraBold()); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ConsumoBobinasSeccionDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ConsumoBobinasSeccionDocument.cs new file mode 100644 index 0000000..1a9b661 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ConsumoBobinasSeccionDocument.cs @@ -0,0 +1,139 @@ +using GestionIntegral.Api.Dtos.Reportes; +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Collections.Generic; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class ConsumoBobinasSeccionDocument : IDocument + { + public ConsumoBobinasSeccionViewModel Model { get; } + + public ConsumoBobinasSeccionDocument(ConsumoBobinasSeccionViewModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + + public void Compose(IDocumentContainer container) + { + container.Page(page => + { + page.Margin(1.5f, 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.Spacing(5); + column.Item().AlignCenter().Text("Reporte de Consumo de Bobinas por Secciones").SemiBold().FontSize(14); + + string subTitle = Model.EsConsolidado ? "Consolidado" : $"Planta: {Model.NombrePlanta}"; + column.Item().AlignCenter().Text(subTitle).FontSize(12); + + column.Item().PaddingTop(5, Unit.Millimetre).Row(row => + { + row.RelativeItem().Text(text => { text.Span("Fecha del Reporte: ").SemiBold(); text.Span(Model.FechaReporte); }); + row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); }); + }); + + // Encabezado de la tabla principal + column.Item().PaddingTop(10).BorderBottom(1.5f).BorderColor(Colors.Black).Padding(4).Row(row => + { + row.RelativeItem(1.5f).Text("Publicación").SemiBold(); + row.RelativeItem(1.5f).Text("Sección").SemiBold(); + row.RelativeItem(3).Text("Bobina").SemiBold(); + row.RelativeItem(1).AlignRight().Text("Cant. Bobinas").SemiBold(); + row.RelativeItem(1).AlignRight().Text("Kilos").SemiBold(); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.Column(column => + { + var gruposPorPublicacion = Model.Detalles.GroupBy(p => p.NombrePublicacion); + + foreach (var grupoPub in gruposPorPublicacion) + { + column.Item().Element(c => ComposePublicacionSection(c, grupoPub)); + } + + // Fila de Totales Generales al final + var totalGeneralBobinas = Model.Detalles.Sum(d => d.CantidadBobinas); + var totalGeneralKilos = Model.Detalles.Sum(d => d.TotalKilos); + + column.Item().PaddingTop(10).Row(row => + { + row.RelativeItem(6).AlignRight().Text("Total General").ExtraBold().FontSize(12); + row.RelativeItem(1).AlignRight().Text(totalGeneralBobinas.ToString("N0")).ExtraBold().FontSize(12); + row.RelativeItem(1).AlignRight().Text(totalGeneralKilos.ToString("N0")).ExtraBold().FontSize(12); + }); + }); + } + + // --- COMPONENTE PARA UNA PUBLICACIÓN ENTERA --- + void ComposePublicacionSection(IContainer container, IGrouping grupoPub) + { + container.Row(row => + { + // Columna 1: Nombre de la Publicación + row.RelativeItem(1.5f).BorderBottom(1).BorderLeft(1).BorderRight(1).BorderColor(Colors.Grey.Medium).Padding(3).Text(grupoPub.Key).SemiBold(); + + // Columna 2: Contiene todas las sub-tablas de secciones + row.RelativeItem(6.5f).Column(seccionesColumn => + { + var gruposPorSeccion = grupoPub.GroupBy(s => s.NombreSeccion); + foreach(var grupoSec in gruposPorSeccion) + { + seccionesColumn.Item().Element(c => ComposeSeccionTable(c, grupoSec)); + } + }); + }); + } + + // --- COMPONENTE PARA LA TABLA DE UNA SECCIÓN --- + void ComposeSeccionTable(IContainer container, IGrouping grupoSec) + { + container.Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(1.5f); // Sección + columns.RelativeColumn(3); // Bobina + columns.RelativeColumn(1); // Cantidad + columns.RelativeColumn(1); // Kilos + }); + + // Filas de detalle + foreach (var detalle in grupoSec.OrderBy(d => d.NombreBobina)) + { + table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).Text(grupoSec.Key); + table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).Text(detalle.NombreBobina); + table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).AlignRight().Text(detalle.CantidadBobinas.ToString("N0")); + table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).AlignRight().Text(detalle.TotalKilos.ToString("N0")); + } + + // Fila de total de la sección + var totalCantSeccion = grupoSec.Sum(d => d.CantidadBobinas); + var totalKilosSeccion = grupoSec.Sum(d => d.TotalKilos); + + table.Cell().ColumnSpan(2).Border(1).BorderColor(Colors.Grey.Medium).AlignRight().Padding(3).Text("Total Sección").SemiBold(); + table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).AlignRight().Text(t => t.Span(totalCantSeccion.ToString("N0")).SemiBold()); + table.Cell().Border(1).BorderColor(Colors.Grey.Medium).Padding(3).AlignRight().Text(t => t.Span(totalKilosSeccion.ToString("N0")).SemiBold()); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ControlDevolucionesDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ControlDevolucionesDocument.cs new file mode 100644 index 0000000..85dd057 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ControlDevolucionesDocument.cs @@ -0,0 +1,126 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class ControlDevolucionesDocument : IDocument + { + public ControlDevolucionesViewModel Model { get; } + + public ControlDevolucionesDocument(ControlDevolucionesViewModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + public DocumentSettings GetSettings() => DocumentSettings.Default; + + public void Compose(IDocumentContainer container) + { + container.Page(page => + { + page.Margin(1, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontFamily("Roboto").FontSize(11)); + + page.Header().Element(ComposeHeader); + page.Content().Element(ComposeContent); + }); + } + + void ComposeHeader(IContainer container) + { + container.Column(column => + { + column.Item().AlignCenter().Text("Control de Devoluciones").SemiBold().FontSize(16); + column.Item().AlignCenter().Text("Canillas / Accionistas").FontSize(13); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(1, Unit.Centimetre).Column(column => + { + column.Spacing(15); + + column.Item().Row(row => + { + row.RelativeItem().Text(text => + { + text.Span("Fecha Consultada: ").SemiBold(); + text.Span(Model.FechaConsultada); + }); + row.RelativeItem().AlignRight().Text(text => + { + text.Span("Cantidad Canillas: ").SemiBold(); + text.Span(Model.CantidadCanillas.ToString()); + }); + }); + + column.Item().PaddingTop(5).Border(1).Background(Colors.Grey.Lighten3).AlignCenter().Padding(2).Text(Model.NombreEmpresa).SemiBold(); + + column.Item().Border(1).Padding(10).Column(innerCol => + { + innerCol.Spacing(5); + + // Fila de "Ingresados por Remito" con borde inferior sólido. + innerCol.Item().BorderBottom(1, Unit.Point).BorderColor(Colors.Grey.Medium).Row(row => + { + row.RelativeItem().Text("Ingresados por Remito:").SemiBold(); + row.RelativeItem().AlignRight().Text(Model.TotalIngresadosPorRemito.ToString("N0")); + }); // <-- SOLUCIÓN: Borde sólido simple. + + foreach (var item in Model.Detalles) + { + var totalSeccion = item.Devueltos - item.Llevados; + innerCol.Item().PaddingTop(5).Row(row => + { + row.ConstantItem(100).Text(item.Tipo).SemiBold(); + + row.RelativeItem().Column(sub => + { + sub.Spacing(2); + sub.Item().Row(r => { + r.RelativeItem().Text("Llevados"); + r.RelativeItem().AlignRight().Text($"-{item.Llevados:N0}"); + }); + sub.Item().Row(r => { + r.RelativeItem().Text("Devueltos"); + r.RelativeItem().AlignRight().Text($"{item.Devueltos:N0}"); + }); + sub.Item().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(2).Row(r => { + r.RelativeItem().Text(t => t.Span("Total").SemiBold()); + r.RelativeItem().AlignRight().Text(t => t.Span(totalSeccion.ToString("N0")).SemiBold()); + }); + }); + }); + } + }); + + column.Item().PaddingTop(10).Column(finalCol => + { + finalCol.Spacing(2); + + Action AddTotalRow = (row, label, value, isBold) => + { + var text = row.RelativeItem().Text(label); + if (isBold) text.SemiBold(); + + var valueText = row.ConstantItem(80).AlignRight().Text(value); + if (isBold) valueText.SemiBold(); + }; + + // Usamos bordes superiores para separar las líneas de total + 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(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(2).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)); + finalCol.Item().BorderTop(1).BorderColor(Colors.Grey.Lighten2).PaddingTop(2).Row(row => AddTotalRow(row, "Sobrantes", $"-{Model.TotalSobrantes.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)); + }); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/CuentasDistribuidorDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/CuentasDistribuidorDocument.cs new file mode 100644 index 0000000..96dd4f8 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/CuentasDistribuidorDocument.cs @@ -0,0 +1,260 @@ +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 CuentasDistribuidorDocument : IDocument + { + public CuentasDistribuidorViewModel Model { get; } + private static readonly CultureInfo CultureAr = new CultureInfo("es-AR", false) + { + NumberFormat = { CurrencySymbol = "$" } + }; + + public CuentasDistribuidorDocument(CuentasDistribuidorViewModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + public DocumentSettings GetSettings() => DocumentSettings.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(); x.Span(" de "); x.TotalPages(); }); + }); + } + + void ComposeHeader(IContainer container) + { + container.Column(column => + { + column.Item().Row(row => + { + row.RelativeItem().Text($"Fecha de Reporte {Model.FechaReporte}"); + row.RelativeItem().AlignCenter().Text("Cuenta De Distribuidor").SemiBold().FontSize(14); + row.RelativeItem(); + }); + + column.Item().AlignCenter().Text(Model.NombreDistribuidor).FontSize(12); + + column.Item().PaddingTop(2, Unit.Millimetre).Row(row => + { + row.RelativeItem(2); + row.RelativeItem(8).AlignCenter().Text(text => + { + text.Span("Fecha Consultada: Desde ").SemiBold(); + text.Span(Model.FechaDesde); + text.Span(" Hasta ").SemiBold(); + text.Span(Model.FechaHasta); + }); + row.RelativeItem(2); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(1, Unit.Centimetre).Column(column => + { + column.Spacing(20); + + if (Model.Movimientos.Any()) column.Item().Element(ComposeMovimientosTable); + if (Model.Pagos.Any()) column.Item().Element(ComposePagosTable); + if (Model.DebitosCreditos.Any()) column.Item().Element(ComposeDebCredTable); + + column.Item().Element(ComposeResumenPeriodo); + column.Item().Element(ComposeSaldoFinal); + }); + } + + void ComposeMovimientosTable(IContainer container) + { + container.Column(column => + { + column.Item().PaddingBottom(5).Text("Movimientos").SemiBold().FontSize(11); + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(60); + columns.RelativeColumn(2); + columns.ConstantColumn(50); + columns.ConstantColumn(50); + columns.RelativeColumn(1.5f); + columns.RelativeColumn(1.5f); + columns.RelativeColumn(2); + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Fecha"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Publicacion"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Remito"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Cantidad"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Debe"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Haber"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Saldo"); + }); + + decimal saldoAcumulado = 0; // Inicia en CERO + foreach (var item in Model.Movimientos.OrderBy(m => m.Fecha)) + { + saldoAcumulado += (item.Debe - item.Haber); + table.Cell().Border(1).Padding(2).Text(item.Fecha.ToString("dd/MM/yyyy")); + table.Cell().Border(1).Padding(2).Text(item.Publicacion); + table.Cell().Border(1).Padding(2).Text(item.Remito); + table.Cell().Border(1).Padding(2).AlignCenter().Text(item.Cantidad.ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.Debe.ToString("C", CultureAr)); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.Haber.ToString("C", CultureAr)); + table.Cell().Border(1).Padding(2).AlignRight().Text(saldoAcumulado.ToString("C", CultureAr)); + } + + table.Cell().ColumnSpan(4).Border(1).AlignRight().Padding(2).Text(t => t.Span("Totales").SemiBold()); + table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.Movimientos.Sum(x => x.Debe).ToString("C", CultureAr)).SemiBold()); + table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.Movimientos.Sum(x => x.Haber).ToString("C", CultureAr)).SemiBold()); + table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(saldoAcumulado.ToString("C", CultureAr)).SemiBold()); + }); + }); + } + + void ComposePagosTable(IContainer container) + { + container.Column(column => + { + column.Item().PaddingBottom(5).Text("Pagos").SemiBold().FontSize(12); + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(60); + columns.RelativeColumn(1.5f); + columns.ConstantColumn(50); + columns.RelativeColumn(1.5f); + columns.RelativeColumn(1.5f); + columns.RelativeColumn(1.5f); + columns.RelativeColumn(2); + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Fecha"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Recibo"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Tipo"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Debe"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Haber"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Saldo"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Detalle"); + }); + + decimal saldoAcumulado = Model.TotalMovimientos; + foreach (var item in Model.Pagos.OrderBy(p => p.Fecha).ThenBy(p => p.Recibo)) + { + saldoAcumulado += (item.Debe - item.Haber); + table.Cell().Border(1).Padding(2).Text(item.Fecha.ToString("dd/MM/yyyy")); + table.Cell().Border(1).Padding(2).Text(item.Recibo.ToString()); + table.Cell().Border(1).Padding(2).Text(item.Tipo); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.Debe.ToString("C", CultureAr)); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.Haber.ToString("C", CultureAr)); + table.Cell().Border(1).Padding(2).AlignRight().Text(saldoAcumulado.ToString("C", CultureAr)); + table.Cell().Border(1).Padding(2).Text(item.Detalle); + } + + table.Cell().ColumnSpan(3).Border(1).AlignRight().Padding(2).Text(t => t.Span("Totales").SemiBold()); + table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.Pagos.Sum(x => x.Debe).ToString("C", CultureAr)).SemiBold()); + table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.Pagos.Sum(x => x.Haber).ToString("C", CultureAr)).SemiBold()); + table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(saldoAcumulado.ToString("C", CultureAr)).SemiBold()); + table.Cell().Border(1); + }); + }); + } + + void ComposeDebCredTable(IContainer container) + { + container.Column(column => + { + column.Item().PaddingBottom(5).Text("Débitos / Créditos").SemiBold().FontSize(12); + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(65); + columns.RelativeColumn(2); + columns.RelativeColumn(1); + columns.RelativeColumn(1); + columns.RelativeColumn(2); + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Fecha"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Referencia"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Debe"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Haber"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Saldo"); + }); + + decimal saldoAcumulado = Model.TotalMovimientos + Model.TotalPagos; + foreach (var item in Model.DebitosCreditos.OrderBy(dc => dc.Fecha)) + { + saldoAcumulado += (item.Debe - item.Haber); + table.Cell().Border(1).Padding(2).Text(item.Fecha.ToString("dd/MM/yyyy")); + table.Cell().Border(1).Padding(2).Text(item.Referencia); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.Debe.ToString("C", CultureAr)); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.Haber.ToString("C", CultureAr)); + table.Cell().Border(1).Padding(2).AlignRight().Text(saldoAcumulado.ToString("C", CultureAr)); + } + + table.Cell().ColumnSpan(2).Border(1).AlignRight().Padding(2).Text(t => t.Span("Totales").SemiBold()); + table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.DebitosCreditos.Sum(x => x.Debe).ToString("C", CultureAr)).SemiBold()); + table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(Model.DebitosCreditos.Sum(x => x.Haber).ToString("C", CultureAr)).SemiBold()); + table.Cell().Border(1).AlignRight().Padding(2).Text(t => t.Span(saldoAcumulado.ToString("C", CultureAr)).SemiBold()); + }); + }); + } + + void ComposeResumenPeriodo(IContainer container) + { + container.PaddingTop(5, Unit.Millimetre).AlignLeft().Column(column => + { + column.Item().Border(1).Padding(5).Width(300, Unit.Point).Column(col => + { + col.Spacing(5); + col.Item().Text("Datos totales del periodo consultado").SemiBold(); + Action AddResumenRow = (row, label, value) => + { + row.RelativeItem().Text(label); + row.ConstantItem(120).AlignRight().Text(value.ToString("C", CultureAr)); + }; + 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, "Pagos", Model.TotalPagos)); + col.Item().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(5).Row(row => + { + row.RelativeItem().Text("Total").SemiBold(); + row.ConstantItem(120).AlignRight().Text(t => t.Span(Model.TotalPeriodo.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); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/DistribucionCanillasDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/DistribucionCanillasDocument.cs new file mode 100644 index 0000000..18eca68 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/DistribucionCanillasDocument.cs @@ -0,0 +1,243 @@ +// --- REEMPLAZAR ARCHIVO: Controllers/Reportes/PdfTemplates/DistribucionCanillasDocument.cs --- +using GestionIntegral.Api.Dtos.Reportes; +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class DistribucionCanillasDocument : IDocument + { + public DistribucionCanillasViewModel Model { get; } + private static readonly CultureInfo CultureAr = new CultureInfo("es-AR"); + + public DistribucionCanillasDocument(DistribucionCanillasViewModel 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().Text("Listado de Distribución: Canillas / Accionistas").FontSize(12); + row.RelativeItem().AlignRight().Text(text => { + text.Span("Fecha Consultada ").SemiBold().FontSize(12); + text.Span(Model.FechaConsultada).FontSize(12); + }); + }); + column.Item().PaddingTop(5).Row(row => { + row.RelativeItem().Text(text => { + text.Span("Fecha de Generación del Reporte ").SemiBold().FontSize(12); + text.Span(Model.FechaReporte); + }); + row.RelativeItem().AlignRight().Text(text => { + text.Span("Empresa ").SemiBold().FontSize(12); + text.Span(Model.Empresa).FontSize(12); + }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(5, Unit.Millimetre).Column(column => + { + column.Spacing(15); + + if(Model.Canillas.Any()) + column.Item().Element(c => ComposeTablaDetallada(c, "Canillas", Model.Canillas)); + + if(Model.CanillasAccionistas.Any()) + column.Item().Element(c => ComposeTablaDetallada(c, "Accionistas", Model.CanillasAccionistas)); + + if(Model.CanillasLiquidadasOtraFecha.Any()) + column.Item().Element(c => ComposeTablaLiquidacion(c, "Liquidación de movimientos de otras fechas canillas", Model.CanillasLiquidadasOtraFecha)); + + if(Model.CanillasAccionistasLiquidadasOtraFecha.Any()) + column.Item().Element(c => ComposeTablaLiquidacion(c, "Liquidación de movimientos de otras fechas accionistas", Model.CanillasAccionistasLiquidadasOtraFecha)); + + if(Model.CanillasTodos.Any()) + column.Item().Element(c => ComposeTablaTotales(c, "Recuento de Publicaciones", Model.CanillasTodos)); + + if(Model.RemitoIngresado > 0) + column.Item().Element(ComposeResumenDevoluciones); + }); + } + + void ComposeTablaDetallada(IContainer container, string title, IEnumerable data) + { + container.Column(column => + { + column.Item().PaddingBottom(4).Text(title).SemiBold().FontSize(11); + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(3); columns.RelativeColumn(3); + columns.RelativeColumn(1); columns.RelativeColumn(1); + columns.RelativeColumn(1); columns.RelativeColumn(1.2f); + }); + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Vendedor"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Publicación"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Vendidos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("A Rendir"); + }); + foreach (var grupoCanilla in data.GroupBy(c => c.Canilla)) + { + var primeraFila = true; + // El RowSpan es el número de publicaciones + la fila de total + var totalFilasGrupo = grupoCanilla.Count() + 1; + foreach (var item in grupoCanilla.OrderBy(p => p.Publicacion)) + { + if (primeraFila) + { + table.Cell().RowSpan((uint)totalFilasGrupo).Border(1).Padding(2).AlignTop().Text(item.Canilla); + primeraFila = false; + } + table.Cell().Border(1).Padding(2).Text(item.Publicacion); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantSalida.ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantEntrada.ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text((item.TotalCantSalida - item.TotalCantEntrada).ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr)); + } + // Subtotal por canilla + table.Cell().Border(1).Padding(2).AlignRight().Text("Totales").SemiBold(); + table.Cell().Border(1).Padding(2).AlignRight().Text(t => t.Span(grupoCanilla.Sum(i => i.TotalCantSalida).ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(2).AlignRight().Text(t => t.Span(grupoCanilla.Sum(i => i.TotalCantEntrada).ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(2).AlignRight().Text(t => t.Span(grupoCanilla.Sum(i => i.TotalCantSalida - i.TotalCantEntrada).ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(2).AlignRight().Text(t => t.Span(grupoCanilla.Sum(i => i.TotalRendir).ToString("C", CultureAr)).SemiBold()); + } + + var boldStyle = TextStyle.Default.ExtraBold(); + table.Cell().ColumnSpan(2).BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span("Total " + title).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantEntrada).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida - i.TotalCantEntrada).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalRendir).ToString("C", CultureAr)).Style(boldStyle)); + }); + }); + } + + void ComposeTablaLiquidacion(IContainer container, string title, IEnumerable data) + { + container.Column(column => + { + column.Item().PaddingBottom(4).Text(title).SemiBold().FontSize(11); + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(3); columns.RelativeColumn(1.5f); + columns.RelativeColumn(3); columns.RelativeColumn(1); + columns.RelativeColumn(1); columns.RelativeColumn(1); + columns.RelativeColumn(1.2f); + }); + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Vendedor"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Fecha"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Publicación"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Vendidos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("A Rendir"); + }); + foreach(var item in data.OrderBy(d => d.Canilla).ThenBy(d => d.Fecha)) + { + table.Cell().Border(1).Padding(2).Text(item.Canilla); + table.Cell().Border(1).Padding(2).Text(item.Fecha?.ToString("dd/MM/yyyy") ?? ""); + table.Cell().Border(1).Padding(2).Text(item.Publicacion); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantSalida.ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantEntrada.ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text((item.TotalCantSalida - item.TotalCantEntrada).ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr)); + } + var boldStyle = TextStyle.Default.ExtraBold(); + table.Cell().ColumnSpan(3).BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span("Total").Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantEntrada).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida - i.TotalCantEntrada).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalRendir).ToString("C", CultureAr)).Style(boldStyle)); + }); + }); + } + + void ComposeTablaTotales(IContainer container, string title, IEnumerable data) + { + container.Column(column => + { + column.Item().PaddingBottom(4).Text(title).SemiBold().FontSize(11); + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(2); columns.RelativeColumn(3); + columns.RelativeColumn(1.2f); columns.RelativeColumn(1.2f); + columns.RelativeColumn(1.2f); columns.RelativeColumn(1.5f); + }); + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Tipo Vendedor"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Publicación"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Vendidos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("A Rendir"); + }); + foreach(var item in data.OrderBy(d => d.TipoVendedor).ThenBy(d => d.Publicacion)) + { + table.Cell().Border(1).Padding(2).Text(item.TipoVendedor); + table.Cell().Border(1).Padding(2).Text(item.Publicacion); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantSalida.ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantEntrada.ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text((item.TotalCantSalida-item.TotalCantEntrada).ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr)); + } + var boldStyle = TextStyle.Default.ExtraBold(); + table.Cell().ColumnSpan(2).BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span("Total General").Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantEntrada).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalCantSalida - i.TotalCantEntrada).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(data.Sum(i => i.TotalRendir).ToString("C", CultureAr)).Style(boldStyle)); + }); + }); + } + + void ComposeResumenDevoluciones(IContainer container) + { + container.PaddingTop(5).Column(column => { + column.Item().Row(row => { + row.Spacing(15); + row.AutoItem().Text(text => { text.Span("Remito: ").SemiBold().FontSize(11); text.Span(Model.RemitoIngresado.ToString("N0")).FontSize(11); }); + row.AutoItem().Text(text => { text.Span("Devolución: ").SemiBold().FontSize(11); text.Span(Model.DevolucionTotal.ToString("N0")).FontSize(11); }); + row.AutoItem().Text(text => { text.Span("Venta: ").SemiBold().FontSize(11); text.Span(Model.VentaTotal.ToString("N0")).FontSize(11); }); + }); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/DistribucionCanillasTotalesDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/DistribucionCanillasTotalesDocument.cs new file mode 100644 index 0000000..63ddec6 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/DistribucionCanillasTotalesDocument.cs @@ -0,0 +1,126 @@ +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 DistribucionCanillasTotalesDocument : IDocument + { + public DistribucionCanillasViewModel Model { get; } + private static readonly CultureInfo CultureAr = new CultureInfo("es-AR"); + + public DistribucionCanillasTotalesDocument(DistribucionCanillasViewModel 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(10)); + + 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().Text("Listado de Distribución: Canillas / Accionistas (Totales)").FontSize(12); + row.RelativeItem().AlignRight().Text(text => { + text.Span("Fecha Consultada ").SemiBold().FontSize(12); + text.Span(Model.FechaConsultada).FontSize(12); + }); + }); + column.Item().PaddingTop(5).Row(row => { + row.RelativeItem().Text(text => { + text.Span("Fecha de Generación del Reporte ").SemiBold().FontSize(12); + text.Span(Model.FechaReporte).FontSize(12); + }); + row.RelativeItem().AlignRight().Text(text => { + text.Span("Empresa ").SemiBold().FontSize(12); + text.Span(Model.Empresa).FontSize(12); + }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(5, Unit.Millimetre).Column(column => + { + column.Spacing(15); + + if(Model.CanillasTodos.Any()) + column.Item().Element(ComposeTablaTotales); + + if(Model.RemitoIngresado > 0) + column.Item().Element(ComposeResumenDevoluciones); + }); + } + + void ComposeTablaTotales(IContainer container) + { + container.Column(column => + { + column.Item().PaddingBottom(4).Text("Recuento de Publicaciones").SemiBold().FontSize(11); + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(2); columns.RelativeColumn(3); + columns.RelativeColumn(1.2f); columns.RelativeColumn(1.2f); + columns.RelativeColumn(1.2f); columns.RelativeColumn(1.5f); + }); + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Tipo Vendedor"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).Text("Publicación"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("Vendidos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(2).AlignRight().Text("A Rendir"); + }); + foreach(var item in Model.CanillasTodos.OrderBy(d => d.TipoVendedor).ThenBy(d => d.Publicacion)) + { + table.Cell().Border(1).Padding(2).Text(item.TipoVendedor); + table.Cell().Border(1).Padding(2).Text(item.Publicacion); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantSalida.ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalCantEntrada.ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text((item.TotalCantSalida-item.TotalCantEntrada).ToString("N0")); + table.Cell().Border(1).Padding(2).AlignRight().Text(item.TotalRendir.ToString("C", CultureAr)); + } + var boldStyle = TextStyle.Default.ExtraBold(); + table.Cell().ColumnSpan(2).BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span("Total General").Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(Model.CanillasTodos.Sum(i => i.TotalCantSalida).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(Model.CanillasTodos.Sum(i => i.TotalCantEntrada).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(Model.CanillasTodos.Sum(i => i.TotalCantSalida - i.TotalCantEntrada).ToString("N0")).Style(boldStyle)); + table.Cell().BorderTop(2).BorderColor(Colors.Black).Padding(2).AlignRight().Text(t => t.Span(Model.CanillasTodos.Sum(i => i.TotalRendir).ToString("C", CultureAr)).Style(boldStyle)); + }); + }); + } + + void ComposeResumenDevoluciones(IContainer container) + { + container.PaddingTop(10).Column(column => { + column.Item().Row(row => { + row.Spacing(15); + row.AutoItem().Text(text => { text.Span("Remito: ").SemiBold(); text.Span(Model.RemitoIngresado.ToString("N0")); }); + row.AutoItem().Text(text => { text.Span("Devolución: ").SemiBold(); text.Span(Model.DevolucionTotal.ToString("N0")); }); + row.AutoItem().Text(text => { text.Span("Venta: ").SemiBold(); text.Span(Model.VentaTotal.ToString("N0")); }); + }); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ExistenciaPapelConsolidadoDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ExistenciaPapelConsolidadoDocument.cs index 34e9c9a..84d90d7 100644 --- a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ExistenciaPapelConsolidadoDocument.cs +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ExistenciaPapelConsolidadoDocument.cs @@ -23,7 +23,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates container .Page(page => { - page.Margin(1.5f, Unit.Centimetre); + page.Margin(1, Unit.Centimetre); page.PageColor(Colors.White); page.DefaultTextStyle(x => x.FontFamily("Roboto").FontSize(10)); diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ExistenciaPapelDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ExistenciaPapelDocument.cs index 36e5f6e..2bb9469 100644 --- a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ExistenciaPapelDocument.cs +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ExistenciaPapelDocument.cs @@ -26,7 +26,7 @@ namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates container .Page(page => { - page.Margin(1.5f, Unit.Centimetre); + page.Margin(1, Unit.Centimetre); page.PageColor(Colors.White); page.DefaultTextStyle(x => x.FontFamily("Roboto").FontSize(10)); diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/LiquidacionCanillaDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/LiquidacionCanillaDocument.cs new file mode 100644 index 0000000..06ec3eb --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/LiquidacionCanillaDocument.cs @@ -0,0 +1,136 @@ +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 LiquidacionCanillaDocument : IDocument + { + public LiquidacionCanillaViewModel Model { get; } + private static readonly CultureInfo CultureAr = new CultureInfo("es-AR"); + + public LiquidacionCanillaDocument(LiquidacionCanillaViewModel model) + { + Model = model; + } + + 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) + { + container.Page(page => + { + page.Size(PageSizes.A5); + page.Margin(1, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(9)); + + page.Header().Element(ComposeHeader); + page.Content().Element(ComposeContent); + }); + } + + void ComposeHeader(IContainer container) + { + container.Column(column => + { + column.Item().Text("EL DIA S.A.I.C. y F."); + column.Item().Text(text => + { + text.Span("Liquidación venta de diarios del: "); + text.Span(Model.FechaLiquidacion); + }); + column.Item().PaddingTop(5).Text($"Vendedor: {Model.NombreVendedor}").SemiBold(); + column.Item().PaddingTop(10); + }); + } + + void ComposeContent(IContainer container) + { + container.Column(column => + { + column.Spacing(15); + + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(2); + columns.RelativeColumn(2); + columns.RelativeColumn(1); + }); + + foreach (var item in Model.Detalles.OrderBy(d => d.Publicacion)) + { + var vendidos = item.TotalCantSalida - item.TotalCantEntrada; + + table.Cell().ColumnSpan(3).Text(item.Publicacion).SemiBold(); + + table.Cell(); + table.Cell().Text("Retirados"); + table.Cell().AlignRight().Text(item.TotalCantSalida.ToString("N0")); + + table.Cell(); + table.Cell().Text("Devueltos"); + table.Cell().AlignRight().Text(item.TotalCantEntrada.ToString("N0")); + + table.Cell(); + table.Cell().Text("Vendidos"); + table.Cell().AlignRight().Text(vendidos.ToString("N0")); + + table.Cell(); + table.Cell().Text("Precio Unitario"); + table.Cell().AlignRight().Text(item.PrecioEjemplar.ToString("C", CultureAr)); + + 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).AlignRight().Text(t => t.Span(item.TotalRendir.ToString("C", CultureAr)).SemiBold()); + } + }); + + column.Item().BorderTop(2).BorderColor(Colors.Black).PaddingTop(4).Row(row => + { + row.RelativeItem().Text("Total A Rendir").SemiBold().FontSize(10); + row.RelativeItem().AlignRight().Text(text => text.Span(Model.TotalARendir.ToString("C", CultureAr)).SemiBold().FontSize(10)); + }); + + if (!Model.EsAccionista && Model.Ganancias.Any()) + { + column.Item().PaddingTop(10).Element(ComposeGananciasTable); + } + }); + } + + void ComposeGananciasTable(IContainer container) + { + container.Column(column => + { + column.Item().Text("Comisiones Acreditadas").SemiBold().FontSize(11); + + column.Item().PaddingTop(5).Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(2); + columns.RelativeColumn(1); + }); + + foreach (var item in Model.Ganancias) + { + 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().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()); + }); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanMensualDiariosDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanMensualDiariosDocument.cs new file mode 100644 index 0000000..c45cb19 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanMensualDiariosDocument.cs @@ -0,0 +1,99 @@ +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 ListadoDistCanMensualDiariosDocument : IDocument + { + public ListadoDistCanMensualDiariosViewModel Model { get; } + private static readonly CultureInfo CultureAr = new CultureInfo("es-AR"); + + public ListadoDistCanMensualDiariosDocument(ListadoDistCanMensualDiariosViewModel 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().AlignCenter().Text($"Ventas por {Model.TipoDestinatario} desde el {Model.FechaDesde} hasta el {Model.FechaHasta}").SemiBold().FontSize(12); + }); + } + + void ComposeContent(IContainer container) + { + // APLICAMOS LA SOLUCIÓN AQUÍ: .AlignTop() + container.PaddingTop(1, Unit.Centimetre).AlignTop().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(3); // Nombre + columns.ConstantColumn(50); // El Día + columns.ConstantColumn(50); // El Plata + columns.ConstantColumn(60); // Vendidos + columns.RelativeColumn(1.5f); // Imp. El Día + columns.RelativeColumn(1.5f); // Imp. El Plata + columns.RelativeColumn(1.5f); // Importe Total + }); + + table.Header(header => + { + header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).Text("Nombre").SemiBold(); + header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignCenter().Text("El Día").SemiBold(); + header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignCenter().Text("El Plata").SemiBold(); + header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignCenter().Text("Vendidos").SemiBold(); + header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignRight().Text("Imp. El Día").SemiBold(); + header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignRight().Text("Imp. El Plata").SemiBold(); + header.Cell().BorderBottom(1).BorderColor(Colors.Grey.Medium).Padding(4).AlignRight().Text("Importe Total").SemiBold(); + }); + + foreach (var item in Model.Detalles.OrderBy(d => d.Canilla)) + { + table.Cell().Padding(3).PaddingRight(10).Text(item.Canilla); + table.Cell().Padding(3).AlignCenter().Text(item.ElDia?.ToString("N0") ?? "0"); + table.Cell().Padding(3).AlignCenter().Text(item.ElPlata?.ToString("N0") ?? "0"); + table.Cell().Padding(3).AlignCenter().Text(item.Vendidos?.ToString("N0") ?? "0"); + table.Cell().Padding(3).AlignRight().Text(item.ImporteElDia?.ToString("C", CultureAr) ?? "$ 0.00"); + table.Cell().Padding(3).AlignRight().Text(item.ImporteElPlata?.ToString("C", CultureAr) ?? "$ 0.00"); + table.Cell().Padding(3).AlignRight().Text(item.ImporteTotal?.ToString("C", CultureAr) ?? "$ 0.00"); + } + + // Fila de Totales + var totalElDia = Model.Detalles.Sum(x => x.ElDia ?? 0); + var totalElPlata = Model.Detalles.Sum(x => x.ElPlata ?? 0); + var totalVendidos = Model.Detalles.Sum(x => x.Vendidos ?? 0); + var totalImpElDia = Model.Detalles.Sum(x => x.ImporteElDia ?? 0); + var totalImpElPlata = Model.Detalles.Sum(x => x.ImporteElPlata ?? 0); + var totalImpTotal = Model.Detalles.Sum(x => x.ImporteTotal ?? 0); + + table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).Text("Totales").SemiBold(); + table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignCenter().Text(t => t.Span(totalElDia.ToString("N0")).SemiBold()); + table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignCenter().Text(t => t.Span(totalElPlata.ToString("N0")).SemiBold()); + table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignCenter().Text(t => t.Span(totalVendidos.ToString("N0")).SemiBold()); + table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignRight().Text(t => t.Span(totalImpElDia.ToString("C", CultureAr)).SemiBold()); + table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignRight().Text(t => t.Span(totalImpElPlata.ToString("C", CultureAr)).SemiBold()); + table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).PaddingTop(4).AlignRight().Text(t => t.Span(totalImpTotal.ToString("C", CultureAr)).SemiBold()); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanMensualDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanMensualDocument.cs new file mode 100644 index 0000000..6c181fc --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanMensualDocument.cs @@ -0,0 +1,113 @@ +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 ListadoDistCanMensualDocument : IDocument + { + public ListadoDistCanMensualViewModel Model { get; } + private static readonly CultureInfo CultureAr = new CultureInfo("es-AR"); + + public ListadoDistCanMensualDocument(ListadoDistCanMensualViewModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + + public void Compose(IDocumentContainer container) + { + container.Page(page => + { + page.Margin(1.5f, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10)); + + 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().AlignCenter().Text("Listado de Distribución Mensual").SemiBold().FontSize(14); + column.Item().AlignCenter().Text(Model.TipoDestinatario).FontSize(12); + + column.Item().PaddingTop(5, Unit.Millimetre).Row(row => + { + row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); }); + row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(1, Unit.Centimetre).Column(column => + { + column.Spacing(10); + + // Agrupamos los datos por Canilla + var gruposPorCanilla = Model.Detalles.GroupBy(d => d.Canilla); + + // Creamos una tabla principal que contendrá todo + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(2.5f); // Canilla / Publicación + columns.RelativeColumn(); // Llevados + columns.RelativeColumn(); // Devueltos + columns.RelativeColumn(1.2f); // Importe + }); + + // Encabezado principal de la tabla + table.Header(header => + { + header.Cell().BorderBottom(1.5f).BorderColor(Colors.Black).Padding(4).Text("Canilla / Publicación").SemiBold(); + header.Cell().BorderBottom(1.5f).BorderColor(Colors.Black).Padding(4).AlignRight().Text("Llevados").SemiBold(); + header.Cell().BorderBottom(1.5f).BorderColor(Colors.Black).Padding(4).AlignRight().Text("Devueltos").SemiBold(); + header.Cell().BorderBottom(1.5f).BorderColor(Colors.Black).Padding(4).AlignRight().Text("Importe").SemiBold(); + }); + + // Iteramos sobre cada grupo de canilla + foreach (var grupo in gruposPorCanilla) + { + // Fila del nombre del Canilla + table.Cell().ColumnSpan(4).PaddingTop(8).Text(grupo.Key).SemiBold(); + + // Filas de detalle para ese canilla + foreach (var detalle in grupo) + { + table.Cell().PaddingLeft(15).Padding(2).Text(detalle.Publicacion); + table.Cell().Padding(2).AlignRight().Text(detalle.TotalCantSalida?.ToString("N0") ?? "0"); + table.Cell().Padding(2).AlignRight().Text(detalle.TotalCantEntrada?.ToString("N0") ?? "0"); + table.Cell().Padding(2).AlignRight().Text(detalle.TotalRendir?.ToString("C", CultureAr) ?? "$ 0.00"); + } + + // Fila de total por canilla + var totalRendirCanilla = grupo.Sum(d => d.TotalRendir ?? 0); + table.Cell().ColumnSpan(3).BorderTop(1.5f).BorderColor(Colors.Black).AlignRight().Padding(2).Text("A Rendir").SemiBold(); + table.Cell().BorderTop(1.5f).BorderColor(Colors.Black).AlignRight().Padding(2).Text(t => t.Span(totalRendirCanilla.ToString("C", CultureAr)).SemiBold()); + } + + // --- Fila de TOTALES GENERALES --- + var totalGeneralLlevados = Model.Detalles.Sum(d => d.TotalCantSalida ?? 0); + var totalGeneralDevueltos = Model.Detalles.Sum(d => d.TotalCantEntrada ?? 0); + var totalGeneralRendir = Model.Detalles.Sum(d => d.TotalRendir ?? 0); + + table.Cell().BorderTop(2).BorderColor(Colors.Black).PaddingTop(5).Text("TOTALES").ExtraBold(); + table.Cell().BorderTop(2).BorderColor(Colors.Black).PaddingTop(5).AlignRight().Text(t => t.Span(totalGeneralLlevados.ToString("N0")).ExtraBold()); + table.Cell().BorderTop(2).BorderColor(Colors.Black).PaddingTop(5).AlignRight().Text(t => t.Span(totalGeneralDevueltos.ToString("N0")).ExtraBold()); + table.Cell().BorderTop(2).BorderColor(Colors.Black).PaddingTop(5).AlignRight().Text(t => t.Span(totalGeneralRendir.ToString("C", CultureAr)).ExtraBold()); + }); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanillasImporteDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanillasImporteDocument.cs new file mode 100644 index 0000000..e26f372 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistCanillasImporteDocument.cs @@ -0,0 +1,103 @@ +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 ListadoDistCanillasImporteDocument : IDocument + { + public ListadoDistCanillasImporteViewModel Model { get; } + private static readonly CultureInfo CultureAr = new CultureInfo("es-AR"); + + public ListadoDistCanillasImporteDocument(ListadoDistCanillasImporteViewModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + + public void Compose(IDocumentContainer container) + { + container.Page(page => + { + page.Margin(1.5f, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10)); + + 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().AlignCenter().Text("Listado de Distribución con Importes").SemiBold().FontSize(14); + column.Item().AlignCenter().Text(Model.TipoDestinatario).FontSize(12); + column.Item().AlignCenter().Text(Model.NombrePublicacion).FontSize(12); + + column.Item().PaddingTop(5, Unit.Millimetre).Row(row => + { + row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); }); + row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(1, Unit.Centimetre).Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(); // Fecha + columns.RelativeColumn(); // Llevados + columns.RelativeColumn(); // Devueltos + columns.RelativeColumn(); // Vendidos + columns.RelativeColumn(1.5f); // Importe Publicación + columns.RelativeColumn(1.5f); // A Rendir + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Fecha"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Vendidos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Imp. Publicación"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("A Rendir"); + }); + + foreach (var item in Model.Detalles) + { + table.Cell().Border(1).Padding(3).Text(item.Fecha); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Llevados.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Devueltos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Vendidos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalRendirPublicacion.ToString("C", CultureAr)); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalRendirGeneral.ToString("C", CultureAr)); + } + + // Fila de Totales + var totalLlevados = Model.Detalles.Sum(x => x.Llevados); + var totalDevueltos = Model.Detalles.Sum(x => x.Devueltos); + var totalVendidos = Model.Detalles.Sum(x => x.Vendidos); + var totalRendirPub = Model.Detalles.Sum(x => x.TotalRendirPublicacion); + var totalRendirGral = Model.Detalles.Sum(x => x.TotalRendirGeneral); + + var boldStyle = TextStyle.Default.SemiBold(); + + table.Cell().Border(1).Padding(3); // Celda vacía + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(totalLlevados.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(totalDevueltos.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(totalVendidos.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(totalRendirPub.ToString("C", CultureAr)).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(totalRendirGral.ToString("C", CultureAr)).Style(boldStyle)); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionCanillasDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionCanillasDocument.cs new file mode 100644 index 0000000..f414595 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionCanillasDocument.cs @@ -0,0 +1,179 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Collections.Generic; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class ListadoDistribucionCanillasDocument : IDocument + { + public ListadoDistribucionCanillasViewModel Model { get; } + + public ListadoDistribucionCanillasDocument(ListadoDistribucionCanillasViewModel 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(10)); + + 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().AlignCenter().Text("Listado de Distribución - Canillitas").SemiBold().FontSize(14); + column.Item().AlignCenter().Text(Model.NombrePublicacion).FontSize(12); + + column.Item().PaddingTop(5, Unit.Millimetre).Row(row => + { + row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); }); + row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(8, Unit.Millimetre).Column(column => + { + column.Spacing(15); + + column.Item().Text("Distribución").SemiBold().FontSize(12); + column.Item().Element(ComposeDetalleDiarioTable); + + column.Item().PaddingTop(5, Unit.Millimetre); + + column.Item().Text("Promedios").SemiBold().FontSize(12); + column.Item().Element(ComposePromediosTable); + }); + } + + void ComposeDetalleDiarioTable(IContainer container) + { + container.Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(50); // Día + columns.RelativeColumn(); // Llevados + columns.RelativeColumn(); // Devueltos + columns.RelativeColumn(); // Venta Neta + columns.RelativeColumn(2); // Promedio (columna vacía en el original) + columns.RelativeColumn(2); // % Devolución + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Venta Neta"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Promedio"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("% Devolución"); + }); + + decimal ventaNetaAcumulada = 0; + int conteoDias = 0; + + foreach (var item in Model.DetalleDiario.OrderBy(x => x.Dia)) + { + var ventaNetaDia = item.Llevados - item.Devueltos; + ventaNetaAcumulada += ventaNetaDia; + conteoDias++; + var promedio = ventaNetaAcumulada / conteoDias; + var porcDevolucion = item.Llevados > 0 ? (decimal)item.Devueltos * 100 / item.Llevados : 0; + + table.Cell().Border(1).Padding(3).Text(item.Dia.ToString()); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Llevados.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Devueltos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(ventaNetaDia.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(promedio.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(porcDevolucion.ToString("F2")); + } + + var totalVentaNeta = Model.TotalesDetalleDiario.Llevados - Model.TotalesDetalleDiario.Devueltos; + var totalPorcDevolucion = Model.TotalesDetalleDiario.Llevados > 0 ? (decimal)Model.TotalesDetalleDiario.Devueltos * 100 / Model.TotalesDetalleDiario.Llevados : 0; + + table.Cell().Border(1).Padding(3); // Celda vacía + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.TotalesDetalleDiario.Llevados.ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.TotalesDetalleDiario.Devueltos.ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalVentaNeta.ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span((ventaNetaAcumulada / (conteoDias > 0 ? conteoDias : 1)).ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalPorcDevolucion.ToString("F2")).SemiBold()); + }); + } + + void ComposePromediosTable(IContainer container) + { + container.Column(column => + { + column.Item().PaddingBottom(5).Text("Promedios por Día de Semana").SemiBold().FontSize(11); + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(1.5f); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(1.2f); + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día Semana"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cant. Días"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Ventas"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("% Devolución"); + }); + + var dayOrder = new Dictionary { { "Lunes", 1 }, { "Martes", 2 }, { "Miércoles", 3 }, { "Jueves", 4 }, { "Viernes", 5 }, { "Sábado", 6 }, { "Domingo", 7 } }; + + 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; + + 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.Promedio_Llevados.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Promedio_Devueltos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Promedio_Ventas.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(porcDevolucion.ToString("F2") + "%"); + } + + // --- SECCIÓN AÑADIDA PARA LA FILA "GENERAL" --- + var general = Model.PromedioGeneral; + if (general != null) + { + var porcDevolucionGeneral = general.Promedio_Llevados > 0 ? (decimal)general.Promedio_Devueltos * 100 / general.Promedio_Llevados : 0; + 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).AlignRight().Text(text => text.Span(general.Cant.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_Ventas.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(porcDevolucionGeneral.ToString("F2") + "%").Style(boldStyle)); + } + }); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionDistribuidoresDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionDistribuidoresDocument.cs new file mode 100644 index 0000000..185df88 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionDistribuidoresDocument.cs @@ -0,0 +1,178 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Collections.Generic; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class ListadoDistribucionDistribuidoresDocument : IDocument + { + public ListadoDistribucionDistribuidoresViewModel Model { get; } + + public ListadoDistribucionDistribuidoresDocument(ListadoDistribucionDistribuidoresViewModel 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(10)); + + 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().AlignCenter().Text("Listado de Distribución").SemiBold().FontSize(14); + column.Item().AlignCenter().Text(Model.NombreDistribuidor).FontSize(12); + column.Item().AlignCenter().Text(Model.NombrePublicacion).FontSize(11); + + column.Item().PaddingTop(5, Unit.Millimetre).Row(row => + { + row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); }); + row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(8, Unit.Millimetre).Column(column => + { + column.Spacing(15); + column.Item().Text("Distribución").SemiBold().FontSize(12); + column.Item().Element(ComposeDetalleDiarioTable); + + column.Item().PaddingTop(5, Unit.Millimetre); + + column.Item().Text("Promedios").SemiBold().FontSize(12); + column.Item().Element(ComposePromediosTable); + }); + } + + void ComposeDetalleDiarioTable(IContainer container) + { + container.Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(50); columns.RelativeColumn(); + columns.RelativeColumn(); columns.RelativeColumn(); + columns.RelativeColumn(2); columns.RelativeColumn(2); + }); + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Venta Neta"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Promedio"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("% Devolución"); + }); + + decimal ventaNetaAcumulada = 0; + int conteoDias = 0; + + foreach (var item in Model.DetalleDiario.OrderBy(x => x.Dia)) + { + var llevados = item.Llevados ?? 0; + var devueltos = item.Devueltos ?? 0; + var ventaNetaDia = llevados - devueltos; + if(llevados > 0) + { + ventaNetaAcumulada += ventaNetaDia; + conteoDias++; + } + var promedio = conteoDias > 0 ? ventaNetaAcumulada / conteoDias : 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).AlignRight().Text(llevados.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(devueltos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(ventaNetaDia.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(promedio.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(porcDevolucion.ToString("F2") + "%"); + } + + var totalLlevados = Model.DetalleDiario.Sum(i => i.Llevados ?? 0); + var totalDevueltos = Model.DetalleDiario.Sum(i => i.Devueltos ?? 0); + var totalVentaNeta = totalLlevados - totalDevueltos; + var totalPorcDevolucion = totalLlevados > 0 ? (decimal)totalDevueltos * 100 / totalLlevados : 0; + + var boldStyle = TextStyle.Default.SemiBold(); + 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(totalDevueltos.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalVentaNeta.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span((ventaNetaAcumulada / (conteoDias > 0 ? conteoDias : 1)).ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(totalPorcDevolucion.ToString("F2") + "%").Style(boldStyle)); + }); + } + + void ComposePromediosTable(IContainer container) + { + var dayOrder = new Dictionary { { "Lunes", 1 }, { "Martes", 2 }, { "Miércoles", 3 }, { "Jueves", 4 }, { "Viernes", 5 }, { "Sábado", 6 }, { "Domingo", 7 }}; + + container.Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(1.5f); columns.RelativeColumn(); + columns.RelativeColumn(); columns.RelativeColumn(); + columns.RelativeColumn(); columns.RelativeColumn(1.2f); + }); + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cant. Días"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Ventas"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("% Devolución"); + }); + foreach (var item in Model.PromediosPorDia.Where(p => p.Dia != "General").OrderBy(d => dayOrder.GetValueOrDefault(d.Dia, 99))) + { + var llevados = item.Promedio_Llevados ?? 0; + var devueltos = item.Promedio_Devueltos ?? 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).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(devueltos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Promedio_Ventas?.ToString("N0") ?? "0"); + table.Cell().Border(1).Padding(3).AlignRight().Text(porcDevolucion.ToString("F2") + "%"); + } + + // --- FILA GENERAL --- + var general = Model.PromedioGeneral; + if (general != null) + { + var boldStyle = TextStyle.Default.SemiBold(); + var llevadosGeneral = general.Llevados ?? 0; // Usamos el total para el % + var devueltosGeneral = general.Devueltos ?? 0; // Usamos el total para el % + var porcDevolucionGeneral = llevadosGeneral > 0 ? (decimal)devueltosGeneral * 100 / llevadosGeneral : 0; + + 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.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_Ventas?.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(porcDevolucionGeneral.ToString("F2") + "%").Style(boldStyle)); + } + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionGeneralDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionGeneralDocument.cs new file mode 100644 index 0000000..5979f63 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/ListadoDistribucionGeneralDocument.cs @@ -0,0 +1,179 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Collections.Generic; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class ListadoDistribucionGeneralDocument : IDocument + { + public ListadoDistribucionGeneralViewModel Model { get; } + + public ListadoDistribucionGeneralDocument(ListadoDistribucionGeneralViewModel 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(10)); + + 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().AlignCenter().Text("Listado de Distribución General Mensual").SemiBold().FontSize(14); + column.Item().AlignCenter().Text(Model.NombrePublicacion).FontSize(12); + + column.Item().PaddingTop(1, Unit.Millimetre).Row(row => + { + row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); }); + row.RelativeItem().AlignRight().Text(text => { text.Span("Mes Consultado: ").SemiBold(); text.Span(Model.MesConsultado); }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(1, Unit.Millimetre).Column(column => + { + column.Spacing(20); + + if (Model.ResumenMensual.Any()) + { + column.Item().Element(ComposeResumenTable); + } + if (Model.PromediosPorDia.Any()) + { + column.Item().Element(ComposePromediosTable); + } + }); + } + + void ComposeResumenTable(IContainer container) + { + container.Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(40); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Tirada"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Sin Cargo"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Perdidos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Vendidos"); + }); + + foreach (var item in Model.ResumenMensual.OrderBy(x => x.Fecha)) + { + table.Cell().Border(1).Padding(3).Text(item.Fecha.Day.ToString()); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.CantidadTirada.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.SinCargo.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Perdidos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Llevados.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Devueltos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.Vendidos.ToString("N0")); + } + + table.Cell().Border(1).Padding(3); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.CantidadTirada).ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.SinCargo).ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.Perdidos).ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.Llevados).ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.Devueltos).ToString("N0")).SemiBold()); + table.Cell().Border(1).Padding(3).AlignRight().Text(t => t.Span(Model.ResumenMensual.Sum(x => x.Vendidos).ToString("N0")).SemiBold()); + }); + } + + void ComposePromediosTable(IContainer container) + { + container.Column(column => + { + // --- TÍTULO DE LA TABLA DE PROMEDIOS --- + column.Item().PaddingBottom(5).AlignCenter().Text("Promedios Diarios de Distribución").SemiBold(); + + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(1.2f); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + columns.RelativeColumn(); + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Día"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cant. Días"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Tirada"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Sin Cargo"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Perdidos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Llevados"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Devueltos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Vendidos"); + }); + + var dayOrder = new Dictionary { { "Lunes", 1 }, { "Martes", 2 }, { "Miércoles", 3 }, { "Jueves", 4 }, { "Viernes", 5 }, { "Sábado", 6 }, { "Domingo", 7 } }; + + // Mostramos los promedios por día de la semana + foreach (var item in Model.PromediosPorDia.OrderBy(d => dayOrder.GetValueOrDefault(d.Dia, 99))) + { + table.Cell().Border(1).Padding(3).Text(item.Dia); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.CantidadDias.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioTirada.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioSinCargo.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioPerdidos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioLlevados.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioDevueltos.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioVendidos.ToString("N0")); + } + + // --- FILA GENERAL CON DATOS CALCULADOS DEL VIEWMODEL --- + var general = Model.PromedioGeneral; + if (general != null) + { + 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).AlignRight().Text(text => text.Span(general.CantidadDias.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioTirada.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioSinCargo.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioPerdidos.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioLlevados.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioDevueltos.ToString("N0")).Style(boldStyle)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(general.PromedioVendidos.ToString("N0")).Style(boldStyle)); + } + }); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/MovimientoBobinasDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/MovimientoBobinasDocument.cs new file mode 100644 index 0000000..aba134a --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/MovimientoBobinasDocument.cs @@ -0,0 +1,122 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class MovimientoBobinasDocument : IDocument + { + public MovimientoBobinasViewModel Model { get; } + + public MovimientoBobinasDocument(MovimientoBobinasViewModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + public DocumentSettings GetSettings() => DocumentSettings.Default; + + public void Compose(IDocumentContainer container) + { + container + .Page(page => + { + // Configuramos la página en modo apaisado (landscape) + page.Size(PageSizes.A4.Landscape()); + page.Margin(1, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontFamily("Roboto").FontSize(9)); // Un poco más pequeño por la cantidad de columnas + + 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.Spacing(5); + column.Item().AlignCenter().Text("Reporte de Movimiento de Bobinas").SemiBold().FontSize(14); + column.Item().AlignCenter().Text($"Planta: {Model.NombrePlanta}").FontSize(12); + + column.Item().PaddingTop(2, Unit.Millimetre).Row(row => + { + row.RelativeItem().Column(col => + { + col.Item().Text(text => + { + text.Span("Fecha del Reporte: ").SemiBold(); + text.Span(Model.FechaReporte); + }); + col.Item().Text($"Periodo Consultado: Desde {Model.FechaDesde} Hasta {Model.FechaHasta}"); + }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(1, Unit.Centimetre).Table(table => + { + // Definimos 11 columnas. Usamos una combinación de relativas y constantes + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(2.5f); // Tipo + columns.ConstantColumn(50); // Cant. Inicial + columns.ConstantColumn(60); // Kg Iniciales + columns.ConstantColumn(50); // Compradas + columns.ConstantColumn(60); // Kg Comprados + columns.ConstantColumn(50); // Consumidas + columns.ConstantColumn(60); // Kg Consumidos + columns.ConstantColumn(50); // Dañadas + columns.ConstantColumn(60); // Kg Dañados + columns.ConstantColumn(50); // Cant. Final + columns.ConstantColumn(60); // Kg Final + }); + + // Encabezado de la tabla + table.Header(header => + { + // Celda por celda para control total + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).Text("Tipo"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Cant. Inicial"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Kg Inicial"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Compradas"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Kg Comprados"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Consumidas"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Kg Consumidos"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Dañadas"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Kg Dañados"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Cant. Final"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(2).AlignCenter().Text("Kg Final"); + }); + + // Filas de datos + foreach (var item in Model.Movimientos) + { + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).Text(item.TipoBobina); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.BobinasIniciales.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.KilosIniciales.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.BobinasCompradas.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.KilosComprados.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.BobinasConsumidas.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.KilosConsumidos.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.BobinasDaniadas.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.KilosDaniados.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.BobinasFinales.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(2).AlignCenter().Text(item.KilosFinales.ToString("N0")); + } + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/MovimientoBobinasEstadoDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/MovimientoBobinasEstadoDocument.cs new file mode 100644 index 0000000..1c98029 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/MovimientoBobinasEstadoDocument.cs @@ -0,0 +1,129 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class MovimientoBobinasEstadoDocument : IDocument + { + public MovimientoBobinasEstadoViewModel Model { get; } + + public MovimientoBobinasEstadoDocument(MovimientoBobinasEstadoViewModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + public DocumentSettings GetSettings() => DocumentSettings.Default; + + public void Compose(IDocumentContainer container) + { + container + .Page(page => + { + page.Margin(1, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontFamily("Roboto").FontSize(10)); + + 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.Spacing(5); + column.Item().AlignCenter().Text("Reporte de Movimiento de Bobinas por Estados").SemiBold().FontSize(14); + column.Item().AlignCenter().Text($"Planta: {Model.NombrePlanta}").FontSize(12); + + column.Item().PaddingTop(2, Unit.Millimetre).Row(row => + { + row.RelativeItem().Column(col => + { + col.Item().Text(text => + { + text.Span("Fecha del Reporte: ").SemiBold(); + text.Span(Model.FechaReporte); + }); + col.Item().Text($"Periodo Consultado: Desde {Model.FechaDesde} Hasta {Model.FechaHasta}"); + }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(1, Unit.Centimetre).Column(column => + { + // Primera tabla: Detalle de Movimientos + column.Item().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(3); // Tipo Bobina + columns.RelativeColumn(2); // Remito + columns.ConstantColumn(80); // Fecha + columns.ConstantColumn(60); // Cantidad + columns.RelativeColumn(2); // Tipo Movimiento + }); + + table.Header(header => + { + header.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text("Tipo Bobina"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text("N° Remito"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Fecha Mov."); + header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Cantidad"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text("Tipo Movimiento"); + }); + + foreach (var item in Model.Detalles) + { + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).Text(item.TipoBobina); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).Text(item.NumeroRemito); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignCenter().Text(item.FechaMovimiento.ToString("dd/MM/yyyy")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(item.Cantidad.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).Text(item.TipoMovimiento); + } + }); + + // Espacio entre tablas + column.Item().PaddingTop(1, Unit.Centimetre); + + // Segunda tabla: Totales + column.Item().AlignLeft().Table(table => // Alineamos la tabla a la izquierda para que no ocupe todo el ancho + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(120); // Tipo Movimiento + columns.ConstantColumn(80); // Total Bobinas + columns.ConstantColumn(80); // Total Kilos + }); + + table.Header(header => + { + header.Cell().Background(Colors.Grey.Lighten3).Padding(4).Text("Totales por Movimiento"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Total Bobinas"); + header.Cell().Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Total Kilos"); + }); + + foreach (var total in Model.Totales) + { + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).Text(total.TipoMovimiento); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(total.TotalBobinas.ToString("N0")); + table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2).Padding(4).AlignRight().Text(total.TotalKilos.ToString("N0")); + } + }); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/NovedadesCanillasDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/NovedadesCanillasDocument.cs new file mode 100644 index 0000000..42ce37a --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/NovedadesCanillasDocument.cs @@ -0,0 +1,144 @@ +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 NovedadesCanillasDocument : IDocument + { + public NovedadesCanillasViewModel Model { get; } + private static readonly CultureInfo CultureAr = new CultureInfo("es-AR"); + + public NovedadesCanillasDocument(NovedadesCanillasViewModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + + public void Compose(IDocumentContainer container) + { + container.Page(page => + { + page.Margin(1.5f, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(10)); + + 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().AlignCenter().Text("Listado de Novedades - Canillas").SemiBold().FontSize(16); + + column.Item().PaddingTop(5, Unit.Millimetre).Row(row => + { + row.RelativeItem().Text(text => { text.Span("Fecha de Reporte: ").SemiBold(); text.Span(Model.FechaReporte); }); + row.RelativeItem().AlignRight().Text(text => { text.Span("Periodo: ").SemiBold(); text.Span($"{Model.FechaDesde} - {Model.FechaHasta}"); }); + }); + + column.Item().PaddingTop(5).Border(1).Background(Colors.Grey.Lighten3).AlignCenter().Padding(2).Text(Model.NombreEmpresa).SemiBold(); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(1, Unit.Centimetre).Column(column => + { + column.Spacing(20); + + if (Model.ResumenCanillas.Any()) + { + column.Item().Element(ComposeResumenTable); + } + + if (Model.DetallesNovedades.Any()) + { + column.Item().Element(ComposeDetallesNovedadesTable); + } + }); + } + + void ComposeResumenTable(IContainer container) + { + container.Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(3); // Canilla + columns.RelativeColumn(1); // Legajo + columns.RelativeColumn(1); // Faltas + columns.RelativeColumn(1); // Francos + columns.RelativeColumn(1.5f); // Comisiones + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Canilla"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Legajo"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Faltas"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignCenter().Text("Francos"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Comisiones"); + }); + + foreach (var item in Model.ResumenCanillas.OrderBy(x => x.Canilla)) + { + table.Cell().Border(1).Padding(3).Text(item.Canilla); + table.Cell().Border(1).Padding(3).Text(item.Legajo?.ToString() ?? "-"); + table.Cell().Border(1).Padding(3).AlignCenter().Text(item.Faltas?.ToString("N0") ?? "0"); + table.Cell().Border(1).Padding(3).AlignCenter().Text(item.Francos?.ToString("N0") ?? "0"); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalRendir?.ToString("C", CultureAr) ?? "$ 0.00"); + } + + // Fila de Totales + table.Cell().ColumnSpan(4).Border(1).Padding(3).AlignRight().Text("Total").SemiBold(); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.ResumenCanillas.Sum(x => x.TotalRendir ?? 0).ToString("C", CultureAr)).SemiBold()); + }); + } + + void ComposeDetallesNovedadesTable(IContainer container) + { + container.Column(column => + { + column.Item().PaddingTop(10).Text("Otras Novedades").SemiBold().FontSize(12); + column.Item().PaddingTop(5).Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(2); // Nombre + columns.ConstantColumn(80); // Fecha + columns.RelativeColumn(4); // Detalle + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Nombre"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Fecha"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Detalle"); + }); + + // Agrupamos por canillita para mostrar su nombre una sola vez + foreach (var grupo in Model.DetallesNovedades.GroupBy(x => x.NomApe)) + { + // Celda con el nombre del canillita, abarcando todas las filas de sus novedades + table.Cell().RowSpan((uint)grupo.Count()).Border(1).Padding(3).Text(grupo.Key); + + // Iteramos sobre las novedades del grupo + foreach (var detalle in grupo.OrderBy(d => d.Fecha)) + { + table.Cell().Border(1).Padding(3).Text(detalle.Fecha.ToString("dd/MM/yyyy")); + table.Cell().Border(1).Padding(3).Text(detalle.Detalle); + } + } + }); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/TiradasPublicacionesSeccionesDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/TiradasPublicacionesSeccionesDocument.cs new file mode 100644 index 0000000..f981ca3 --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/TiradasPublicacionesSeccionesDocument.cs @@ -0,0 +1,99 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class TiradasPublicacionesSeccionesDocument : IDocument + { + public TiradasPublicacionesSeccionesViewModel Model { get; } + + public TiradasPublicacionesSeccionesDocument(TiradasPublicacionesSeccionesViewModel 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.Spacing(5); + column.Item().AlignCenter().Text("Reporte de Tiradas por Publicación Mensual").SemiBold().FontSize(14); + + // Título secundario dinámico + string subTitle = Model.EsConsolidado + ? $"Consolidado - Publicación: {Model.NombrePublicacion}" + : $"Planta: {Model.NombrePlanta} - Publicación: {Model.NombrePublicacion}"; + column.Item().AlignCenter().Text(subTitle).FontSize(12); + + column.Item().PaddingTop(5, Unit.Millimetre).Row(row => + { + row.RelativeItem().Text(text => { text.Span("Fecha del Reporte: ").SemiBold(); text.Span(Model.FechaReporte); }); + row.RelativeItem().AlignRight().Text(text => { text.Span("Mes Consultado: ").SemiBold(); text.Span(Model.MesConsultado); }); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(5, Unit.Millimetre).Table(table => + { + table.ColumnsDefinition(columns => + { + columns.RelativeColumn(2.5f); // Nombre Seccion + columns.RelativeColumn(1.5f); // Páginas Impresas + columns.RelativeColumn(1); // Total Ediciones + columns.RelativeColumn(1.5f); // Pág. Por Edición + columns.RelativeColumn(1.2f); // Total Ejemplares + columns.RelativeColumn(1.5f); // Pág. Ejemplar + }); + + table.Header(header => + { + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).Text("Nombre Sección"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Páginas Impresas"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Total Ediciones"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Pág/Edición"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Total Ejemplares"); + header.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(4).AlignRight().Text("Prom. Pág/Ejemplar"); + }); + + foreach (var item in Model.Detalles.OrderBy(x => x.NombreSeccion)) + { + table.Cell().Border(1).Padding(3).Text(item.NombreSeccion); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalPaginasImpresas.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.CantidadTiradas.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalPaginasEjemplares.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.TotalEjemplares.ToString("N0")); + table.Cell().Border(1).Padding(3).AlignRight().Text(item.PromedioPaginasPorEjemplar.ToString("N0")); + } + + // Fila de Totales + var style = TextStyle.Default.SemiBold(); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span("Totales").Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.TotalPaginasImpresas).ToString("N0")).Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.CantidadTiradas).ToString("N0")).Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.TotalPaginasEjemplares).ToString("N0")).Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.TotalEjemplares).ToString("N0")).Style(style)); + table.Cell().Border(1).Padding(3).AlignRight().Text(text => text.Span(Model.Detalles.Sum(x => x.PromedioPaginasPorEjemplar).ToString("N0")).Style(style)); + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaElDiaDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaElDiaDocument.cs new file mode 100644 index 0000000..2dffb8e --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaElDiaDocument.cs @@ -0,0 +1,89 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class VentaMensualSecretariaElDiaDocument : IDocument + { + public VentaMensualSecretariaElDiaViewModel Model { get; } + + public VentaMensualSecretariaElDiaDocument(VentaMensualSecretariaElDiaViewModel 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(11)); + + 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.Spacing(5); + column.Item().AlignCenter().Text("VENTA DIARIO EL DÍA").SemiBold().FontSize(16); + + column.Item().AlignCenter().Text(text => + { + text.Span("Fecha Consultada: Desde ").SemiBold().FontSize(12); + text.Span(Model.FechaDesde).FontSize(12); + text.Span(" Hasta ").SemiBold().FontSize(12); + text.Span(Model.FechaHasta).FontSize(12); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(5, Unit.Millimetre).Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(50); // Día + columns.RelativeColumn(); // Canillas + columns.RelativeColumn(); // Tirajes + columns.RelativeColumn(); // Ventas + columns.RelativeColumn(); // Accionistas + columns.RelativeColumn(); // Total Coop. + columns.RelativeColumn(); // Total Gral. + }); + + table.Header(header => + { + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("DÍA").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("CANILLAS").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TIRAJES").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("VENTAS").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("ACCIONISTAS").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TOTAL COOPERATIVA").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TOTAL").FontColor(Colors.White).SemiBold()); + }); + + foreach (var item in Model.VentasDiarias.OrderBy(x => x.Dia)) + { + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Dia.ToString()); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.CantidadCanillas.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Tirajes.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Ventas.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Accionistas.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TotalCooperativa.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.TotalGeneral.ToString("N0")).SemiBold()); + } + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaElPlataDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaElPlataDocument.cs new file mode 100644 index 0000000..f5c0f0c --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaElPlataDocument.cs @@ -0,0 +1,89 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class VentaMensualSecretariaElPlataDocument : IDocument + { + public VentaMensualSecretariaElPlataViewModel Model { get; } + + public VentaMensualSecretariaElPlataDocument(VentaMensualSecretariaElPlataViewModel 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(11)); + + 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.Spacing(5); + column.Item().AlignCenter().Text("VENTA DIARIO EL PLATA").SemiBold().FontSize(16); + + column.Item().AlignCenter().Text(text => + { + text.Span("Fecha Consultada: Desde ").SemiBold().FontSize(12); + text.Span(Model.FechaDesde).FontSize(12); + text.Span(" Hasta ").SemiBold().FontSize(12); + text.Span(Model.FechaHasta).FontSize(12); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(5, Unit.Millimetre).Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(50); // Día + columns.RelativeColumn(); // Tirada Coop + columns.RelativeColumn(); // Devolución Coop + columns.RelativeColumn(); // Venta Coop + columns.RelativeColumn(); // Tirada Canillas + columns.RelativeColumn(); // Venta Canillas + columns.RelativeColumn(); // Total + }); + + table.Header(header => + { + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("DÍA").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TIRADA COOP.").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("DEVOLUCIÓN COOP.").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("VENTA COOP.").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TIRADA CANILLAS").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("VENTA CANILLAS (TOTAL)").FontColor(Colors.White).SemiBold()); + header.Cell().Background(Colors.Black).Border(1).BorderColor(Colors.White).Padding(4).AlignCenter().Text(text => text.Span("TOTAL").FontColor(Colors.White).SemiBold()); + }); + + foreach (var item in Model.VentasDiarias.OrderBy(x => x.Dia)) + { + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Dia.ToString()); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaCoop.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.DevolucionCoop.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.VentaCoop.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaCan.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.VentaCan.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.Total.ToString("N0")).SemiBold()); + } + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaTirDevoDocument.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaTirDevoDocument.cs new file mode 100644 index 0000000..dc02ced --- /dev/null +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/PdfTemplates/VentaMensualSecretariaTirDevoDocument.cs @@ -0,0 +1,116 @@ +using GestionIntegral.Api.Dtos.Reportes.ViewModels; +using QuestPDF.Elements.Table; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using System.Linq; + +namespace GestionIntegral.Api.Controllers.Reportes.PdfTemplates +{ + public class VentaMensualSecretariaTirDevoDocument : IDocument + { + public VentaMensualSecretariaTirDevoViewModel Model { get; } + + public VentaMensualSecretariaTirDevoDocument(VentaMensualSecretariaTirDevoViewModel model) + { + Model = model; + } + + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + + public void Compose(IDocumentContainer container) + { + container.Page(page => + { + // CORRECCIÓN: Se aplica Landscape() al tamaño de página. + page.Size(PageSizes.A4.Landscape()); + page.Margin(1, Unit.Centimetre); + page.DefaultTextStyle(x => x.FontFamily("Arial").FontSize(12)); + + 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().AlignCenter().Text("TIRADA Y DEVOLUCIÓN").SemiBold().FontSize(18); + column.Item().AlignCenter().Text(text => + { + text.Span("Fecha Consultada: Desde ").SemiBold().FontSize(14); + text.Span(Model.FechaDesde).FontSize(14); + text.Span(" Hasta ").SemiBold().FontSize(14); + text.Span(Model.FechaHasta).FontSize(14); + }); + }); + } + + void ComposeContent(IContainer container) + { + container.PaddingTop(5, Unit.Millimetre).Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(40); + columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); // EL DÍA + columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); // POPULAR + columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); // CLARÍN + columns.RelativeColumn(); columns.RelativeColumn(); columns.RelativeColumn(); // LA NACIÓN + }); + + table.Header(header => + { + // CORRECCIÓN: La sintaxis de VerticalAlign es un método. + header.Cell().RowSpan(2).Border(1).Background(Colors.Black).AlignCenter().AlignMiddle().Text(text => text.Span("Día").FontColor(Colors.White).SemiBold()); + + header.Cell().ColumnSpan(3).Border(1).Background(Colors.Black).AlignCenter().Text(text => text.Span("EL DÍA").FontColor(Colors.White).SemiBold()); + header.Cell().ColumnSpan(3).Border(1).Background(Colors.Black).AlignCenter().Text(text => text.Span("POPULAR").FontColor(Colors.White).SemiBold()); + header.Cell().ColumnSpan(3).Border(1).Background(Colors.Black).AlignCenter().Text(text => text.Span("CLARÍN").FontColor(Colors.White).SemiBold()); + header.Cell().ColumnSpan(3).Border(1).Background(Colors.Black).AlignCenter().Text(text => text.Span("LA NACIÓN").FontColor(Colors.White).SemiBold()); + + // CORRECCIÓN: Se define una función local para crear y estilizar las celdas del sub-encabezado. + // Esto evita el error de "multiple child elements". + void SubHeaderCell(ITableCellContainer cell, string text) + { + cell.Border(1) + .Background(Colors.Black) + .AlignCenter() + .Text(txt => txt.Span(text).FontColor(Colors.White).SemiBold()); + } + + foreach (var _ in Enumerable.Range(0, 4)) + { + SubHeaderCell(header.Cell(), "TIRADA"); + SubHeaderCell(header.Cell(), "DEVOLUC"); + SubHeaderCell(header.Cell(), "VENTA"); + } + }); + + // Filas de datos (sin cambios) + foreach (var item in Model.VentasDiarias.OrderBy(x => x.Dia)) + { + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.Dia.ToString()); + + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaCoop.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.DevolucionCoop.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.VentaCoop.ToString("N0")).SemiBold()); + + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaPopular.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.DevolucionPopular.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.VentaPopular.ToString("N0")).SemiBold()); + + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaClarin.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.DevolucionClarin.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.VentaClarin.ToString("N0")).SemiBold()); + + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.TiradaNacion.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(item.DevolucionNacion.ToString("N0")); + table.Cell().Border(1).Padding(4).AlignCenter().Text(t => t.Span(item.VentaNacion.ToString("N0")).SemiBold()); + } + }); + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Controllers/Reportes/ReportesController.cs b/Backend/GestionIntegral.Api/Controllers/Reportes/ReportesController.cs index 5b1cc34..d099960 100644 --- a/Backend/GestionIntegral.Api/Controllers/Reportes/ReportesController.cs +++ b/Backend/GestionIntegral.Api/Controllers/Reportes/ReportesController.cs @@ -16,6 +16,7 @@ using GestionIntegral.Api.Services.Pdf; using GestionIntegral.Api.Dtos.Reportes.ViewModels; using GestionIntegral.Api.Controllers.Reportes.PdfTemplates; using QuestPDF.Infrastructure; +using System.Globalization; namespace GestionIntegral.Api.Controllers { @@ -100,10 +101,10 @@ namespace GestionIntegral.Api.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetReporteExistenciaPapelPdf( -[FromQuery] DateTime fechaDesde, -[FromQuery] DateTime fechaHasta, -[FromQuery] int? idPlanta, -[FromQuery] bool consolidado = false) + [FromQuery] DateTime fechaDesde, + [FromQuery] DateTime fechaHasta, + [FromQuery] int? idPlanta, + [FromQuery] bool consolidado = false) { if (!TienePermiso(PermisoVerReporteExistenciaPapel)) return Forbid(); @@ -185,32 +186,30 @@ namespace GestionIntegral.Api.Controllers { if (!TienePermiso(PermisoVerReporteMovimientoBobinas)) return Forbid(); - var (data, error) = await _reportesService.ObtenerMovimientoBobinasAsync(fechaDesde, fechaHasta, idPlanta); // <--- CORREGIDO + var (data, error) = await _reportesService.ObtenerMovimientoBobinasAsync(fechaDesde, fechaHasta, idPlanta); if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) { - return NotFound(new { message = "No hay datos para generar el PDF del movimiento de bobinas con los parámetros seleccionados." }); + return NotFound(new { message = "No hay datos para generar el PDF del movimiento de bobinas." }); } try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteMovimientoBobinas.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSMovimientoBobinas", data)); // El RDLC usa "DSMovimientoBobinas" - var planta = await _plantaRepository.GetByIdAsync(idPlanta); - var parameters = new List(); - parameters.Add(new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy"))); - parameters.Add(new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy"))); - parameters.Add(new ReportParameter("NomPlanta", planta?.Nombre ?? "N/A")); - report.SetParameters(parameters); + var viewModel = new MovimientoBobinasViewModel + { + Movimientos = data, + NombrePlanta = planta?.Nombre ?? $"Planta ID {idPlanta}", + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; + + var document = new MovimientoBobinasDocument(viewModel); + + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); - byte[] pdfBytes = report.Render("PDF"); string fileName = $"MovimientoBobinas_Planta{idPlanta}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } @@ -264,33 +263,31 @@ namespace GestionIntegral.Api.Controllers { if (!TienePermiso(PermisoVerReporteMovimientoBobinas)) return Forbid(); - var (detalle, totales, error) = await _reportesService.ObtenerMovimientoBobinasPorEstadoAsync(fechaDesde, fechaHasta, idPlanta); // <--- CORREGIDO + var (detalle, totales, error) = await _reportesService.ObtenerMovimientoBobinasPorEstadoAsync(fechaDesde, fechaHasta, idPlanta); if (error != null) return BadRequest(new { message = error }); if ((detalle == null || !detalle.Any()) && (totales == null || !totales.Any())) { - return NotFound(new { message = "No hay datos para generar el PDF del movimiento de bobinas por estado con los parámetros seleccionados." }); + return NotFound(new { message = "No hay datos para generar el PDF." }); } try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteMovimientoBobinasEstado.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSMovimientoBobinasEstado", detalle ?? new List())); - report.DataSources.Add(new ReportDataSource("DSMovimientoBobinasEstadoTotales", totales ?? new List())); - var planta = await _plantaRepository.GetByIdAsync(idPlanta); - var parameters = new List(); - parameters.Add(new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy"))); - parameters.Add(new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy"))); - parameters.Add(new ReportParameter("NomPlanta", planta?.Nombre ?? "N/A")); - report.SetParameters(parameters); + var viewModel = new MovimientoBobinasEstadoViewModel + { + Detalles = detalle ?? Enumerable.Empty(), + Totales = totales, + NombrePlanta = planta?.Nombre ?? $"Planta ID {idPlanta}", + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; + + var document = new MovimientoBobinasEstadoDocument(viewModel); + + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); - byte[] pdfBytes = report.Render("PDF"); string fileName = $"MovimientoBobinasEstado_Planta{idPlanta}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } @@ -338,11 +335,12 @@ namespace GestionIntegral.Api.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetListadoDistribucionGeneralPdf( [FromQuery] int idPublicacion, - [FromQuery] DateTime fechaDesde, // Usado para Mes y Año en el SP - [FromQuery] DateTime fechaHasta) // Usado para el nombre del archivo y potencialmente como parámetro si el SP cambia + [FromQuery] DateTime fechaDesde, + [FromQuery] DateTime fechaHasta) { if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); + // El servicio ya devuelve la tupla con los dos conjuntos de datos. var (resumen, promedios, error) = await _reportesService.ObtenerListadoDistribucionGeneralAsync(idPublicacion, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); @@ -353,24 +351,19 @@ namespace GestionIntegral.Api.Controllers try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteListadoDistribucionGeneral.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - // Los nombres de DataSet deben coincidir con los del RDLC. Basado en DSListadoDistribucion.txt - report.DataSources.Add(new ReportDataSource("DSResumenMensual", resumen ?? new List())); - report.DataSources.Add(new ReportDataSource("DSResumenMensualPorDiaSemana", promedios ?? new List())); - var publicacion = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); - var parameters = new List - { - new ReportParameter("NomPubli", publicacion?.Nombre ?? "N/A"), - new ReportParameter("FechaDesde", fechaDesde.ToString("MMMM 'de' yyyy")) // El RDLC espera un parámetro FechaDesde - }; - report.SetParameters(parameters); - byte[] pdfBytes = report.Render("PDF"); + var viewModel = new ListadoDistribucionGeneralViewModel + { + ResumenMensual = resumen ?? Enumerable.Empty(), + PromediosPorDia = promedios, + NombrePublicacion = publicacion?.Nombre ?? "N/A", + MesConsultado = fechaDesde.ToString("MMMM 'de' yyyy", new CultureInfo("es-ES")) + }; + + var document = new ListadoDistribucionGeneralDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + string fileName = $"ListadoDistribucionGeneral_Pub{idPublicacion}_{fechaDesde:yyyyMM}.pdf"; return File(pdfBytes, "application/pdf", fileName); } @@ -433,24 +426,20 @@ namespace GestionIntegral.Api.Controllers try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteListadoDistribucionCanillas.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSListadoDistribucion", simple ?? new List())); - report.DataSources.Add(new ReportDataSource("DSListadoDistribucionAgDias", promedios ?? new List())); - var publicacion = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); - var parameters = new List - { - new ReportParameter("NomPubli", publicacion?.Nombre ?? "N/A"), - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) - }; - report.SetParameters(parameters); - byte[] pdfBytes = report.Render("PDF"); + var viewModel = new ListadoDistribucionCanillasViewModel + { + DetalleDiario = simple ?? Enumerable.Empty(), + PromediosPorDia = promedios, + NombrePublicacion = publicacion?.Nombre ?? "N/A", + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; + + var document = new ListadoDistribucionCanillasDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + string fileName = $"ListadoDistribucionCanillas_Pub{idPublicacion}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } @@ -509,24 +498,20 @@ namespace GestionIntegral.Api.Controllers try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteListadoDistribucionCanImp.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCan", data)); - var publicacion = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); - var parameters = new List - { - new ReportParameter("NomPubli", publicacion?.Nombre ?? "N/A"), - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")), - new ReportParameter("CanAcc", esAccionista ? "1" : "0") - }; - report.SetParameters(parameters); - byte[] pdfBytes = report.Render("PDF"); + var viewModel = new ListadoDistCanillasImporteViewModel + { + Detalles = data, + NombrePublicacion = publicacion?.Nombre ?? "N/A", + TipoDestinatario = esAccionista ? "Accionistas" : "Canillitas", + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; + + var document = new ListadoDistCanillasImporteDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + string tipoVendedor = esAccionista ? "Accionistas" : "Canillitas"; string fileName = $"ListadoDistCanImp_Pub{idPublicacion}_{tipoVendedor}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); @@ -554,29 +539,39 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("venta-mensual-secretaria/el-dia/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetVentaMensualSecretariaElDiaPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { - if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // Asumiendo RR002 + if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); + var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElDiaAsync(fechaDesde, fechaHasta); + if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte." }); try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteVentaMensualSecretariaElDia.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + var viewModel = new VentaMensualSecretariaElDiaViewModel { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSListadoDistribucion", data)); // Basado en el RDLC - report.SetParameters(new[] { - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) - }); - byte[] pdfBytes = report.Render("PDF"); - return File(pdfBytes, "application/pdf", $"VentaMensualSecretaria_ElDia_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"); + VentasDiarias = data, + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; + + var document = new VentaMensualSecretariaElDiaDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + + string fileName = $"VentaMensualSecretaria_ElDia_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; + return File(pdfBytes, "application/pdf", fileName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error PDF VentaMensualSecretariaElDia."); + return StatusCode(500, "Error interno al generar el PDF del reporte."); } - catch (Exception ex) { _logger.LogError(ex, "Error PDF VentaMensualSecretariaElDia."); return StatusCode(500, "Error interno."); } } // GET: api/reportes/venta-mensual-secretaria/el-plata @@ -595,29 +590,39 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("venta-mensual-secretaria/el-plata/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetVentaMensualSecretariaElPlataPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { - if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // Asumiendo RR002 + if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); + var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaElPlataAsync(fechaDesde, fechaHasta); + if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte." }); try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteVentaMensualSecretariaElPlata.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + var viewModel = new VentaMensualSecretariaElPlataViewModel { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSListadoDistribucion", data)); // Basado en el RDLC - report.SetParameters(new[] { - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) - }); - byte[] pdfBytes = report.Render("PDF"); - return File(pdfBytes, "application/pdf", $"VentaMensualSecretaria_ElPlata_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"); + VentasDiarias = data, + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; + + var document = new VentaMensualSecretariaElPlataDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + + string fileName = $"VentaMensualSecretaria_ElPlata_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; + return File(pdfBytes, "application/pdf", fileName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error PDF VentaMensualSecretariaElPlata."); + return StatusCode(500, "Error interno al generar el PDF del reporte."); } - catch (Exception ex) { _logger.LogError(ex, "Error PDF VentaMensualSecretariaElPlata."); return StatusCode(500, "Error interno."); } } // GET: api/reportes/venta-mensual-secretaria/tirada-devolucion @@ -636,29 +641,39 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("venta-mensual-secretaria/tirada-devolucion/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetVentaMensualSecretariaTirDevoPdf([FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) { - if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); // Asumiendo RR002 + if (!TienePermiso(PermisoVerListadoDistribucion)) return Forbid(); + var (data, error) = await _reportesService.ObtenerVentaMensualSecretariaTirDevoAsync(fechaDesde, fechaHasta); + if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para el reporte." }); try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteVentaMensualSecretariaTirDevo.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + var viewModel = new VentaMensualSecretariaTirDevoViewModel { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSListadoDistribucion", data)); // Basado en el RDLC - report.SetParameters(new[] { - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) - }); - byte[] pdfBytes = report.Render("PDF"); - return File(pdfBytes, "application/pdf", $"VentaMensualSecretaria_TirDevo_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"); + VentasDiarias = data, + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; + + var document = new VentaMensualSecretariaTirDevoDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + + string fileName = $"VentaMensualSecretaria_TirDevo_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; + return File(pdfBytes, "application/pdf", fileName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error PDF VentaMensualSecretariaTirDevo."); + return StatusCode(500, "Error interno al generar el PDF del reporte."); } - catch (Exception ex) { _logger.LogError(ex, "Error PDF VentaMensualSecretariaTirDevo."); return StatusCode(500, "Error interno."); } } // GET: api/reportes/distribucion-canillas @@ -704,69 +719,60 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("distribucion-canillas/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteDistribucionCanillasPdf([FromQuery] DateTime fecha, [FromQuery] int idEmpresa, [FromQuery] bool soloTotales = false) { if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid(); - // CORRECCIÓN AQUÍ: Añadir la variable para el nuevo elemento de la tupla var ( - canillas, - canillasAcc, - canillasAll, - canillasFechaLiq, - canillasAccFechaLiq, - ctrlDevolucionesRemitos, // Renombrado para claridad - ctrlDevolucionesParaDistCan, - _, // Descartamos ctrlDevolucionesOtrosDias si no se usa aquí - error + canillas, canillasAcc, canillasAll, canillasFechaLiq, canillasAccFechaLiq, + remitos, ctrlDevoluciones, _, error ) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); if (error != null) return BadRequest(new { message = error }); - // La lógica de noHayDatosParaTotales y noHayDatosParaDetalle debería usar los nombres correctos - bool noHayDatosParaTotales = (canillasAll == null || !canillasAll.Any()) && - (ctrlDevolucionesRemitos == null || !ctrlDevolucionesRemitos.Any()) && // Usar ctrlDevolucionesRemitos o ctrlDevolucionesParaDistCan según el RDLC - (ctrlDevolucionesParaDistCan == null || !ctrlDevolucionesParaDistCan.Any()); - - bool noHayDatosParaDetalle = noHayDatosParaTotales && - (canillas == null || !canillas.Any()) && - (canillasAcc == null || !canillasAcc.Any()) && - (canillasFechaLiq == null || !canillasFechaLiq.Any()) && - (canillasAccFechaLiq == null || !canillasAccFechaLiq.Any()); - - if ((soloTotales && noHayDatosParaTotales) || (!soloTotales && noHayDatosParaDetalle)) + // Verificamos si hay datos suficientes para CUALQUIERA de los dos reportes. + if (!canillas.Any() && !canillasAcc.Any() && !canillasAll.Any()) { - return NotFound(new { message = "No hay datos para generar el PDF del reporte de distribución de canillas." }); + return NotFound(new { message = "No hay datos de distribución para generar el PDF." }); } try { - LocalReport report = new LocalReport(); - string rdlcPath = soloTotales ? "Controllers/Reportes/RDLC/ReporteDistribucionCanillasTotales.rdlc" : "Controllers/Reportes/RDLC/ReporteDistribucionCanillas.rdlc"; + var empresa = await _empresaRepository.GetByIdAsync(idEmpresa); - using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + var viewModel = new DistribucionCanillasViewModel { - report.LoadReportDefinition(fs); + Canillas = canillas, + CanillasAccionistas = canillasAcc, + CanillasTodos = canillasAll, + CanillasLiquidadasOtraFecha = canillasFechaLiq, + CanillasAccionistasLiquidadasOtraFecha = canillasAccFechaLiq, + ControlDevolucionesDetalle = ctrlDevoluciones, + RemitoIngresado = remitos.FirstOrDefault()?.Remito ?? 0, + Empresa = empresa?.Nombre ?? "N/A", + FechaConsultada = fecha.ToString("dd/MM/yyyy") + }; + + IDocument document; + string tipoReporte; + + if (soloTotales) + { + document = new DistribucionCanillasTotalesDocument(viewModel); + tipoReporte = "Totales"; + } + else + { + document = new DistribucionCanillasDocument(viewModel); + tipoReporte = "Detalle"; } - report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCan", canillas ?? new List())); - report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCanAcc", canillasAcc ?? new List())); - report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCanALL", canillasAll ?? new List())); - report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCanFechaLiq", canillasFechaLiq ?? new List())); - report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCanAccFechaLiq", canillasAccFechaLiq ?? new List())); - report.DataSources.Add(new ReportDataSource("DSObtenerCtrlDevoluciones", ctrlDevolucionesRemitos ?? new List())); // Usa el renombrado - report.DataSources.Add(new ReportDataSource("DSCtrlDevoluciones", ctrlDevolucionesParaDistCan ?? new List())); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); - var empresa = await _empresaRepository.GetByIdAsync(idEmpresa); - var parameters = new List - { - new ReportParameter("FechaConsultada", fecha.ToString("dd/MM/yyyy")), - new ReportParameter("Empresa", empresa?.Nombre ?? "N/A") - }; - report.SetParameters(parameters); - - byte[] pdfBytes = report.Render("PDF"); - string tipoReporte = soloTotales ? "Totales" : "Detalle"; string fileName = $"DistribucionCanillas_{tipoReporte}_Emp{idEmpresa}_{fecha:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } @@ -820,57 +826,43 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("control-devoluciones/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteControlDevolucionesPdf([FromQuery] DateTime fecha, [FromQuery] int idEmpresa) { if (!TienePermiso(PermisoVerControlDevoluciones)) return Forbid(); - // La tupla ahora devuelve un campo más var ( - _, _, _, _, _, - ctrlDevolucionesRemitosData, // Para DSObtenerCtrlDevoluciones - ctrlDevolucionesParaDistCanData, // Para DSCtrlDevoluciones - ctrlDevolucionesOtrosDiasData, // <--- NUEVO: Para DSCtrlDevolucionesOtrosDias - error + _, _, _, _, _, // Datos no utilizados + remitos, detalles, otrosDias, error ) = await _reportesService.ObtenerReporteDistribucionCanillasAsync(fecha, idEmpresa); if (error != null) return BadRequest(new { message = error }); - // Ajustamos la condición para verificar los DataSets que realmente usa este reporte específico - if ((ctrlDevolucionesRemitosData == null || !ctrlDevolucionesRemitosData.Any()) && - (ctrlDevolucionesParaDistCanData == null || !ctrlDevolucionesParaDistCanData.Any()) && - (ctrlDevolucionesOtrosDiasData == null || !ctrlDevolucionesOtrosDiasData.Any()) // <--- AÑADIDO A LA VERIFICACIÓN - ) + if ((detalles == null || !detalles.Any()) && (otrosDias == null || !otrosDias.Any())) { return NotFound(new { message = "No hay datos para generar el PDF para control de devoluciones." }); } try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteCtrlDevoluciones.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - - // DataSet que usa SP_DistCanillasCantidadEntradaSalida - report.DataSources.Add(new ReportDataSource("DSCtrlDevoluciones", ctrlDevolucionesParaDistCanData ?? new List())); - - // DataSet que usa SP_DistCanillasCantidadEntradaSalidaOtrosDias - report.DataSources.Add(new ReportDataSource("DSCtrlDevolucionesOtrosDias", ctrlDevolucionesOtrosDiasData ?? new List())); // <--- CORREGIDO - - // DataSet que usa SP_ObtenerCtrlDevoluciones - report.DataSources.Add(new ReportDataSource("DSObtenerCtrlDevoluciones", ctrlDevolucionesRemitosData ?? new List())); - - var empresa = await _empresaRepository.GetByIdAsync(idEmpresa); - var parameters = new List - { - new ReportParameter("FechaConsultada", fecha.ToString("dd/MM/yyyy")), - new ReportParameter("NomEmp", empresa?.Nombre ?? "N/A") - }; - report.SetParameters(parameters); - byte[] pdfBytes = report.Render("PDF"); + // La creación del ViewModel es ahora mucho más limpia + var viewModel = new ControlDevolucionesViewModel + { + Detalles = detalles ?? Enumerable.Empty(), + TotalDevolucionDiasAnteriores = otrosDias?.Sum(d => d.Devueltos) ?? 0, + NombreEmpresa = empresa?.Nombre ?? "N/A", + FechaConsultada = fecha.ToString("dd/MM/yyyy") + }; + + var document = new ControlDevolucionesDocument(viewModel); + + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + string fileName = $"ControlDevoluciones_Emp{idEmpresa}_{fecha:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } @@ -921,6 +913,10 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("cuentas-distribuidores/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteCuentasDistribuidoresPdf( [FromQuery] int idDistribuidor, [FromQuery] int idEmpresa, @@ -933,33 +929,29 @@ namespace GestionIntegral.Api.Controllers await _reportesService.ObtenerReporteCuentasDistribuidorAsync(idDistribuidor, idEmpresa, fechaDesde, fechaHasta); if (error != null) return BadRequest(new { message = error }); - if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any() && !saldos.Any()) + if (!entradasSalidas.Any() && !debitosCreditos.Any() && !pagos.Any()) { return NotFound(new { message = "No hay datos para generar el reporte de cuenta del distribuidor." }); } try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteCuentasDistribuidores.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } + var distribuidor = await _distribuidorRepository.GetByIdAsync(idDistribuidor); - report.DataSources.Add(new ReportDataSource("DSDistribuidoresEntradasSalidas", entradasSalidas ?? new List())); - report.DataSources.Add(new ReportDataSource("DSDistribuidoresDebCred", debitosCreditos ?? new List())); - report.DataSources.Add(new ReportDataSource("DSDistribuidoresPagos", pagos ?? new List())); - report.DataSources.Add(new ReportDataSource("DSDistribuidoresSaldos", saldos ?? new List())); - - var parameters = new List + var viewModel = new CuentasDistribuidorViewModel { - new ReportParameter("FechaDesde", fechaDesde.ToString("yyyy-MM-dd")), - new ReportParameter("FechaHasta", fechaHasta.ToString("yyyy-MM-dd")), + Movimientos = entradasSalidas, + Pagos = pagos, + DebitosCreditos = debitosCreditos, + SaldoDeCuenta = saldos.FirstOrDefault()?.Monto ?? 0, // <-- Se asigna a SaldoDeCuenta + NombreDistribuidor = distribuidor.Distribuidor?.Nombre ?? $"Distribuidor ID {idDistribuidor}", + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy"), }; - report.SetParameters(parameters); + var document = new CuentasDistribuidorDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); - byte[] pdfBytes = report.Render("PDF"); string fileName = $"CuentaDistribuidor_{idDistribuidor}_Emp{idEmpresa}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } @@ -1005,6 +997,10 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("tiradas-publicaciones-secciones/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteTiradasPublicacionesSeccionesPdf( [FromQuery] int idPublicacion, [FromQuery] DateTime fechaDesde, @@ -1015,7 +1011,7 @@ namespace GestionIntegral.Api.Controllers if (!TienePermiso(PermisoVerReporteTiradas)) return Forbid(); IEnumerable data; - string? errorMsg; // Para recibir el mensaje de error + string? errorMsg; if (consolidado) { @@ -1032,36 +1028,34 @@ namespace GestionIntegral.Api.Controllers try { - LocalReport report = new LocalReport(); - string rdlcPath = consolidado ? - "Controllers/Reportes/RDLC/ReporteTiradasPublicacionesSeccionesConsolidado.rdlc" : - "Controllers/Reportes/RDLC/ReporteTiradasPublicacionesSecciones.rdlc"; - - using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSTiradasPublicacionesSecciones", data)); - var publicacion = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); - var parameters = new List - { - new ReportParameter("Mes", fechaDesde.ToString("MMMM yyyy")), - new ReportParameter("NomPubli", publicacion?.Nombre ?? "N/A") - }; + + string? nombrePlanta = null; if (!consolidado && idPlanta.HasValue) { var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); - parameters.Add(new ReportParameter("NomPlanta", planta?.Nombre ?? "N/A")); + nombrePlanta = planta?.Nombre; } - report.SetParameters(parameters); + var viewModel = new TiradasPublicacionesSeccionesViewModel + { + Detalles = data, + NombrePublicacion = publicacion?.Nombre ?? "N/A", + MesConsultado = fechaDesde.ToString("MMMM 'de' yyyy", new CultureInfo("es-ES")), + NombrePlanta = nombrePlanta + }; + + var document = new TiradasPublicacionesSeccionesDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); - byte[] pdfBytes = report.Render("PDF"); string fileName = $"TiradasSecciones_Pub{idPublicacion}_{(consolidado ? "Consolidado" : $"Planta{idPlanta}")}_{fechaDesde:yyyyMM}.pdf"; return File(pdfBytes, "application/pdf", fileName); } - catch (Exception ex) { _logger.LogError(ex, "Error PDF Tiradas Publicaciones Secciones."); return StatusCode(500, "Error interno."); } + catch (Exception ex) + { + _logger.LogError(ex, "Error PDF Tiradas Publicaciones Secciones."); + return StatusCode(500, "Error interno al generar el PDF del reporte."); + } } // GET: api/reportes/consumo-bobinas-seccion @@ -1098,6 +1092,10 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("consumo-bobinas-seccion/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteConsumoBobinasSeccionPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, @@ -1124,33 +1122,32 @@ namespace GestionIntegral.Api.Controllers try { - LocalReport report = new LocalReport(); - string rdlcPath = consolidado ? - "Controllers/Reportes/RDLC/ReporteConsumoBobinasSeccionConsolidado.rdlc" : - "Controllers/Reportes/RDLC/ReporteConsumoBobinasSeccion.rdlc"; - using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSConsumoBobinasSeccion", data)); - - var parameters = new List - { - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) - }; + string? nombrePlanta = null; if (!consolidado && idPlanta.HasValue) { var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); - parameters.Add(new ReportParameter("NomPlanta", planta?.Nombre ?? "N/A")); + nombrePlanta = planta?.Nombre; } - report.SetParameters(parameters); - byte[] pdfBytes = report.Render("PDF"); + var viewModel = new ConsumoBobinasSeccionViewModel + { + Detalles = data, + NombrePlanta = nombrePlanta, + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; + + var document = new ConsumoBobinasSeccionDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + string fileName = $"ConsumoBobinasSeccion_{(consolidado ? "Consolidado" : $"Planta{idPlanta}")}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } - catch (Exception ex) { _logger.LogError(ex, "Error PDF Consumo Bobinas por Seccion."); return StatusCode(500, "Error interno."); } + catch (Exception ex) + { + _logger.LogError(ex, "Error PDF Consumo Bobinas por Seccion."); + return StatusCode(500, "Error interno al generar el PDF del reporte."); + } } // GET: api/reportes/consumo-bobinas-publicacion @@ -1174,6 +1171,10 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("consumo-bobinas-publicacion/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteConsumoBobinasPublicacionPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta) @@ -1187,25 +1188,24 @@ namespace GestionIntegral.Api.Controllers try { - LocalReport report = new LocalReport(); - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteConsumoBobinasPublicacion.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + var viewModel = new ConsumoBobinasPublicacionViewModel { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSConsumoBobinasPublicacion", data)); - - var parameters = new List - { - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) + Detalles = data, + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; - report.SetParameters(parameters); - byte[] pdfBytes = report.Render("PDF"); + var document = new ConsumoBobinasPublicacionDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + string fileName = $"ConsumoBobinasPublicacion_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } - catch (Exception ex) { _logger.LogError(ex, "Error PDF Consumo Bobinas por Publicacion."); return StatusCode(500, "Error interno."); } + catch (Exception ex) + { + _logger.LogError(ex, "Error PDF Consumo Bobinas por Publicacion."); + return StatusCode(500, "Error interno al generar el PDF del reporte."); + } } // GET: api/reportes/comparativa-consumo-bobinas @@ -1242,6 +1242,10 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("comparativa-consumo-bobinas/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetReporteComparativaConsumoBobinasPdf( [FromQuery] DateTime fechaInicioMesA, [FromQuery] DateTime fechaFinMesA, [FromQuery] DateTime fechaInicioMesB, [FromQuery] DateTime fechaFinMesB, @@ -1268,34 +1272,32 @@ namespace GestionIntegral.Api.Controllers try { - LocalReport report = new LocalReport(); - string rdlcPath = consolidado ? - "Controllers/Reportes/RDLC/ReporteConsumoBobinasMesesConsolidado.rdlc" : - "Controllers/Reportes/RDLC/ReporteConsumoBobinasMeses.rdlc"; - - using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSConsumoBobinasMeses", data)); - - var parameters = new List - { - new ReportParameter("MesA", fechaInicioMesA.ToString("MMMM yyyy")), - new ReportParameter("MesB", fechaInicioMesB.ToString("MMMM yyyy")) - }; + string? nombrePlanta = null; if (!consolidado && idPlanta.HasValue) { var planta = await _plantaRepository.GetByIdAsync(idPlanta.Value); - parameters.Add(new ReportParameter("NomPlanta", planta?.Nombre ?? "N/A")); + nombrePlanta = planta?.Nombre; } - report.SetParameters(parameters); - byte[] pdfBytes = report.Render("PDF"); + var viewModel = new ComparativaConsumoBobinasViewModel + { + Detalles = data, + NombrePlanta = nombrePlanta, + MesA = fechaInicioMesA.ToString("MMMM yyyy", new CultureInfo("es-ES")), + MesB = fechaInicioMesB.ToString("MMMM yyyy", new CultureInfo("es-ES")) + }; + + var document = new ComparativaConsumoBobinasDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + string fileName = $"ComparativaConsumoBobinas_{(consolidado ? "Consolidado" : $"Planta{idPlanta}")}_{fechaInicioMesA:yyyyMM}_vs_{fechaInicioMesB:yyyyMM}.pdf"; return File(pdfBytes, "application/pdf", fileName); } - catch (Exception ex) { _logger.LogError(ex, "Error PDF Comparativa Consumo Bobinas."); return StatusCode(500, "Error interno."); } + catch (Exception ex) + { + _logger.LogError(ex, "Error PDF Comparativa Consumo Bobinas."); + return StatusCode(500, "Error interno al generar el PDF del reporte."); + } } // GET: api/reportes/listado-distribucion-distribuidores @@ -1353,29 +1355,22 @@ namespace GestionIntegral.Api.Controllers try { - LocalReport report = new LocalReport(); - // USAREMOS EL MISMO RDLC QUE PARA CANILLITAS, YA QUE TIENE LA MISMA ESTRUCTURA DE DATASOURCES - using (var fs = new FileStream("Controllers/Reportes/RDLC/ReporteListadoDistribucion.rdlc", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - // Nombres de DataSources deben coincidir con los del RDLC - report.DataSources.Add(new ReportDataSource("DSListadoDistribucion", simple ?? new List())); - report.DataSources.Add(new ReportDataSource("DSListadoDistribucionAgDias", promedios ?? new List())); - - var publicacionData = await _publicacionRepository.GetByIdAsync(idPublicacion); + var publicacionData = await _publicacionRepository.GetByIdSimpleAsync(idPublicacion); var distribuidorData = await _distribuidorRepository.GetByIdAsync(idDistribuidor); - var parameters = new List + var viewModel = new ListadoDistribucionDistribuidoresViewModel { - new ReportParameter("NomPubli", publicacionData.Publicacion?.Nombre ?? "N/A"), - new ReportParameter("NomDist", distribuidorData.Distribuidor?.Nombre ?? "N/A"), // Parámetro para el RDLC - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) + DetalleDiario = simple ?? Enumerable.Empty(), + PromediosPorDia = promedios, + NombrePublicacion = publicacionData?.Nombre ?? "N/A", + NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "N/A", + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") }; - report.SetParameters(parameters); - byte[] pdfBytes = report.Render("PDF"); + var document = new ListadoDistribucionDistribuidoresDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); + string fileName = $"ListadoDistribucion_Dist{idDistribuidor}_Pub{idPublicacion}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } @@ -1392,19 +1387,15 @@ namespace GestionIntegral.Api.Controllers [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetTicketLiquidacionCanillaPdf( - [FromQuery] DateTime fecha, - [FromQuery] int idCanilla, - [FromQuery] bool esAccionista = false) // Añadir esAccionista + [FromQuery] DateTime fecha, + [FromQuery] int idCanilla, + [FromQuery] bool esAccionista = false) { - // Usar PermisoVerComprobanteLiquidacionCanilla o uno específico si lo creas if (!TienePermiso(PermisoVerComprobanteLiquidacionCanilla)) return Forbid(); var (detalles, ganancias, error) = await _reportesService.ObtenerDatosTicketLiquidacionAsync(fecha, idCanilla); if (error != null) return BadRequest(new { message = error }); - - // El PDF podría funcionar incluso si solo uno de los datasets tiene datos, - // pero es bueno verificar si al menos hay detalles. if (detalles == null || !detalles.Any()) { return NotFound(new { message = "No hay detalles de liquidación para generar el PDF." }); @@ -1412,42 +1403,17 @@ namespace GestionIntegral.Api.Controllers try { - LocalReport report = new LocalReport(); - string rdlcPath = esAccionista ? - "Controllers/Reportes/RDLC/ReporteLiquidacionCanillasAcc.rdlc" : - "Controllers/Reportes/RDLC/ReporteLiquidacionCanillas.rdlc"; - - if (!System.IO.File.Exists(rdlcPath)) + var viewModel = new LiquidacionCanillaViewModel { - _logger.LogError("Archivo RDLC no encontrado: {Path}", rdlcPath); - return StatusCode(StatusCodes.Status500InternalServerError, $"Archivo de reporte no encontrado: {System.IO.Path.GetFileName(rdlcPath)}"); - } + Detalles = detalles, + Ganancias = ganancias, + FechaLiquidacion = fecha.ToString("dd/MM/yyyy"), + EsAccionista = esAccionista + }; - using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } + var document = new LiquidacionCanillaDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); - report.DataSources.Add(new ReportDataSource("DSLiquidacionCanillas", detalles)); - if (!esAccionista) // El reporte de accionistas podría no usar el dataset de ganancias, o usar uno diferente - { - report.DataSources.Add(new ReportDataSource("DSLiquidacionCanillasGanancias", ganancias ?? new List())); - } - - - var parameters = new List - { - // El RDLC espera "FechaLiqui" - new ReportParameter("FechaLiqui", fecha.ToString("dd/MM/yyyy")) - }; - // El nombre del canilla ya está en el DataSet "DSLiquidacionCanillas" (campo "Canilla") - // Si el RDLC lo espera como parámetro, lo añadiríamos aquí. - // var canilla = await _canillaRepository.GetByIdAsync(idCanilla); - // parameters.Add(new ReportParameter("NombreCanillaParam", canilla?.NomApe ?? "N/A")); - - report.SetParameters(parameters); - - byte[] pdfBytes = report.Render("PDF"); string tipo = esAccionista ? "Accionista" : "Canillita"; string fileName = $"TicketLiquidacion_{tipo}_{idCanilla}_{fecha:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); @@ -1510,26 +1476,22 @@ namespace GestionIntegral.Api.Controllers [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetReporteNovedadesCanillasPdf( [FromQuery] int idEmpresa, - [FromQuery] DateTime fechaDesde, - [FromQuery] DateTime fechaHasta) + [FromQuery] DateTime fechaDesde, + [FromQuery] DateTime fechaHasta) { - if (!TienePermiso(PermisoVerReporteNovedadesCanillas)) // RR004 - { - _logger.LogWarning("Acceso denegado a GetReporteNovedadesCanillasPdf. Usuario: {User}", User.Identity?.Name ?? "Desconocido"); - return Forbid(); - } - if (fechaDesde > fechaHasta) - { - return BadRequest(new { message = "La fecha 'desde' no puede ser posterior a la fecha 'hasta'." }); - } + if (!TienePermiso(PermisoVerReporteNovedadesCanillas)) return Forbid(); + if (fechaDesde > fechaHasta) return BadRequest(new { message = "La fecha 'desde' no puede ser posterior a la fecha 'hasta'." }); try { - // Obtener datos para AMBOS datasets - var novedadesData = await _novedadCanillaService.ObtenerReporteNovedadesAsync(idEmpresa, fechaDesde, fechaHasta); - var gananciasData = await _novedadCanillaService.ObtenerReporteGananciasAsync(idEmpresa, fechaDesde, fechaHasta); // << OBTENER DATOS DE GANANCIAS + var novedadesDataTask = _novedadCanillaService.ObtenerReporteNovedadesAsync(idEmpresa, fechaDesde, fechaHasta); + var gananciasDataTask = _novedadCanillaService.ObtenerReporteGananciasAsync(idEmpresa, fechaDesde, fechaHasta); + + await Task.WhenAll(novedadesDataTask, gananciasDataTask); + + var novedadesData = await novedadesDataTask; + var gananciasData = await gananciasDataTask; - // Verificar si hay datos en *alguno* de los datasets necesarios para el reporte if ((novedadesData == null || !novedadesData.Any()) && (gananciasData == null || !gananciasData.Any())) { return NotFound(new { message = "No hay datos para generar el PDF con los parámetros seleccionados." }); @@ -1537,35 +1499,18 @@ namespace GestionIntegral.Api.Controllers var empresa = await _empresaRepository.GetByIdAsync(idEmpresa); - LocalReport report = new LocalReport(); - string rdlcPath = Path.Combine("Controllers", "Reportes", "RDLC", "ReporteListadoNovedadesCanillas.rdlc"); - if (!System.IO.File.Exists(rdlcPath)) + var viewModel = new NovedadesCanillasViewModel { - _logger.LogError("Archivo RDLC no encontrado en la ruta: {RdlcPath}", rdlcPath); - return StatusCode(StatusCodes.Status500InternalServerError, "Archivo de definición de reporte no encontrado."); - } + ResumenCanillas = gananciasData, + DetallesNovedades = novedadesData ?? Enumerable.Empty(), + NombreEmpresa = empresa?.Nombre ?? "N/A", + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; - using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } + var document = new NovedadesCanillasDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); - // Nombre del DataSet en RDLC para SP_DistCanillasNovedades (detalles) - report.DataSources.Add(new ReportDataSource("DSNovedadesCanillasDetalles", novedadesData ?? new List())); - - // Nombre del DataSet en RDLC para SP_DistCanillasGanancias (ganancias/resumen) - report.DataSources.Add(new ReportDataSource("DSNovedadesCanillas", gananciasData ?? new List())); - - - var parameters = new List - { - new ReportParameter("NomEmp", empresa?.Nombre ?? "N/A"), - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")) - }; - report.SetParameters(parameters); - - byte[] pdfBytes = report.Render("PDF"); string fileName = $"ReporteNovedadesCanillas_Emp{idEmpresa}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } @@ -1575,6 +1520,7 @@ namespace GestionIntegral.Api.Controllers return StatusCode(StatusCodes.Status500InternalServerError, new { message = $"Error interno al generar el PDF: {ex.Message}" }); } } + // GET: api/reportes/novedades-canillas-ganancias [HttpGet("novedades-canillas-ganancias")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] @@ -1627,6 +1573,11 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("listado-distribucion-mensual/diarios/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetListadoDistMensualDiariosPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, @@ -1636,37 +1587,32 @@ namespace GestionIntegral.Api.Controllers if (fechaDesde > fechaHasta) return BadRequest(new { message = "Fecha Desde no puede ser mayor a Fecha Hasta." }); var (data, error) = await _reportesService.ObtenerReporteMensualDiariosAsync(fechaDesde, fechaHasta, esAccionista); + if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el PDF." }); try { - LocalReport report = new LocalReport(); - string rdlcPath = Path.Combine("Controllers", "Reportes", "RDLC", "ReporteListadoDistribucionCanMensualDiarios.rdlc"); - if (!System.IO.File.Exists(rdlcPath)) + var viewModel = new ListadoDistCanMensualDiariosViewModel { - _logger.LogError("Archivo RDLC no encontrado: {Path}", rdlcPath); - return StatusCode(StatusCodes.Status500InternalServerError, $"Archivo de reporte no encontrado: {Path.GetFileName(rdlcPath)}"); - } - using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCanMensualDiarios", data)); + Detalles = data, + TipoDestinatario = esAccionista ? "Accionistas" : "Canillitas", + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; - var parameters = new List - { - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")), - new ReportParameter("CanAcc", esAccionista ? "1" : "0") // El RDLC espera un Integer para CanAcc - }; - report.SetParameters(parameters); + var document = new ListadoDistCanMensualDiariosDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); - byte[] pdfBytes = report.Render("PDF"); string tipoDesc = esAccionista ? "Accionistas" : "Canillitas"; - return File(pdfBytes, "application/pdf", $"ListadoDistMensualDiarios_{tipoDesc}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"); + string fileName = $"ListadoDistMensualDiarios_{tipoDesc}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; + return File(pdfBytes, "application/pdf", fileName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error PDF ListadoDistMensualDiarios"); + return StatusCode(500, "Error interno al generar el PDF del reporte."); } - catch (Exception ex) { _logger.LogError(ex, "Error PDF ListadoDistMensualDiarios"); return StatusCode(500, "Error interno."); } } @@ -1689,6 +1635,11 @@ namespace GestionIntegral.Api.Controllers } [HttpGet("listado-distribucion-mensual/publicaciones/pdf")] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetListadoDistMensualPorPublicacionPdf( [FromQuery] DateTime fechaDesde, [FromQuery] DateTime fechaHasta, @@ -1698,37 +1649,32 @@ namespace GestionIntegral.Api.Controllers if (fechaDesde > fechaHasta) return BadRequest(new { message = "Fecha Desde no puede ser mayor a Fecha Hasta." }); var (data, error) = await _reportesService.ObtenerReporteMensualPorPublicacionAsync(fechaDesde, fechaHasta, esAccionista); + if (error != null) return BadRequest(new { message = error }); if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el PDF." }); try { - LocalReport report = new LocalReport(); - string rdlcPath = Path.Combine("Controllers", "Reportes", "RDLC", "ReporteListadoDistribucionCanMensual.rdlc"); - if (!System.IO.File.Exists(rdlcPath)) + var viewModel = new ListadoDistCanMensualViewModel { - _logger.LogError("Archivo RDLC no encontrado: {Path}", rdlcPath); - return StatusCode(StatusCodes.Status500InternalServerError, $"Archivo de reporte no encontrado: {Path.GetFileName(rdlcPath)}"); - } - using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - report.LoadReportDefinition(fs); - } - report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCanMensual", data)); + Detalles = data, + TipoDestinatario = esAccionista ? "Accionistas" : "Canillitas", + FechaDesde = fechaDesde.ToString("dd/MM/yyyy"), + FechaHasta = fechaHasta.ToString("dd/MM/yyyy") + }; - var parameters = new List - { - new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")), - new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")), - new ReportParameter("CanAcc", esAccionista ? "1" : "0") - }; - report.SetParameters(parameters); + var document = new ListadoDistCanMensualDocument(viewModel); + byte[] pdfBytes = await _pdfGenerator.GeneratePdfAsync(document); - byte[] pdfBytes = report.Render("PDF"); string tipoDesc = esAccionista ? "Accionistas" : "Canillitas"; - return File(pdfBytes, "application/pdf", $"ListadoDistMensualPub_{tipoDesc}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"); + string fileName = $"ListadoDistMensualPub_{tipoDesc}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf"; + return File(pdfBytes, "application/pdf", fileName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error PDF ListadoDistMensualPorPublicacion"); + return StatusCode(500, "Error interno al generar el PDF del reporte."); } - catch (Exception ex) { _logger.LogError(ex, "Error PDF ListadoDistMensualPorPublicacion"); return StatusCode(500, "Error interno."); } } } } \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/GestionIntegral.Api.csproj b/Backend/GestionIntegral.Api/GestionIntegral.Api.csproj index 299b044..8c4e0df 100644 --- a/Backend/GestionIntegral.Api/GestionIntegral.Api.csproj +++ b/Backend/GestionIntegral.Api/GestionIntegral.Api.csproj @@ -19,29 +19,4 @@ - \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ComparativaConsumoBobinasViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ComparativaConsumoBobinasViewModel.cs new file mode 100644 index 0000000..9aa7420 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ComparativaConsumoBobinasViewModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class ComparativaConsumoBobinasViewModel + { + public IEnumerable Detalles { get; set; } = new List(); + + public string? NombrePlanta { get; set; } + public string MesA { get; set; } = string.Empty; + public string MesB { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + + public bool EsConsolidado => string.IsNullOrEmpty(NombrePlanta); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ConsumoBobinasPublicacionViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ConsumoBobinasPublicacionViewModel.cs new file mode 100644 index 0000000..dd21d39 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ConsumoBobinasPublicacionViewModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class ConsumoBobinasPublicacionViewModel + { + public IEnumerable Detalles { get; set; } = new List(); + + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ConsumoBobinasSeccionViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ConsumoBobinasSeccionViewModel.cs new file mode 100644 index 0000000..b83a750 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ConsumoBobinasSeccionViewModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class ConsumoBobinasSeccionViewModel + { + public IEnumerable Detalles { get; set; } = new List(); + + public string? NombrePlanta { get; set; } + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + + public bool EsConsolidado => string.IsNullOrEmpty(NombrePlanta); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ControlDevolucionesViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ControlDevolucionesViewModel.cs new file mode 100644 index 0000000..e54a529 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ControlDevolucionesViewModel.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class ControlDevolucionesViewModel + { + // --- Datos de entrada --- + public IEnumerable Detalles { get; set; } = new List(); + public int TotalDevolucionDiasAnteriores { get; set; } + + // --- Parámetros del reporte --- + public string NombreEmpresa { get; set; } = string.Empty; + public string FechaConsultada { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + + // --- Propiedades calculadas (corregidas para ser de solo lectura) --- + public int CantidadCanillas => Detalles?.FirstOrDefault()?.TotalNoAccionistas ?? 0; + public int TotalIngresadosPorRemito => Detalles?.FirstOrDefault()?.Ingresados ?? 0; + public int TotalSobrantes => Detalles?.FirstOrDefault()?.Sobrantes ?? 0; + public int TotalSinCargo => Detalles?.FirstOrDefault()?.SinCargo ?? 0; + + public int TotalLlevados => Detalles?.Sum(d => d.Llevados) ?? 0; + public int TotalDevueltosFecha => Detalles?.Sum(d => d.Devueltos) ?? 0; + + public decimal TotalDevolucionALaFecha => (decimal)(TotalIngresadosPorRemito - TotalLlevados + TotalDevueltosFecha); + public decimal TotalDevolucionGeneral => TotalDevolucionALaFecha + TotalDevolucionDiasAnteriores; + public decimal DiferenciaFinal => TotalDevolucionALaFecha - TotalSobrantes - TotalSinCargo; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/CuentasDistribuidorViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/CuentasDistribuidorViewModel.cs new file mode 100644 index 0000000..1eaa0aa --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/CuentasDistribuidorViewModel.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class CuentasDistribuidorViewModel + { + // --- Datos de entrada --- + public IEnumerable Movimientos { get; set; } = new List(); + public IEnumerable Pagos { get; set; } = new List(); + public IEnumerable DebitosCreditos { get; set; } = new List(); + + // Saldo real de la cuenta, se muestra al final sin usarse en cálculos intermedios. + public decimal SaldoDeCuenta { get; set; } + + // --- Parámetros del reporte --- + public string NombreDistribuidor { get; set; } = string.Empty; + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + + // --- Propiedades para el resumen final --- + public decimal TotalMovimientos => Movimientos.Sum(m => m.Debe - m.Haber); + public decimal TotalPagos => Pagos.Sum(p => p.Debe - p.Haber); + public decimal TotalDebitosCreditos => DebitosCreditos.Sum(d => d.Debe - d.Haber); + public decimal TotalPeriodo => TotalMovimientos + TotalPagos + TotalDebitosCreditos; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/DistribucionCanillasViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/DistribucionCanillasViewModel.cs new file mode 100644 index 0000000..5e44935 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/DistribucionCanillasViewModel.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class DistribucionCanillasViewModel + { + // --- Datos de entrada --- + public IEnumerable Canillas { get; set; } = new List(); + public IEnumerable CanillasAccionistas { get; set; } = new List(); + public IEnumerable CanillasTodos { get; set; } = new List(); + public IEnumerable CanillasLiquidadasOtraFecha { get; set; } = new List(); + public IEnumerable CanillasAccionistasLiquidadasOtraFecha { get; set; } = new List(); + public IEnumerable ControlDevolucionesDetalle { get; set; } = new List(); + public int RemitoIngresado { get; set; } + + // --- Parámetros del reporte --- + public string Empresa { get; set; } = string.Empty; + public string FechaConsultada { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + + // Propiedades calculadas para el resumen final + public int VentaTotal => (ControlDevolucionesDetalle?.Sum(d => d.Llevados) ?? 0) - (ControlDevolucionesDetalle?.Sum(d => d.Devueltos) ?? 0); + public int DevolucionTotal => RemitoIngresado - VentaTotal; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/LiquidacionCanillaViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/LiquidacionCanillaViewModel.cs new file mode 100644 index 0000000..ea89b81 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/LiquidacionCanillaViewModel.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class LiquidacionCanillaViewModel + { + // --- Datos de entrada --- + public IEnumerable Detalles { get; set; } = new List(); + public IEnumerable Ganancias { get; set; } = new List(); + + // --- Parámetros del reporte --- + public string FechaLiquidacion { get; set; } = string.Empty; + + // Propiedades calculadas para un acceso más fácil y limpio en la plantilla + public string NombreVendedor => Detalles.FirstOrDefault()?.Canilla ?? "N/A"; + public decimal TotalARendir => Detalles.Sum(d => d.TotalRendir); + public decimal TotalComisiones => Ganancias.Sum(g => g.TotalRendir); + + // Propiedad para el título del reporte + public string TituloReporte => EsAccionista ? "Liquidación de Accionistas" : "Liquidación Venta de Diarios"; + public bool EsAccionista { get; set; } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanMensualDiariosViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanMensualDiariosViewModel.cs new file mode 100644 index 0000000..c62d281 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanMensualDiariosViewModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class ListadoDistCanMensualDiariosViewModel + { + public IEnumerable Detalles { get; set; } = new List(); + + public string TipoDestinatario { get; set; } = string.Empty; // "Canillitas" o "Accionistas" + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanMensualViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanMensualViewModel.cs new file mode 100644 index 0000000..9ed9d17 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanMensualViewModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class ListadoDistCanMensualViewModel + { + // Usamos el DTO existente que ya tiene la estructura que necesitamos. + public IEnumerable Detalles { get; set; } = new List(); + + // Parámetros para el encabezado + public string TipoDestinatario { get; set; } = string.Empty; // "Canillitas" o "Accionistas" + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanillasImporteViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanillasImporteViewModel.cs new file mode 100644 index 0000000..4af90e4 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistCanillasImporteViewModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class ListadoDistCanillasImporteViewModel + { + public IEnumerable Detalles { get; set; } = new List(); + + public string NombrePublicacion { get; set; } = string.Empty; + public string TipoDestinatario { get; set; } = string.Empty; // "Canillitas" o "Accionistas" + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionCanillasViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionCanillasViewModel.cs new file mode 100644 index 0000000..c662ec1 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionCanillasViewModel.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class ListadoDistribucionCanillasViewModel + { + public IEnumerable DetalleDiario { get; set; } = new List(); + public IEnumerable PromediosPorDia { get; set; } = new List(); + + public string NombrePublicacion { get; set; } = string.Empty; + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + + public ListadoDistribucionCanillasSimpleDto TotalesDetalleDiario + { + get + { + if (DetalleDiario == null || !DetalleDiario.Any()) + { + return new ListadoDistribucionCanillasSimpleDto(); + } + return new ListadoDistribucionCanillasSimpleDto + { + Llevados = DetalleDiario.Sum(d => d.Llevados), + Devueltos = DetalleDiario.Sum(d => d.Devueltos) + }; + } + } + + // --- PROPIEDAD PARA LA FILA "GENERAL" --- + public ListadoDistribucionCanillasPromedioDiaDto? PromedioGeneral + { + get + { + if (PromediosPorDia == null || !PromediosPorDia.Any()) return null; + + // Sumamos los totales, no promediamos los promedios + var totalLlevados = PromediosPorDia.Sum(p => p.Llevados); + var totalDevueltos = PromediosPorDia.Sum(p => p.Devueltos); + var totalDias = PromediosPorDia.Sum(p => p.Cant); + + if (totalDias == 0) return null; + + return new ListadoDistribucionCanillasPromedioDiaDto + { + Dia = "General", + Cant = totalDias, + Promedio_Llevados = totalLlevados / totalDias, + Promedio_Devueltos = totalDevueltos / totalDias, + Promedio_Ventas = (totalLlevados - totalDevueltos) / totalDias + }; + } + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionDistribuidoresViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionDistribuidoresViewModel.cs new file mode 100644 index 0000000..7e1404b --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionDistribuidoresViewModel.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class ListadoDistribucionDistribuidoresViewModel + { + public IEnumerable DetalleDiario { get; set; } = new List(); + public IEnumerable PromediosPorDia { get; set; } = new List(); + + public string NombrePublicacion { get; set; } = string.Empty; + public string NombreDistribuidor { get; set; } = string.Empty; + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + + // --- PROPIEDAD CALCULADA PARA LA FILA "GENERAL" --- + public ListadoDistribucionDistPromedioDiaDto? PromedioGeneral + { + get + { + if (DetalleDiario == null || !DetalleDiario.Any()) + { + return null; + } + + var diasConDatos = DetalleDiario.Count(d => (d.Llevados ?? 0) > 0); + if (diasConDatos == 0) return null; + + var totalLlevados = DetalleDiario.Sum(d => d.Llevados ?? 0); + var totalDevueltos = DetalleDiario.Sum(d => d.Devueltos ?? 0); + + return new ListadoDistribucionDistPromedioDiaDto + { + Dia = "General", + Cant = diasConDatos, + Promedio_Llevados = totalLlevados / diasConDatos, + Promedio_Devueltos = totalDevueltos / diasConDatos, + Promedio_Ventas = (totalLlevados - totalDevueltos) / diasConDatos, + Llevados = totalLlevados, // Guardamos el total para el cálculo del % + Devueltos = totalDevueltos // Guardamos el total para el cálculo del % + }; + } + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionGeneralViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionGeneralViewModel.cs new file mode 100644 index 0000000..a00e722 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/ListadoDistribucionGeneralViewModel.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; // Necesario para .Any() y .Sum() + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class ListadoDistribucionGeneralViewModel + { + public IEnumerable ResumenMensual { get; set; } = new List(); + public IEnumerable PromediosPorDia { get; set; } = new List(); + + public string NombrePublicacion { get; set; } = string.Empty; + public string MesConsultado { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + + // --- PROPIEDAD PARA LOS TOTALES GENERALES DE PROMEDIOS --- + // Esta propiedad calcula los promedios generales basados en los datos del resumen mensual. + public ListadoDistribucionGeneralPromedioDiaDto? PromedioGeneral + { + get + { + if (ResumenMensual == null || !ResumenMensual.Any()) + { + return null; + } + + // Contar solo los días con tirada > 0 para promediar correctamente + var diasConTirada = ResumenMensual.Count(d => d.CantidadTirada > 0); + if (diasConTirada == 0) return null; + + return new ListadoDistribucionGeneralPromedioDiaDto + { + Dia = "General", + CantidadDias = diasConTirada, + PromedioTirada = (int)ResumenMensual.Average(r => r.CantidadTirada), + PromedioSinCargo = (int)ResumenMensual.Average(r => r.SinCargo), + PromedioPerdidos = (int)ResumenMensual.Average(r => r.Perdidos), + PromedioLlevados = (int)ResumenMensual.Average(r => r.Llevados), + PromedioDevueltos = (int)ResumenMensual.Average(r => r.Devueltos), + PromedioVendidos = (int)ResumenMensual.Average(r => r.Vendidos) + }; + } + } + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/MovimientoBobinasEstadoViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/MovimientoBobinasEstadoViewModel.cs new file mode 100644 index 0000000..814d646 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/MovimientoBobinasEstadoViewModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class MovimientoBobinasEstadoViewModel + { + public IEnumerable Detalles { get; set; } = new List(); + public IEnumerable Totales { get; set; } = new List(); + public string NombrePlanta { get; set; } = string.Empty; + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/MovimientoBobinasViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/MovimientoBobinasViewModel.cs new file mode 100644 index 0000000..3e07920 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/MovimientoBobinasViewModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class MovimientoBobinasViewModel + { + public IEnumerable Movimientos { get; set; } = new List(); + public string NombrePlanta { get; set; } = string.Empty; + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/NovedadesCanillasViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/NovedadesCanillasViewModel.cs new file mode 100644 index 0000000..da9f355 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/NovedadesCanillasViewModel.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class NovedadesCanillasViewModel + { + // Resumen de ganancias, faltas y francos por canilla + public IEnumerable ResumenCanillas { get; set; } = new List(); + + // Detalle de las novedades textuales + public IEnumerable DetallesNovedades { get; set; } = new List(); + + // Parámetros para el encabezado + public string NombreEmpresa { get; set; } = string.Empty; + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/TiradasPublicacionesSeccionesViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/TiradasPublicacionesSeccionesViewModel.cs new file mode 100644 index 0000000..bfe2611 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/TiradasPublicacionesSeccionesViewModel.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class TiradasPublicacionesSeccionesViewModel + { + public IEnumerable Detalles { get; set; } = new List(); + + public string NombrePublicacion { get; set; } = string.Empty; + public string MesConsultado { get; set; } = string.Empty; + public string FechaReporte { get; set; } = DateTime.Now.ToString("dd/MM/yyyy"); + + // Será nulo o vacío para la versión consolidada. + public string? NombrePlanta { get; set; } + + // Propiedad calculada para simplificar la lógica en la plantilla. + public bool EsConsolidado => string.IsNullOrEmpty(NombrePlanta); + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaElDiaViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaElDiaViewModel.cs new file mode 100644 index 0000000..b578452 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaElDiaViewModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class VentaMensualSecretariaElDiaViewModel + { + public IEnumerable VentasDiarias { get; set; } = new List(); + + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaElPlataViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaElPlataViewModel.cs new file mode 100644 index 0000000..1876498 --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaElPlataViewModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class VentaMensualSecretariaElPlataViewModel + { + public IEnumerable VentasDiarias { get; set; } = new List(); + + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaTirDevoViewModel.cs b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaTirDevoViewModel.cs new file mode 100644 index 0000000..580336e --- /dev/null +++ b/Backend/GestionIntegral.Api/Models/Dtos/Reportes/ViewModels/VentaMensualSecretariaTirDevoViewModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace GestionIntegral.Api.Dtos.Reportes.ViewModels +{ + public class VentaMensualSecretariaTirDevoViewModel + { + public IEnumerable VentasDiarias { get; set; } = new List(); + + public string FechaDesde { get; set; } = string.Empty; + public string FechaHasta { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Backend/GestionIntegral.Api/Services/Reportes/ReportesService.cs b/Backend/GestionIntegral.Api/Services/Reportes/ReportesService.cs index 0df9c16..321839b 100644 --- a/Backend/GestionIntegral.Api/Services/Reportes/ReportesService.cs +++ b/Backend/GestionIntegral.Api/Services/Reportes/ReportesService.cs @@ -209,16 +209,16 @@ namespace GestionIntegral.Api.Services.Reportes } public async Task<( - IEnumerable Canillas, - IEnumerable CanillasAcc, - IEnumerable CanillasAll, - IEnumerable CanillasFechaLiq, - IEnumerable CanillasAccFechaLiq, - IEnumerable CtrlDevolucionesRemitos, - IEnumerable CtrlDevolucionesParaDistCan, - IEnumerable CtrlDevolucionesOtrosDias, - string? Error -)> ObtenerReporteDistribucionCanillasAsync(DateTime fecha, int idEmpresa) + IEnumerable Canillas, + IEnumerable CanillasAcc, + IEnumerable CanillasAll, + IEnumerable CanillasFechaLiq, + IEnumerable CanillasAccFechaLiq, + IEnumerable CtrlDevolucionesRemitos, + IEnumerable CtrlDevolucionesParaDistCan, + IEnumerable CtrlDevolucionesOtrosDias, + string? Error + )> ObtenerReporteDistribucionCanillasAsync(DateTime fecha, int idEmpresa) { try { @@ -238,6 +238,9 @@ namespace GestionIntegral.Api.Services.Reportes ctrlDevolucionesOtrosDiasTask ); + var detallesOriginales = await ctrlDevolucionesParaDistCanTask ?? Enumerable.Empty(); + var detallesOrdenados = detallesOriginales.OrderBy(d => d.Tipo).ToList(); + Func, IEnumerable> toUtc = items => items?.Select(c => { if (c.Fecha.HasValue) c.Fecha = DateTime.SpecifyKind(c.Fecha.Value.Date, DateTimeKind.Utc); return c; }).ToList() ?? Enumerable.Empty(); @@ -249,7 +252,7 @@ namespace GestionIntegral.Api.Services.Reportes toUtc(await canillasFechaLiqTask), toUtc(await canillasAccFechaLiqTask), await ctrlDevolucionesRemitosTask ?? Enumerable.Empty(), - await ctrlDevolucionesParaDistCanTask ?? Enumerable.Empty(), + detallesOrdenados, await ctrlDevolucionesOtrosDiasTask ?? Enumerable.Empty(), null ); diff --git a/Frontend/src/pages/Reportes/ReporteListadoDistMensualPage.tsx b/Frontend/src/pages/Reportes/ReporteListadoDistMensualPage.tsx index 5bf2c53..b93c8cf 100644 --- a/Frontend/src/pages/Reportes/ReporteListadoDistMensualPage.tsx +++ b/Frontend/src/pages/Reportes/ReporteListadoDistMensualPage.tsx @@ -1,4 +1,3 @@ -// src/pages/Reportes/ReporteListadoDistMensualPage.tsx import React, { useState, useCallback, useMemo } from 'react'; import { Box, Typography, Paper, CircularProgress, Alert, Button, diff --git a/Frontend/src/pages/Reportes/ReporteListadoDistribucionGeneralPage.tsx b/Frontend/src/pages/Reportes/ReporteListadoDistribucionGeneralPage.tsx index f6615b4..c313e4b 100644 --- a/Frontend/src/pages/Reportes/ReporteListadoDistribucionGeneralPage.tsx +++ b/Frontend/src/pages/Reportes/ReporteListadoDistribucionGeneralPage.tsx @@ -1,116 +1,50 @@ -// src/pages/Reportes/ReporteDetalleDistribucionCanillasPage.tsx -import React, { useState, useCallback, useMemo } from 'react'; +import React, { useState, useCallback } from 'react'; import { - Box, Typography, Paper, CircularProgress, Alert, Button + Box, Typography, Paper, CircularProgress, Alert, Button, + TableContainer, Table, TableHead, TableRow, TableCell, TableBody } from '@mui/material'; -import { DataGrid, type GridColDef, GridFooterContainer, GridFooter } from '@mui/x-data-grid'; -import { esES } from '@mui/x-data-grid/locales'; import reportesService from '../../services/Reportes/reportesService'; -import type { ReporteDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ReporteDistribucionCanillasResponseDto'; -import SeleccionaReporteDetalleDistribucionCanillas from './SeleccionaReporteDetalleDistribucionCanillas'; +import type { ListadoDistribucionGeneralResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionGeneralResponseDto'; +import SeleccionaReporteListadoDistribucionGeneral from './SeleccionaReporteListadoDistribucionGeneral'; import * as XLSX from 'xlsx'; import axios from 'axios'; -interface TotalesComunes { - totalCantSalida: number; - totalCantEntrada: number; - vendidos: number; - totalRendir: number; -} - -const ReporteDetalleDistribucionCanillasPage: React.FC = () => { - const [reportData, setReportData] = useState(null); +const ReporteListadoDistribucionGeneralPage: React.FC = () => { + const [reportData, setReportData] = useState(null); const [loading, setLoading] = useState(false); const [loadingPdf, setLoadingPdf] = useState(false); const [error, setError] = useState(null); const [apiErrorParams, setApiErrorParams] = useState(null); const [showParamSelector, setShowParamSelector] = useState(true); const [currentParams, setCurrentParams] = useState<{ - fecha: string; - idEmpresa: number; - nombreEmpresa?: string; + idPublicacion: number; + fechaDesde: string; // Primer día del mes + fechaHasta: string; // Último día del mes + nombrePublicacion?: string; // Para el nombre del archivo + mesAnioParaNombreArchivo?: string; // Para el nombre del archivo (ej. YYYY-MM) } | null>(null); - const [pdfSoloTotales, setPdfSoloTotales] = useState(false); - - // Estados para los totales de cada sección - const initialTotals: TotalesComunes = { totalCantSalida: 0, totalCantEntrada: 0, vendidos: 0, totalRendir: 0 }; - const [totalesCanillas, setTotalesCanillas] = useState(initialTotals); - const [totalesAccionistas, setTotalesAccionistas] = useState(initialTotals); - const [totalesTodos, setTotalesTodos] = useState(initialTotals); - const [totalesCanillasOtraFecha, setTotalesCanillasOtraFecha] = useState(initialTotals); - const [totalesAccionistasOtraFecha, setTotalesAccionistasOtraFecha] = useState(initialTotals); - - // --- Formateadores --- - const currencyFormatter = (value: number | null | undefined) => - value != null ? value.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' }) : ''; - const numberFormatter = (value: number | null | undefined) => - value != null ? Number(value).toLocaleString('es-AR') : ''; - - const calculateAndSetTotals = (dataArray: Array | undefined, setTotalsFunc: React.Dispatch>) => { - if (dataArray && dataArray.length > 0) { - const totals = dataArray.reduce((acc, item) => { - acc.totalCantSalida += Number(item.totalCantSalida) || 0; - acc.totalCantEntrada += Number(item.totalCantEntrada) || 0; - acc.totalRendir += Number(item.totalRendir) || 0; - return acc; - }, { totalCantSalida: 0, totalCantEntrada: 0, totalRendir: 0 }); - totals.vendidos = totals.totalCantSalida - totals.totalCantEntrada; - setTotalsFunc(totals); - } else { - setTotalsFunc(initialTotals); - } - }; const handleGenerarReporte = useCallback(async (params: { - fecha: string; - idEmpresa: number; + idPublicacion: number; + fechaDesde: string; + fechaHasta: string; }) => { setLoading(true); setError(null); setApiErrorParams(null); - - const empresaService = (await import('../../services/Distribucion/empresaService')).default; - const empData = await empresaService.getEmpresaById(params.idEmpresa); - - setCurrentParams({ ...params, nombreEmpresa: empData?.nombre }); - setReportData(null); // Limpiar datos antiguos - - // Resetear totales - setTotalesCanillas(initialTotals); - setTotalesAccionistas(initialTotals); - setTotalesTodos(initialTotals); - setTotalesCanillasOtraFecha(initialTotals); - setTotalesAccionistasOtraFecha(initialTotals); + // Para el nombre del archivo y título del PDF + const pubService = (await import('../../services/Distribucion/publicacionService')).default; + const pubData = await pubService.getPublicacionById(params.idPublicacion); + const mesAnioParts = params.fechaDesde.split('-'); // YYYY-MM-DD -> [YYYY, MM, DD] + const mesAnioNombre = `${mesAnioParts[1]}/${mesAnioParts[0]}`; + + + setCurrentParams({...params, nombrePublicacion: pubData?.nombre, mesAnioParaNombreArchivo: mesAnioNombre }); try { - const data = await reportesService.getReporteDistribucionCanillas(params); - - const addIds = >(arr: T[] | undefined, prefix: string): Array => - (arr || []).map((item, index) => ({ ...item, id: `${prefix}-${item.publicacion || item.tipoVendedor || 'item'}-${index}-${Math.random().toString(36).substring(7)}` })); - - const processedData = { - canillas: addIds(data.canillas, 'can'), - canillasAccionistas: addIds(data.canillasAccionistas, 'acc'), - canillasTodos: addIds(data.canillasTodos, 'all'), - canillasLiquidadasOtraFecha: addIds(data.canillasLiquidadasOtraFecha, 'canliq'), - canillasAccionistasLiquidadasOtraFecha: addIds(data.canillasAccionistasLiquidadasOtraFecha, 'accliq'), - controlDevolucionesDetalle: addIds(data.controlDevolucionesDetalle, 'cdd'), - controlDevolucionesRemitos: addIds(data.controlDevolucionesRemitos, 'cdr'), - controlDevolucionesOtrosDias: addIds(data.controlDevolucionesOtrosDias, 'cdo') - }; - setReportData(processedData); - - // Calcular y setear totales para cada sección - calculateAndSetTotals(processedData.canillas, setTotalesCanillas); - calculateAndSetTotals(processedData.canillasAccionistas, setTotalesAccionistas); - calculateAndSetTotals(processedData.canillasTodos, setTotalesTodos); - calculateAndSetTotals(processedData.canillasLiquidadasOtraFecha, setTotalesCanillasOtraFecha); - calculateAndSetTotals(processedData.canillasAccionistasLiquidadasOtraFecha, setTotalesAccionistasOtraFecha); - - const noData = (!data.canillas || data.canillas.length === 0) && - (!data.canillasAccionistas || data.canillasAccionistas.length === 0) && - (!data.canillasTodos || data.canillasTodos.length === 0); // Podrías añadir más chequeos si es necesario - if (noData) { + const data = await reportesService.getListadoDistribucionGeneral(params); + setReportData(data); + if ((!data.resumen || data.resumen.length === 0) && (!data.promediosPorDia || data.promediosPorDia.length === 0)) { setError("No se encontraron datos para los parámetros seleccionados."); } setShowParamSelector(false); @@ -134,96 +68,62 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => { }, []); const handleExportToExcel = useCallback(() => { - if (!reportData) { + if (!reportData || (!reportData.resumen?.length && !reportData.promediosPorDia?.length)) { alert("No hay datos para exportar."); return; } const wb = XLSX.utils.book_new(); - const formatAndSheet = ( - data: any[], - sheetName: string, - fields: Record, - totals?: TotalesComunes - ) => { - if (data && data.length > 0) { - let exportedData = data.map(item => { - const row: Record = {}; - const { id, ...itemData } = item; // Excluir el 'id' generado - Object.keys(fields).forEach(key => { - row[fields[key]] = (itemData as any)[key]; - if (key === 'fecha' && (itemData as any)[key]) { - row[fields[key]] = new Date((itemData as any)[key]).toLocaleDateString('es-AR', { timeZone: 'UTC' }); - } - if ((key === 'totalRendir') && (itemData as any)[key] != null) { - row[fields[key]] = parseFloat((itemData as any)[key]); // Mantener como número para suma en Excel - } - if (key === 'vendidos' && itemData.totalCantSalida != null && itemData.totalCantEntrada != null) { - row[fields[key]] = itemData.totalCantSalida - itemData.totalCantEntrada; - } - }); - return row; - }); + if (reportData.resumen?.length) { + const resumenToExport = reportData.resumen.map(item => ({ + "Fecha": item.fecha ? new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-', + "Tirada": item.cantidadTirada, + "Sin Cargo": item.sinCargo, + "Perdidos": item.perdidos, + "Llevados": item.llevados, + "Devueltos": item.devueltos, + "Vendidos": item.vendidos, + })); + const wsResumen = XLSX.utils.json_to_sheet(resumenToExport); + XLSX.utils.book_append_sheet(wb, wsResumen, "ResumenDiario"); + } - if (totals) { - const totalRow: Record = {}; - const fieldKeys = Object.keys(fields); - totalRow[fields[fieldKeys[0]]] = "TOTALES"; // Título en la primera columna - if (fields.totalCantSalida) totalRow[fields.totalCantSalida] = totals.totalCantSalida; - if (fields.totalCantEntrada) totalRow[fields.totalCantEntrada] = totals.totalCantEntrada; - if (fields.vendidos) totalRow[fields.vendidos] = totals.vendidos; - if (fields.totalRendir) totalRow[fields.totalRendir] = totals.totalRendir; - exportedData.push(totalRow); - } + if (reportData.promediosPorDia?.length) { + const promediosToExport = reportData.promediosPorDia.map(item => ({ + "Día Semana": item.dia, + "Cant. Días": item.cantidadDias, + "Prom. Tirada": item.promedioTirada, + "Prom. Sin Cargo": item.promedioSinCargo, + "Prom. Perdidos": item.promedioPerdidos, + "Prom. Llevados": item.promedioLlevados, + "Prom. Devueltos": item.promedioDevueltos, + "Prom. Vendidos": item.promedioVendidos, + })); + const wsPromedios = XLSX.utils.json_to_sheet(promediosToExport); + XLSX.utils.book_append_sheet(wb, wsPromedios, "PromediosPorDia"); + } - const ws = XLSX.utils.json_to_sheet(exportedData); - const headers = Object.values(fields); - ws['!cols'] = headers.map(h => { - const maxLen = Math.max(...exportedData.map(row => (row[h]?.toString() ?? '').length), h.length); - return { wch: maxLen + 2 }; - }); - ws['!freeze'] = { xSplit: 0, ySplit: 1 }; // Congelar primera fila - XLSX.utils.book_append_sheet(wb, ws, sheetName); - } - }; - - const fieldsCanillaAccionista = { publicacion: "Publicación", canilla: "Canilla", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" }; - const fieldsCanillaAccionistaFechaLiq = { publicacion: "Publicación", canilla: "Canilla", fecha:"Fecha Mov.", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" }; - const fieldsTodos = { publicacion: "Publicación", tipoVendedor: "Tipo", totalCantSalida: "Llevados", totalCantEntrada: "Devueltos", vendidos: "Vendidos", totalRendir: "A Rendir" }; - const fieldsCtrlDevDetalle = { ingresados: "Ingresados", sobrantes: "Sobrantes", sinCargo: "Sin Cargo", publicacion: "Publicación", llevados: "Llevados", devueltos: "Devueltos", tipo: "Tipo" }; - const fieldsCtrlDevRemitos = { remito: "Remito Ingresado" }; - const fieldsCtrlDevOtrosDias = { devueltos: "Devueltos Otros Días" }; - - formatAndSheet(reportData.canillas, "Canillitas_Dia", fieldsCanillaAccionista, totalesCanillas); - formatAndSheet(reportData.canillasAccionistas, "Accionistas_Dia", fieldsCanillaAccionista, totalesAccionistas); - formatAndSheet(reportData.canillasTodos, "Resumen_Dia", fieldsTodos, totalesTodos); - formatAndSheet(reportData.canillasLiquidadasOtraFecha, "Canillitas_OtrasFechas", fieldsCanillaAccionistaFechaLiq, totalesCanillasOtraFecha); - formatAndSheet(reportData.canillasAccionistasLiquidadasOtraFecha, "Accionistas_OtrasFechas", fieldsCanillaAccionistaFechaLiq, totalesAccionistasOtraFecha); - formatAndSheet(reportData.controlDevolucionesDetalle, "CtrlDev_Detalle", fieldsCtrlDevDetalle); // Sin totales para estos - formatAndSheet(reportData.controlDevolucionesRemitos, "CtrlDev_Remitos", fieldsCtrlDevRemitos); - formatAndSheet(reportData.controlDevolucionesOtrosDias, "CtrlDev_OtrosDias", fieldsCtrlDevOtrosDias); - - let fileName = "ReporteDetalleDistribucionCanillitas"; + let fileName = "ListadoDistribucionGeneral"; if (currentParams) { - fileName += `_${currentParams.nombreEmpresa?.replace(/\s+/g, '') ?? `Emp${currentParams.idEmpresa}`}`; - fileName += `_${currentParams.fecha}`; + fileName += `_${currentParams.nombrePublicacion?.replace(/\s+/g, '') ?? `Pub${currentParams.idPublicacion}`}`; + fileName += `_${currentParams.mesAnioParaNombreArchivo?.replace('/', '-')}`; } fileName += ".xlsx"; XLSX.writeFile(wb, fileName); - }, [reportData, currentParams, totalesCanillas, totalesAccionistas, totalesTodos, totalesCanillasOtraFecha, totalesAccionistasOtraFecha]); + }, [reportData, currentParams]); - const handleGenerarYAbrirPdf = useCallback(async (soloTotales: boolean) => { + const handleGenerarYAbrirPdf = useCallback(async () => { if (!currentParams) { setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); return; } setLoadingPdf(true); setError(null); - setPdfSoloTotales(soloTotales); try { - const blob = await reportesService.getReporteDistribucionCanillasPdf({ - ...currentParams, - soloTotales + const blob = await reportesService.getListadoDistribucionGeneralPdf({ + idPublicacion: currentParams.idPublicacion, + fechaDesde: currentParams.fechaDesde, // El servicio y SP esperan fechaDesde para el mes/año + fechaHasta: currentParams.fechaHasta // El SP no usa esta, pero el servicio de reporte sí para el nombre }); if (blob.type === "application/json") { const text = await blob.text(); @@ -241,99 +141,13 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => { } }, [currentParams]); - // Definiciones de columnas - const commonColumns: GridColDef[] = [ - { field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1.2 }, - { field: 'canilla', headerName: 'Canillita', width: 220, flex: 1.3 }, - { field: 'totalCantSalida', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) }, - { field: 'totalCantEntrada', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) }, - { field: 'vendidos', headerName: 'Vendidos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueGetter: (_value, row) => (row.totalCantSalida || 0) - (row.totalCantEntrada || 0), valueFormatter: (value) => numberFormatter(Number(value)) }, - { field: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) }, - ]; - - const commonColumnsWithFecha: GridColDef[] = [ - { field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1 }, - { field: 'canilla', headerName: 'Canillita', width: 220, flex: 1.1 }, - { field: 'fecha', headerName: 'Fecha Mov.', width: 120, flex: 0.7, valueFormatter: (value) => value ? new Date(value as string).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-' }, - { field: 'totalCantSalida', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) }, - { field: 'totalCantEntrada', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value))}, - { field: 'vendidos', headerName: 'Vendidos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueGetter: (_value, row) => (row.totalCantSalida || 0) - (row.totalCantEntrada || 0), valueFormatter: (value) => numberFormatter(Number(value)) }, - { field: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) }, - ]; - - const columnsTodos: GridColDef[] = [ - { field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1.2 }, - { field: 'tipoVendedor', headerName: 'Tipo Vendedor', width: 150, flex: 0.8 }, - { field: 'totalCantSalida', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value)) }, - { field: 'totalCantEntrada', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberFormatter(Number(value))}, - { field: 'vendidos', headerName: 'Vendidos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueGetter: (_value, row) => (row.totalCantSalida || 0) - (row.totalCantEntrada || 0), valueFormatter: (value) => numberFormatter(Number(value)) }, - { field: 'totalRendir', headerName: 'A Rendir', type: 'number', width: 150, align: 'right', headerAlign: 'right', valueFormatter: (value) => currencyFormatter(Number(value)) }, - ]; - - const columnsCtrlDevDetalle: GridColDef[] = [ - { field: 'publicacion', headerName: 'Publicación', width: 200, flex: 1.5 }, - { field: 'tipo', headerName: 'Tipo', width: 100, flex: 0.8 }, - { field: 'ingresados', headerName: 'Ingresados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (v) => numberFormatter(Number(v))}, - { field: 'sobrantes', headerName: 'Sobrantes', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (v) => numberFormatter(Number(v))}, - { field: 'sinCargo', headerName: 'Sin Cargo', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (v) => numberFormatter(Number(v))}, - { field: 'llevados', headerName: 'Llevados', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (v) => numberFormatter(Number(v))}, - { field: 'devueltos', headerName: 'Devueltos', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (v) => numberFormatter(Number(v))}, - ]; - - const columnsCtrlDevRemitos: GridColDef[] = [ - { field: 'remito', headerName: 'Remito Ingresado', flex: 1 }, - ]; - - const columnsCtrlDevOtrosDias: GridColDef[] = [ - { field: 'devueltos', headerName: 'Devueltos Otros Días', flex: 1 }, - ]; - - // Memoizar filas (los IDs ya se añaden en handleGenerarReporte) - const rowsCanillas = useMemo(() => reportData?.canillas ?? [], [reportData]); - const rowsAccionistas = useMemo(() => reportData?.canillasAccionistas ?? [], [reportData]); - const rowsTodos = useMemo(() => reportData?.canillasTodos ?? [], [reportData]); - const rowsCanillasOtraFecha = useMemo(() => reportData?.canillasLiquidadasOtraFecha ?? [], [reportData]); - const rowsAccionistasOtraFecha = useMemo(() => reportData?.canillasAccionistasLiquidadasOtraFecha ?? [], [reportData]); - const rowsCtrlDevDetalle = useMemo(() => reportData?.controlDevolucionesDetalle ?? [], [reportData]); - const rowsCtrlDevRemitos = useMemo(() => reportData?.controlDevolucionesRemitos ?? [], [reportData]); - const rowsCtrlDevOtrosDias = useMemo(() => reportData?.controlDevolucionesOtrosDias ?? [], [reportData]); - - // --- Custom Footers --- - // eslint-disable-next-line react/display-name - const createCustomFooter = (totals: TotalesComunes, columns: GridColDef[]) => () => ( - - - - - - TOTALES: - {columns[1].field !== 'tipoVendedor' && /* Placeholder for Canilla/Tipo */ } - {columns[1].field === 'tipoVendedor' && /* Placeholder for Canilla/Tipo */ } - - {columns.find(c => c.field === 'fecha') && c.field === 'fecha')?.flex, width: columns.find(c=>c.field === 'fecha')?.width, textAlign: 'right', fontWeight: 'bold', pr:1 }}> /* Placeholder for Fecha */} - - c.field === 'totalCantSalida')?.width, textAlign: 'right', fontWeight: 'bold', pr:1 }}>{numberFormatter(totals.totalCantSalida)} - c.field === 'totalCantEntrada')?.width, textAlign: 'right', fontWeight: 'bold', pr:1 }}>{numberFormatter(totals.totalCantEntrada)} - c.field === 'vendidos')?.width, textAlign: 'right', fontWeight: 'bold', pr:1 }}>{numberFormatter(totals.vendidos)} - c.field === 'totalRendir')?.width, textAlign: 'right', fontWeight: 'bold' }}>{currencyFormatter(totals.totalRendir)} - - - ); - - const CustomFooterCanillas = useMemo(() => createCustomFooter(totalesCanillas, commonColumns), [totalesCanillas]); - const CustomFooterAccionistas = useMemo(() => createCustomFooter(totalesAccionistas, commonColumns), [totalesAccionistas]); - const CustomFooterTodos = useMemo(() => createCustomFooter(totalesTodos, columnsTodos), [totalesTodos]); - const CustomFooterCanillasOtraFecha = useMemo(() => createCustomFooter(totalesCanillasOtraFecha, commonColumnsWithFecha), [totalesCanillasOtraFecha]); - const CustomFooterAccionistasOtraFecha = useMemo(() => createCustomFooter(totalesAccionistasOtraFecha, commonColumnsWithFecha), [totalesAccionistasOtraFecha]); - - if (showParamSelector) { return ( - @@ -345,13 +159,10 @@ const ReporteDetalleDistribucionCanillasPage: React.FC = () => { return ( - Reporte: Detalle Distribución Canillitas ({currentParams?.nombreEmpresa}) - {currentParams?.fecha} - - -