| | | 1 | | using System.IO.Compression; |
| | | 2 | | using FAU.DataAccess.Repositories; |
| | | 3 | | using FAU.Entidades; |
| | | 4 | | using QuestPDF.Fluent; |
| | | 5 | | using QuestPDF.Helpers; |
| | | 6 | | using QuestPDF.Infrastructure; |
| | | 7 | | |
| | | 8 | | namespace FAU.Logica.Services; |
| | | 9 | | |
| | | 10 | | public class ReciboService : IReciboService |
| | | 11 | | { |
| | | 12 | | private readonly IReciboRepository _repo; |
| | | 13 | | |
| | 8 | 14 | | public ReciboService(IReciboRepository repo) |
| | | 15 | | { |
| | 8 | 16 | | _repo = repo; |
| | 8 | 17 | | } |
| | | 18 | | |
| | | 19 | | public async Task<List<ReciboResumenDto>> ObtenerResumenesAsync(long periodoId, ReciboFiltroRequest filtro) |
| | | 20 | | { |
| | 0 | 21 | | var version = await _repo.ObtenerVersionMaximaAsync(periodoId); |
| | 0 | 22 | | return await _repo.ObtenerResumenesAsync(periodoId, version, filtro); |
| | 0 | 23 | | } |
| | | 24 | | |
| | | 25 | | public async Task<ReciboDto?> ObtenerReciboAsync(long periodoId, long relacionId) |
| | | 26 | | { |
| | 4 | 27 | | var version = await _repo.ObtenerVersionMaximaAsync(periodoId); |
| | 4 | 28 | | return await _repo.ObtenerDatosReciboAsync(periodoId, version, relacionId); |
| | 4 | 29 | | } |
| | | 30 | | |
| | | 31 | | public async Task<byte[]> GenerarPdfAsync(long periodoId, long relacionId) |
| | | 32 | | { |
| | 4 | 33 | | var version = await _repo.ObtenerVersionMaximaAsync(periodoId); |
| | 4 | 34 | | var recibo = await _repo.ObtenerDatosReciboAsync(periodoId, version, relacionId) |
| | 4 | 35 | | ?? throw new KeyNotFoundException($"No se encontró liquidación para la relación {relacionId} en el período { |
| | | 36 | | |
| | 3 | 37 | | return GenerarPdfDesdeRecibo(recibo); |
| | 3 | 38 | | } |
| | | 39 | | |
| | | 40 | | public async Task<Stream> GenerarLoteZipAsync(long periodoId, ReciboFiltroRequest filtro) |
| | | 41 | | { |
| | 0 | 42 | | var version = await _repo.ObtenerVersionMaximaAsync(periodoId); |
| | 0 | 43 | | var resumenes = await _repo.ObtenerResumenesAsync(periodoId, version, filtro); |
| | | 44 | | |
| | 0 | 45 | | var memoryStream = new MemoryStream(); |
| | 0 | 46 | | using (var zip = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true)) |
| | | 47 | | { |
| | 0 | 48 | | foreach (var resumen in resumenes) |
| | | 49 | | { |
| | 0 | 50 | | var recibo = await _repo.ObtenerDatosReciboAsync(periodoId, version, resumen.RelacionId); |
| | 0 | 51 | | if (recibo == null) continue; |
| | | 52 | | |
| | 0 | 53 | | var pdfBytes = GenerarPdfDesdeRecibo(recibo); |
| | 0 | 54 | | var entry = zip.CreateEntry($"recibo_{resumen.Cedula}.pdf", CompressionLevel.Fastest); |
| | 0 | 55 | | using var entryStream = entry.Open(); |
| | 0 | 56 | | await entryStream.WriteAsync(pdfBytes); |
| | 0 | 57 | | } |
| | 0 | 58 | | } |
| | | 59 | | |
| | 0 | 60 | | memoryStream.Position = 0; |
| | 0 | 61 | | return memoryStream; |
| | 0 | 62 | | } |
| | | 63 | | |
| | | 64 | | private static byte[] GenerarPdfDesdeRecibo(ReciboDto recibo) |
| | | 65 | | { |
| | 3 | 66 | | var nombreMes = System.Globalization.CultureInfo |
| | 3 | 67 | | .GetCultureInfo("es-UY") |
| | 3 | 68 | | .DateTimeFormat.GetMonthName(recibo.Mes); |
| | | 69 | | |
| | 3 | 70 | | var nombreCompleto = string.Join(" ", |
| | 3 | 71 | | new[] { recibo.PrimerNombre, recibo.SegundoNombre, recibo.PrimerApellido, recibo.SegundoApellido } |
| | 15 | 72 | | .Where(s => !string.IsNullOrWhiteSpace(s))); |
| | | 73 | | |
| | 3 | 74 | | return Document.Create(container => |
| | 3 | 75 | | { |
| | 3 | 76 | | container.Page(page => |
| | 3 | 77 | | { |
| | 3 | 78 | | page.Size(PageSizes.A4); |
| | 3 | 79 | | page.Margin(30); |
| | 6 | 80 | | page.DefaultTextStyle(x => x.FontSize(9)); |
| | 3 | 81 | | |
| | 3 | 82 | | page.Content().Column(col => |
| | 3 | 83 | | { |
| | 3 | 84 | | // Encabezado |
| | 3 | 85 | | col.Item().BorderBottom(1).PaddingBottom(6).Row(row => |
| | 3 | 86 | | { |
| | 3 | 87 | | row.RelativeItem().Column(c => |
| | 3 | 88 | | { |
| | 3 | 89 | | c.Item().Text("FUERZAS ARMADAS DEL URUGUAY").Bold().FontSize(12); |
| | 3 | 90 | | c.Item().Text($"RECIBO DE SUELDO — {nombreMes.ToUpper()} {recibo.Anio}").FontSize(10); |
| | 6 | 91 | | }); |
| | 3 | 92 | | row.ConstantItem(120).AlignRight().Column(c => |
| | 3 | 93 | | { |
| | 3 | 94 | | c.Item().Text($"Período: {recibo.Mes:D2}/{recibo.Anio}").Bold(); |
| | 3 | 95 | | c.Item().Text($"Régimen: {recibo.Regimen}"); |
| | 6 | 96 | | }); |
| | 6 | 97 | | }); |
| | 3 | 98 | | |
| | 3 | 99 | | col.Item().PaddingTop(8).PaddingBottom(4).Row(row => |
| | 3 | 100 | | { |
| | 3 | 101 | | row.RelativeItem().Column(c => |
| | 3 | 102 | | { |
| | 12 | 103 | | c.Item().Text(t => { t.Span("Funcionario: ").Bold(); t.Span(nombreCompleto); }); |
| | 12 | 104 | | c.Item().Text(t => { t.Span("Cédula: ").Bold(); t.Span(recibo.Cedula); }); |
| | 12 | 105 | | c.Item().Text(t => { t.Span("Grado: ").Bold(); t.Span(recibo.Grado); }); |
| | 12 | 106 | | c.Item().Text(t => { t.Span("Escalafón: ").Bold(); t.Span(recibo.Escalafon); }); |
| | 6 | 107 | | }); |
| | 3 | 108 | | row.RelativeItem().Column(c => |
| | 3 | 109 | | { |
| | 12 | 110 | | c.Item().Text(t => { t.Span("Unidad: ").Bold(); t.Span(recibo.Unidad); }); |
| | 12 | 111 | | c.Item().Text(t => { t.Span("Programa: ").Bold(); t.Span(recibo.Programa); }); |
| | 6 | 112 | | }); |
| | 6 | 113 | | }); |
| | 3 | 114 | | |
| | 3 | 115 | | // Tabla haberes |
| | 3 | 116 | | col.Item().PaddingTop(8).Text("HABERES").Bold().FontSize(10); |
| | 3 | 117 | | col.Item().Table(table => |
| | 3 | 118 | | { |
| | 3 | 119 | | table.ColumnsDefinition(cols => |
| | 3 | 120 | | { |
| | 3 | 121 | | cols.ConstantColumn(60); |
| | 3 | 122 | | cols.RelativeColumn(); |
| | 3 | 123 | | cols.ConstantColumn(80); |
| | 3 | 124 | | cols.ConstantColumn(80); |
| | 6 | 125 | | }); |
| | 3 | 126 | | AgregarEncabezadoTabla(table); |
| | 18 | 127 | | foreach (var item in recibo.Haberes) |
| | 6 | 128 | | AgregarFilaTabla(table, item); |
| | 3 | 129 | | AgregarFilaSubtotal(table, "Subtotal Haberes", recibo.TotalHaberes); |
| | 6 | 130 | | }); |
| | 3 | 131 | | |
| | 3 | 132 | | // Tabla descuentos legales |
| | 3 | 133 | | col.Item().PaddingTop(8).Text("DESCUENTOS LEGALES").Bold().FontSize(10); |
| | 3 | 134 | | col.Item().Table(table => |
| | 3 | 135 | | { |
| | 3 | 136 | | table.ColumnsDefinition(cols => |
| | 3 | 137 | | { |
| | 3 | 138 | | cols.ConstantColumn(60); |
| | 3 | 139 | | cols.RelativeColumn(); |
| | 3 | 140 | | cols.ConstantColumn(80); |
| | 3 | 141 | | cols.ConstantColumn(80); |
| | 6 | 142 | | }); |
| | 3 | 143 | | AgregarEncabezadoTabla(table); |
| | 18 | 144 | | foreach (var item in recibo.DescuentosLegales) |
| | 6 | 145 | | AgregarFilaTabla(table, item, negativo: true); |
| | 3 | 146 | | AgregarFilaSubtotal(table, "Subtotal Desc. Legales", recibo.TotalDescuentosLegales); |
| | 6 | 147 | | }); |
| | 3 | 148 | | |
| | 3 | 149 | | // Tabla descuentos personales |
| | 3 | 150 | | if (recibo.DescuentosPersonales.Any()) |
| | 3 | 151 | | { |
| | 2 | 152 | | col.Item().PaddingTop(8).Text("DESCUENTOS PERSONALES").Bold().FontSize(10); |
| | 2 | 153 | | col.Item().Table(table => |
| | 2 | 154 | | { |
| | 2 | 155 | | table.ColumnsDefinition(cols => |
| | 2 | 156 | | { |
| | 2 | 157 | | cols.ConstantColumn(60); |
| | 2 | 158 | | cols.RelativeColumn(); |
| | 2 | 159 | | cols.ConstantColumn(80); |
| | 2 | 160 | | cols.ConstantColumn(80); |
| | 4 | 161 | | }); |
| | 2 | 162 | | AgregarEncabezadoTabla(table); |
| | 8 | 163 | | foreach (var item in recibo.DescuentosPersonales) |
| | 2 | 164 | | AgregarFilaTabla(table, item, negativo: true); |
| | 2 | 165 | | AgregarFilaSubtotal(table, "Subtotal Desc. Personales", recibo.TotalDescuentosPersonales); |
| | 4 | 166 | | }); |
| | 3 | 167 | | } |
| | 3 | 168 | | |
| | 3 | 169 | | // Resumen final |
| | 3 | 170 | | col.Item().PaddingTop(10).BorderTop(1).PaddingTop(6).Row(row => |
| | 3 | 171 | | { |
| | 3 | 172 | | row.RelativeItem(); |
| | 3 | 173 | | row.ConstantItem(200).Column(c => |
| | 3 | 174 | | { |
| | 3 | 175 | | c.Item().Row(r => |
| | 3 | 176 | | { |
| | 3 | 177 | | r.RelativeItem().Text("Total Haberes").Bold(); |
| | 3 | 178 | | r.ConstantItem(100).AlignRight().Text($"{recibo.TotalHaberes:N2}").Bold(); |
| | 6 | 179 | | }); |
| | 3 | 180 | | c.Item().Row(r => |
| | 3 | 181 | | { |
| | 3 | 182 | | r.RelativeItem().Text("Total Descuentos"); |
| | 3 | 183 | | r.ConstantItem(100).AlignRight().Text($"({recibo.TotalDescuentos:N2})"); |
| | 6 | 184 | | }); |
| | 3 | 185 | | c.Item().BorderTop(1).PaddingTop(3).Row(r => |
| | 3 | 186 | | { |
| | 3 | 187 | | r.RelativeItem().Text("LÍQUIDO PAGABLE").Bold().FontSize(11); |
| | 3 | 188 | | r.ConstantItem(100).AlignRight().Text($"{recibo.LiquidoPagable:N2}").Bold().FontSize(11) |
| | 6 | 189 | | }); |
| | 6 | 190 | | }); |
| | 6 | 191 | | }); |
| | 3 | 192 | | |
| | 3 | 193 | | // Datos bancarios |
| | 3 | 194 | | col.Item().PaddingTop(10).Row(row => |
| | 3 | 195 | | { |
| | 3 | 196 | | row.RelativeItem().Column(c => |
| | 3 | 197 | | { |
| | 3 | 198 | | c.Item().Text("DATOS BANCARIOS").Bold(); |
| | 3 | 199 | | if (recibo.DatosBancarios.Cuenta != null) |
| | 3 | 200 | | { |
| | 8 | 201 | | c.Item().Text(t => { t.Span("Banco: ").Bold(); t.Span(recibo.DatosBancarios.Banco ?? "-" |
| | 8 | 202 | | c.Item().Text(t => { t.Span("Cuenta: ").Bold(); t.Span(recibo.DatosBancarios.Cuenta); }) |
| | 3 | 203 | | } |
| | 3 | 204 | | else |
| | 3 | 205 | | { |
| | 1 | 206 | | c.Item().Text(recibo.DatosBancarios.Observacion).Italic(); |
| | 3 | 207 | | } |
| | 4 | 208 | | }); |
| | 6 | 209 | | }); |
| | 6 | 210 | | }); |
| | 6 | 211 | | }); |
| | 6 | 212 | | }).GeneratePdf(); |
| | | 213 | | } |
| | | 214 | | |
| | | 215 | | private static void AgregarEncabezadoTabla(TableDescriptor table) |
| | | 216 | | { |
| | 8 | 217 | | table.Header(header => |
| | 8 | 218 | | { |
| | 8 | 219 | | header.Cell().Background(Colors.Grey.Lighten2).Padding(3).Text("Código").Bold(); |
| | 8 | 220 | | header.Cell().Background(Colors.Grey.Lighten2).Padding(3).Text("Concepto").Bold(); |
| | 8 | 221 | | header.Cell().Background(Colors.Grey.Lighten2).Padding(3).AlignRight().Text("Rubro").Bold(); |
| | 8 | 222 | | header.Cell().Background(Colors.Grey.Lighten2).Padding(3).AlignRight().Text("Importe").Bold(); |
| | 16 | 223 | | }); |
| | 8 | 224 | | } |
| | | 225 | | |
| | | 226 | | private static void AgregarFilaTabla(TableDescriptor table, ReciboItemDto item, bool negativo = false) |
| | | 227 | | { |
| | 14 | 228 | | var monto = negativo ? Math.Abs(item.Monto) : item.Monto; |
| | 14 | 229 | | table.Cell().Padding(3).Text(item.Codigo); |
| | 14 | 230 | | table.Cell().Padding(3).Text(item.Descripcion); |
| | 14 | 231 | | table.Cell().Padding(3).AlignRight().Text(item.RubroContable); |
| | 14 | 232 | | table.Cell().Padding(3).AlignRight().Text($"{monto:N2}"); |
| | 14 | 233 | | } |
| | | 234 | | |
| | | 235 | | private static void AgregarFilaSubtotal(TableDescriptor table, string label, decimal total) |
| | | 236 | | { |
| | 8 | 237 | | table.Cell().ColumnSpan(3).Padding(3).AlignRight().Text(label).Bold(); |
| | 8 | 238 | | table.Cell().Padding(3).AlignRight().Text($"{total:N2}").Bold(); |
| | 8 | 239 | | } |
| | | 240 | | } |