< Summary

Information
Class: FAU.Logica.Services.ReciboService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/ReciboService.cs
Line coverage
78%
Covered lines: 205
Uncovered lines: 55
Coverable lines: 260
Total lines: 331
Line coverage: 78.8%
Branch coverage
58%
Covered branches: 58
Total branches: 100
Branch coverage: 58%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ObtenerResumenesAsync()100%210%
ObtenerReciboAsync()100%11100%
GenerarPdfAsync()100%22100%
GenerarLoteZipAsync()0%4260%
.cctor()100%11100%
CargarLogo()50%22100%
GenerarPdfDesdeRecibo(...)70.83%827287.7%
Monto(...)100%11100%
FilaItem(...)100%11100%
FilaSeparador(...)100%11100%
FilaTotalBold(...)100%11100%
ObtenerFechas(...)22.22%1401827.77%

File(s)

/home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/ReciboService.cs

#LineLine coverage
 1using System.Globalization;
 2using System.IO.Compression;
 3using System.Reflection;
 4using FAU.DataAccess.Repositories;
 5using FAU.Entidades;
 6using QuestPDF.Fluent;
 7using QuestPDF.Helpers;
 8using QuestPDF.Infrastructure;
 9
 10namespace FAU.Logica.Services;
 11
 12public class ReciboService : IReciboService
 13{
 14    private readonly IReciboRepository _repo;
 15
 816    public ReciboService(IReciboRepository repo)
 17    {
 818        _repo = repo;
 819    }
 20
 21    public async Task<List<ReciboResumenDto>> ObtenerResumenesAsync(long periodoId, ReciboFiltroRequest filtro)
 22    {
 023        var version = await _repo.ObtenerVersionMaximaAsync(periodoId);
 024        return await _repo.ObtenerResumenesAsync(periodoId, version, filtro);
 025    }
 26
 27    public async Task<ReciboDto?> ObtenerReciboAsync(long periodoId, long relacionId)
 28    {
 429        var version = await _repo.ObtenerVersionMaximaAsync(periodoId);
 430        return await _repo.ObtenerDatosReciboAsync(periodoId, version, relacionId);
 431    }
 32
 33    public async Task<byte[]> GenerarPdfAsync(long periodoId, long relacionId)
 34    {
 435        var version = await _repo.ObtenerVersionMaximaAsync(periodoId);
 436        var recibo = await _repo.ObtenerDatosReciboAsync(periodoId, version, relacionId)
 437            ?? throw new KeyNotFoundException($"No se encontró liquidación para la relación {relacionId} en el período {
 38
 339        return GenerarPdfDesdeRecibo(recibo);
 340    }
 41
 42    public async Task<Stream> GenerarLoteZipAsync(long periodoId, ReciboFiltroRequest filtro)
 43    {
 044        var version = await _repo.ObtenerVersionMaximaAsync(periodoId);
 045        var resumenes = await _repo.ObtenerResumenesAsync(periodoId, version, filtro);
 46
 047        var memoryStream = new MemoryStream();
 048        using (var zip = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true))
 49        {
 050            foreach (var resumen in resumenes)
 51            {
 052                var recibo = await _repo.ObtenerDatosReciboAsync(periodoId, version, resumen.RelacionLaboralId);
 053                if (recibo == null) continue;
 54
 055                var pdfBytes = GenerarPdfDesdeRecibo(recibo);
 056                var entry = zip.CreateEntry($"recibo_{resumen.Cedula}.pdf", CompressionLevel.Fastest);
 057                using var entryStream = entry.Open();
 058                await entryStream.WriteAsync(pdfBytes);
 059            }
 060        }
 61
 062        memoryStream.Position = 0;
 063        return memoryStream;
 064    }
 65
 166    private static readonly byte[]? _logoBytes = CargarLogo();
 67
 68    private static byte[]? CargarLogo()
 69    {
 170        var asm = Assembly.GetExecutingAssembly();
 171        var nombre = asm.GetManifestResourceNames()
 272            .FirstOrDefault(n => n.EndsWith("fau-logo.png", StringComparison.OrdinalIgnoreCase));
 173        if (nombre is null) return null;
 174        using var stream = asm.GetManifestResourceStream(nombre)!;
 175        using var ms = new MemoryStream();
 176        stream.CopyTo(ms);
 177        return ms.ToArray();
 178    }
 79
 80    private static byte[] GenerarPdfDesdeRecibo(ReciboDto recibo)
 81    {
 382        var ci = CultureInfo.GetCultureInfo("es-UY");
 383        var nombreMes = ci.TextInfo.ToTitleCase(ci.DateTimeFormat.GetMonthName(recibo.Mes));
 84
 385        var apellidos = string.IsNullOrWhiteSpace(recibo.SegundoApellido)
 386            ? recibo.PrimerApellido
 387            : $"{recibo.PrimerApellido} {recibo.SegundoApellido}";
 388        var nombres = string.IsNullOrWhiteSpace(recibo.SegundoNombre)
 389            ? recibo.PrimerNombre
 390            : $"{recibo.PrimerNombre} {recibo.SegundoNombre}";
 391        var apellidoNombre = $"{apellidos.ToUpper()}, {nombres.ToUpper()}";
 92
 393        var (fecha1Label, fecha1Value, fecha2Label, fecha2Value) = ObtenerFechas(recibo);
 394        var descuentosFooter = recibo.Nominal - recibo.ACobrar;
 95
 396        return Document.Create(container =>
 397        {
 398            container.Page(page =>
 399            {
 3100                page.Size(PageSizes.A4);
 3101                page.MarginHorizontal(20);
 3102                page.MarginTop(15);
 3103                page.MarginBottom(15);
 6104                page.DefaultTextStyle(x => x.FontSize(7.5f).FontFamily("Courier New"));
 3105
 3106                page.Content().Column(col =>
 3107                {
 3108                    // ── ENCABEZADO ────────────────────────────────────────
 3109                    col.Item().PaddingBottom(4).Row(row =>
 3110                    {
 3111                        row.RelativeItem().Row(r =>
 3112                        {
 3113                            if (_logoBytes is not null)
 3114                                r.ConstantItem(90).Image(_logoBytes).FitArea();
 3115                            r.RelativeItem().PaddingLeft(_logoBytes is not null ? 6 : 0)
 3116                             .Column(c =>
 3117                             {
 3118                                 c.Item().Text("FUERZA AÉREA").Bold().FontSize(13).FontFamily("Arial");
 3119                                 c.Item().Text("URUGUAYA").Bold().FontSize(13).FontFamily("Arial");
 6120                             });
 6121                        });
 3122                        row.ConstantItem(165).Column(c =>
 3123                        {
 3124                            c.Item().Border(1).Padding(6).Column(inner =>
 3125                            {
 3126                                inner.Item().Text($" MES DE : {nombreMes} {recibo.Anio}");
 3127                                inner.Item().Text(" ");
 6128                            });
 6129                        });
 6130                    });
 3131
 3132                    // ── FILA DE ENCABEZADOS DEL FUNCIONARIO ───────────────
 3133                    col.Item().Border(1).Row(row =>
 3134                    {
 3135                        row.ConstantItem(85).BorderRight(1).PaddingHorizontal(5).PaddingVertical(3)
 3136                           .Text("CEDULA").Bold();
 3137                        row.ConstantItem(80).BorderRight(1).PaddingHorizontal(5).PaddingVertical(3)
 3138                           .AlignCenter().Text("GDO").Bold();
 3139                        row.ConstantItem(32).BorderRight(1).PaddingHorizontal(5).PaddingVertical(3)
 3140                           .AlignCenter().Text("ESC").Bold();
 3141                        row.RelativeItem().BorderRight(1).PaddingHorizontal(5).PaddingVertical(3)
 3142                           .Text("APELLIDO Y NOMBRE").Bold();
 3143                        row.ConstantItem(22).BorderRight(1).PaddingHorizontal(4).PaddingVertical(3)
 3144                           .AlignCenter().Text("UN").Bold();
 3145                        row.ConstantItem(22).BorderRight(1).PaddingHorizontal(4).PaddingVertical(3)
 3146                           .AlignCenter().Text("CIA").Bold();
 3147                        row.ConstantItem(138).PaddingHorizontal(5).PaddingVertical(3)
 3148                           .AlignCenter().Text("FECHAS").Bold();
 6149                    });
 3150
 3151                    // ── FILA DE DATOS DEL FUNCIONARIO ─────────────────────
 3152                    col.Item().BorderLeft(1).BorderRight(1).BorderBottom(1).Row(row =>
 3153                    {
 3154                        row.ConstantItem(85).BorderRight(1).PaddingHorizontal(5).PaddingVertical(3)
 3155                           .Text(recibo.Cedula);
 3156                        row.ConstantItem(80).BorderRight(1).PaddingHorizontal(5).PaddingVertical(3)
 3157                           .Text(recibo.Grado);
 3158                        row.ConstantItem(32).BorderRight(1).PaddingHorizontal(5).PaddingVertical(3)
 3159                           .Text(recibo.Escalafon);
 3160                        row.RelativeItem().BorderRight(1).PaddingHorizontal(5).PaddingVertical(3)
 3161                           .Text(apellidoNombre);
 3162                        row.ConstantItem(22).BorderRight(1).PaddingHorizontal(4).PaddingVertical(3)
 3163                           .AlignCenter().Text(recibo.UnidadCodigo);
 3164                        row.ConstantItem(22).BorderRight(1).PaddingHorizontal(4).PaddingVertical(3)
 3165                           .AlignCenter().Text(recibo.Compania);
 3166                        row.ConstantItem(138).BorderLeft(1).PaddingHorizontal(5).PaddingVertical(3)
 3167                           .Column(c =>
 3168                           {
 3169                               if (fecha1Label != null)
 0170                                   c.Item().Text(t =>
 0171                                   {
 0172                                       t.Span(fecha1Label).Bold();
 0173                                       t.Span(fecha1Value ?? "");
 0174                                   });
 3175                               if (fecha2Label != null)
 0176                                   c.Item().Text(t =>
 0177                                   {
 0178                                       t.Span(fecha2Label).Bold();
 0179                                       t.Span(fecha2Value ?? "");
 0180                                   });
 3181                               if (fecha1Label == null)
 3182                                   c.Item().Text(" "); // blank row for alignment
 6183                           });
 6184                    });
 3185
 3186                    // ── CUERPO DE 3 COLUMNAS ──────────────────────────────
 3187                    col.Item().Extend()
 3188                       .BorderLeft(1).BorderRight(1).BorderTop(1)
 3189                       .Row(row =>
 3190                    {
 3191                        // Columna 1: Haberes + Beneficios
 3192                        row.RelativeItem(40).BorderRight(1).Column(c =>
 3193                        {
 18194                            foreach (var item in recibo.Haberes)
 6195                                FilaItem(c, item.Descripcion, item.Monto);
 3196
 3197                            FilaSeparador(c, "----------------");
 3198                            FilaTotalBold(c, "TOTAL DE HABERES", recibo.TotalHaberes);
 3199
 3200                            if (recibo.Beneficios.Any())
 3201                            {
 0202                                c.Item().Text(" ");
 0203                                foreach (var item in recibo.Beneficios)
 0204                                    FilaItem(c, item.Descripcion, item.Monto);
 0205                                FilaSeparador(c, "----------------");
 0206                                FilaTotalBold(c, "TOTAL DE BENEFIC", recibo.TotalBeneficios);
 3207                            }
 6208                        });
 3209
 3210                        // Columna 2: Descuentos Legales + ítems de Descuentos Personales
 3211                        row.RelativeItem(35).BorderRight(1).Column(c =>
 3212                        {
 18213                            foreach (var item in recibo.DescuentosLegales)
 6214                                FilaItem(c, item.Descripcion, Math.Abs(item.Monto));
 3215
 3216                            FilaSeparador(c, "------------");
 3217                            FilaTotalBold(c, "TOTAL DESC.", recibo.TotalDescuentosLegales);
 3218
 3219                            if (recibo.DescuentosPersonales.Any())
 3220                            {
 2221                                c.Item().Text(" ");
 8222                                foreach (var item in recibo.DescuentosPersonales)
 2223                                    FilaItem(c, item.Descripcion, Math.Abs(item.Monto));
 3224                            }
 6225                        });
 3226
 3227                        // Columna 3: Total de Descuentos Personales + Ajuste
 3228                        row.RelativeItem(25).Column(c =>
 3229                        {
 3230                            if (recibo.TotalDescuentosPersonales > 0)
 3231                            {
 3232                                FilaSeparador(c, "----------------");
 3233                                FilaTotalBold(c, "TOTAL DESC. PERS.", recibo.TotalDescuentosPersonales);
 3234                            }
 3235
 3236                            if (recibo.Ajuste != 0)
 3237                            {
 0238                                if (recibo.TotalDescuentosPersonales > 0)
 0239                                    c.Item().Text(" ");
 0240                                c.Item().Row(r =>
 0241                                {
 0242                                    r.RelativeItem().PaddingHorizontal(5).Text("AJUSTE");
 0243                                    r.ConstantItem(60).AlignRight().PaddingHorizontal(5)
 0244                                     .Text(Monto(recibo.Ajuste));
 0245                                });
 3246                            }
 6247                        });
 6248                    });
 6249                });
 3250
 3251                // ── PIE: NOMINAL | DESCUENTOS | A COBRAR ─────────────────
 3252                page.Footer().Border(1).Row(row =>
 3253                {
 3254                    row.RelativeItem().BorderRight(1).Padding(5).Row(r =>
 3255                    {
 6256                        r.RelativeItem().Text(t => t.Span("NOMINAL....: ").Bold());
 3257                        r.ConstantItem(75).AlignRight().Text(Monto(recibo.Nominal));
 6258                    });
 3259                    row.RelativeItem().BorderRight(1).Padding(5).Row(r =>
 3260                    {
 6261                        r.RelativeItem().Text(t => t.Span("DESCUENTOS.: ").Bold());
 3262                        r.ConstantItem(75).AlignRight().Text(Monto(descuentosFooter));
 6263                    });
 3264                    row.RelativeItem().Padding(5).Row(r =>
 3265                    {
 6266                        r.RelativeItem().Text(t => t.Span("A COBRAR...: ").Bold());
 3267                        r.ConstantItem(75).AlignRight().Text(Monto(recibo.ACobrar));
 6268                    });
 6269                });
 6270            });
 6271        }).GeneratePdf();
 272    }
 273
 274    private static string Monto(decimal valor) =>
 32275        valor.ToString("N2", CultureInfo.InvariantCulture);
 276
 277    private static void FilaItem(ColumnDescriptor col, string descripcion, decimal monto)
 278    {
 14279        col.Item().Row(r =>
 14280        {
 14281            r.RelativeItem().PaddingHorizontal(5).Text(descripcion);
 14282            r.ConstantItem(62).AlignRight().PaddingHorizontal(5).Text(Monto(monto));
 28283        });
 14284    }
 285
 286    private static void FilaSeparador(ColumnDescriptor col, string _)
 287    {
 9288        col.Item().PaddingHorizontal(5).PaddingVertical(2).LineHorizontal(0.5f);
 9289    }
 290
 291    private static void FilaTotalBold(ColumnDescriptor col, string label, decimal total)
 292    {
 9293        col.Item().Row(r =>
 9294        {
 9295            r.RelativeItem().PaddingHorizontal(5).Text(label).Bold();
 9296            r.ConstantItem(62).AlignRight().PaddingHorizontal(5).Text(Monto(total)).Bold();
 18297        });
 9298    }
 299
 300    private static (string? l1, string? v1, string? l2, string? v2) ObtenerFechas(ReciboDto recibo)
 301    {
 302        const string fmt = "dd/MM/yyyy";
 303
 3304        if (recibo.EsCivil)
 0305            return ("Fecha Ing.(FIAP): ", recibo.FechaInicio != default
 0306                ? recibo.FechaInicio.ToString(fmt) : string.Empty, null, null);
 307
 3308        if (recibo.EsOficial)
 309        {
 0310            var v1 = recibo.FechaInicio != default ? recibo.FechaInicio.ToString(fmt) : string.Empty;
 0311            if (recibo.FechaUltimoAscenso.HasValue)
 0312                return ("Asc. a Oficial: ", v1,
 0313                        "Ult. Ascenso .: ", recibo.FechaUltimoAscenso.Value.ToString(fmt));
 0314            return ("Asc. a Oficial: ", v1, null, null);
 315        }
 316
 3317        if (recibo.EsSubalterno)
 318        {
 0319            var v1 = recibo.FechaInicio != default ? recibo.FechaInicio.ToString(fmt) : string.Empty;
 0320            if (recibo.FechaUltimoAscenso.HasValue)
 0321                return ("Fecha de Ingreso: ", v1,
 0322                        "Fecha Ult. Asc. : ", recibo.FechaUltimoAscenso.Value.ToString(fmt));
 0323            return ("Fecha de Ingreso: ", v1, null, null);
 324        }
 325
 3326        if (recibo.FechaInicio != default)
 0327            return ("Fecha Ingreso: ", recibo.FechaInicio.ToString(fmt), null, null);
 328
 3329        return (null, null, null, null);
 330    }
 331}