< Summary

Information
Class: FAU.Logica.Services.ReporteMDNService
Assembly: FAU.Logica
File(s): /home/runner/work/Sistema.Liquidacion.BE/Sistema.Liquidacion.BE/FAU.Logica/Services/ReporteMDNService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 289
Coverable lines: 289
Total lines: 416
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 106
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

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

#LineLine coverage
 1using System.Globalization;
 2using System.Text;
 3using FAU.DataAccess;
 4using FAU.Entidades;
 5using FAU.Logica.DTOs;
 6using Microsoft.EntityFrameworkCore;
 7
 8namespace FAU.Logica.Services;
 9
 10public class ReporteMDNService : IReporteMDNService
 11{
 12    private readonly ApplicationDbContext _context;
 13
 014    public ReporteMDNService(ApplicationDbContext context)
 15    {
 016        _context = context;
 017    }
 18
 19    // ─── Helpers ─────────────────────────────────────────────────────────────
 20
 21    private async Task<int> ObtenerVersionMaximaAsync(long periodoId)
 22    {
 023        return await _context.LiquidacionItems
 024            .AsNoTracking()
 025            .Where(i => i.PeriodoId == periodoId)
 026            .MaxAsync(i => (int?)i.Version) ?? 0;
 027    }
 28
 29    private async Task<Dictionary<string, (string Tipo, bool EsGravado)>> CargarCatalogoAsync()
 30    {
 031        return await _context.CatalogoItems
 032            .AsNoTracking()
 033            .ToDictionaryAsync(ci => ci.Codigo.ToString(), ci => (ci.Tipo, ci.EsGravado));
 034    }
 35
 036    private static string ClasificarMdn((string Tipo, bool EsGravado) entry) => entry.Tipo switch
 037    {
 038        "haber_normal" => entry.EsGravado ? "HabNorGrab" : "HabsNoGrab",
 039        "haber_no_gravado" => "HabsNoGrab",
 040        "beneficio_social_grav" => "BenSocGrab",
 041        "beneficio_social_no_grav" => "BenSocNoGr",
 042        "descuento_legal" => entry.EsGravado ? "DtoLegGrab" : "DtoLegNoGr",
 043        "descuento_personal" => "DtoPerGrab",
 044        "ficto_descuento" => "DtoPerGrab",
 045        _ => ""
 046    };
 47
 048    private static string F(decimal value) => value.ToString("F2", CultureInfo.InvariantCulture);
 49
 50    // ─── RPT-MDN-1: Padrón del Personal ───────────────────────────────────────
 51
 52    public async Task<List<PadronPersonalMdnDto>> ObtenerPadronPersonalAsync(long periodoId, long? regimenId = null)
 53    {
 054        var version = await ObtenerVersionMaximaAsync(periodoId);
 55
 056        var cedulasConLiquidacion = await _context.LiquidacionItems
 057            .AsNoTracking()
 058            .Where(i => i.PeriodoId == periodoId && i.Version == version)
 059            .Select(i => i.Cedula)
 060            .Distinct()
 061            .ToListAsync();
 62
 063        var query = _context.RelacionesLaborales
 064            .AsNoTracking()
 065            .Where(r => cedulasConLiquidacion.Contains(r.Persona.Cedula))
 066            .Where(r => r.Estado == EstadoRelacion.Activo)
 067            .Select(r => new
 068            {
 069                r.Persona.Cedula,
 070                r.Persona.PrimerApellido,
 071                r.Persona.SegundoApellido,
 072                r.Persona.PrimerNombre,
 073                r.Persona.SegundoNombre,
 074                Situacion = r.Situacion.Denominacion,
 075                Grado = r.Grado.Denominacion,
 076                Escalafon = r.Escalafon.Denominacion,
 077                Regimen = r.Regimen.Denominacion,
 078                r.AniosServicioAnterior,
 079                r.FechaInicio,
 080                r.FechaFin,
 081                r.FechaAscensoOficial,
 082                r.FechaUltimoAscenso
 083            });
 84
 085        if (regimenId.HasValue)
 086            query = query.Where(r => r.Regimen == _context.Regimenes.AsNoTracking().First(rg => rg.Id == regimenId.Value
 87
 088        var raw = await query
 089            .OrderBy(r => r.PrimerApellido)
 090            .ThenBy(r => r.SegundoApellido)
 091            .ThenBy(r => r.PrimerNombre)
 092            .ToListAsync();
 93
 094        return raw.Select(r => new PadronPersonalMdnDto
 095        {
 096            Cedula = r.Cedula,
 097            Apellidos = $"{r.PrimerApellido} {r.SegundoApellido}".Trim(),
 098            Nombres = $"{r.PrimerNombre} {r.SegundoNombre}".Trim(),
 099            Situacion = r.Situacion,
 0100            Grado = r.Grado,
 0101            Escalafon = r.Escalafon,
 0102            Regimen = r.Regimen,
 0103            AniosTropa = r.AniosServicioAnterior,
 0104            FechaIngreso = r.FechaInicio,
 0105            FechaRetiro = r.FechaFin,
 0106            FechaOficialidad = r.FechaAscensoOficial,
 0107            FechaUltimoAscenso = r.FechaUltimoAscenso
 0108        }).ToList();
 0109    }
 110
 111    public async Task<byte[]> GenerarCsvPadronPersonalAsync(long periodoId, long? regimenId = null)
 112    {
 0113        var data = await ObtenerPadronPersonalAsync(periodoId, regimenId);
 0114        var sb = new StringBuilder();
 0115        sb.AppendLine("Cédula;Apellidos;Nombres;Situación;Grado;Escalafón;Régimen;Años Tropa;Fecha Ingreso;Fecha Retiro;
 116
 0117        foreach (var f in data)
 118        {
 0119            sb.AppendLine(
 0120                $"{f.Cedula};{f.Apellidos};{f.Nombres};{f.Situacion};{f.Grado};{f.Escalafon};{f.Regimen};" +
 0121                $"{f.AniosTropa?.ToString() ?? ""};" +
 0122                $"{f.FechaIngreso:dd/MM/yyyy};{f.FechaRetiro:dd/MM/yyyy};{f.FechaOficialidad:dd/MM/yyyy};{f.FechaUltimoA
 123        }
 124
 0125        return Encoding.UTF8.GetBytes(sb.ToString());
 0126    }
 127
 128    // ─── RPT-MDN-2: Liquidación por Rubros ────────────────────────────────────
 129
 130    public async Task<LiquidacionRubrosMdnDto> ObtenerLiquidacionPorRubrosAsync(long periodoId, long? regimenId = null)
 131    {
 0132        var version = await ObtenerVersionMaximaAsync(periodoId);
 133
 0134        var itemsQuery = _context.LiquidacionItems
 0135            .AsNoTracking()
 0136            .Where(i => i.PeriodoId == periodoId && i.Version == version);
 137
 0138        var items = await itemsQuery
 0139            .Select(i => new
 0140            {
 0141                i.Cedula,
 0142                i.CodigoConcepto,
 0143                i.NombreConcepto,
 0144                i.Importe,
 0145                i.RubroContable,
 0146                i.ObjetoGasto,
 0147                i.RegimenId,
 0148                i.ProgramaId
 0149            })
 0150            .ToListAsync();
 151
 0152        var catalogoDict = await CargarCatalogoAsync();
 0153        items = items.Where(i => ClasificarMdn(catalogoDict.GetValueOrDefault(i.CodigoConcepto)) != "DtoPerGrab").ToList
 154
 0155        var cedulas = items.Select(i => i.Cedula).Distinct().ToList();
 156
 0157        var personasDict = await _context.RelacionesLaborales
 0158            .AsNoTracking()
 0159            .Where(r => cedulas.Contains(r.Persona.Cedula) && r.Estado == EstadoRelacion.Activo)
 0160            .Select(r => new
 0161            {
 0162                r.Persona.Cedula,
 0163                Nombre = $"{r.Persona.PrimerApellido} {r.Persona.SegundoApellido}, {r.Persona.PrimerNombre}",
 0164                Grado = r.Grado.Denominacion,
 0165                Regimen = r.Regimen.Denominacion,
 0166                Programa = r.Programa.Denominacion
 0167            })
 0168            .ToDictionaryAsync(x => x.Cedula);
 169
 0170        var objetosGasto = items
 0171            .Where(i => !string.IsNullOrEmpty(i.ObjetoGasto))
 0172            .Select(i => i.ObjetoGasto!)
 0173            .Distinct()
 0174            .OrderBy(o => o)
 0175            .ToList();
 176
 0177        var filas = items
 0178            .GroupBy(i => i.Cedula)
 0179            .Select(g =>
 0180            {
 0181                var persona = personasDict.GetValueOrDefault(g.Key);
 0182                var montosPorOG = g
 0183                    .Where(i => !string.IsNullOrEmpty(i.ObjetoGasto))
 0184                    .GroupBy(i => i.ObjetoGasto!)
 0185                    .ToDictionary(og => og.Key, og => og.Sum(i => i.Importe));
 0186
 0187                var haberes = g.Where(i => i.Importe > 0).Sum(i => i.Importe);
 0188                var descuentos = g.Where(i => i.Importe < 0).Sum(i => i.Importe);
 0189
 0190                return new LiquidacionRubrosMdnFilaDto
 0191                {
 0192                    Cedula = g.Key,
 0193                    Nombre = persona?.Nombre ?? g.Key,
 0194                    Grado = persona?.Grado ?? "",
 0195                    Regimen = persona?.Regimen ?? "",
 0196                    Programa = persona?.Programa ?? "",
 0197                    MontosPorObjetoGasto = montosPorOG,
 0198                    TotalHaberes = haberes,
 0199                    TotalDescuentosLegales = Math.Abs(descuentos),
 0200                    LiquidoMDN = haberes + descuentos
 0201                };
 0202            })
 0203            .ToList();
 204
 0205        return new LiquidacionRubrosMdnDto
 0206        {
 0207            ObjetosGasto = objetosGasto,
 0208            Filas = filas,
 0209            TotalHaberes = filas.Sum(f => f.TotalHaberes),
 0210            TotalDescuentosLegales = filas.Sum(f => f.TotalDescuentosLegales),
 0211            LiquidoMDN = filas.Sum(f => f.LiquidoMDN)
 0212        };
 0213    }
 214
 215    public async Task<byte[]> GenerarCsvLiquidacionPorRubrosAsync(long periodoId, long? regimenId = null)
 216    {
 0217        var dto = await ObtenerLiquidacionPorRubrosAsync(periodoId, regimenId);
 0218        var sb = new StringBuilder();
 219
 0220        var headers = "Cédula;Nombre;Grado;Régimen;Programa;" +
 0221            string.Join(";", dto.ObjetosGasto) +
 0222            ";Total Haberes;Total Descuentos Legales;Líquido MDN";
 0223        sb.AppendLine(headers);
 224
 0225        foreach (var f in dto.Filas)
 226        {
 0227            var ogValues = string.Join(";",
 0228                dto.ObjetosGasto.Select(og => f.MontosPorObjetoGasto.TryGetValue(og, out var m) ? F(m) : "0.00"));
 229
 0230            sb.AppendLine(
 0231                $"{f.Cedula};{f.Nombre};{f.Grado};{f.Regimen};{f.Programa};" +
 0232                $"{ogValues};{F(f.TotalHaberes)};{F(f.TotalDescuentosLegales)};{F(f.LiquidoMDN)}");
 233        }
 234
 0235        sb.AppendLine(
 0236            $"TOTAL;;;;;" +
 0237            $"{string.Join(";", dto.ObjetosGasto.Select(_ => ""))};" +
 0238            $"{F(dto.TotalHaberes)};{F(dto.TotalDescuentosLegales)};{F(dto.LiquidoMDN)}");
 239
 0240        return Encoding.UTF8.GetBytes(sb.ToString());
 0241    }
 242
 243    // ─── RPT-MDN-3: Control Porcentual ────────────────────────────────────────
 244
 245    public async Task<ControlPorcentualMdnDto> ObtenerControlPorcentualAsync(long periodoId, long? regimenId = null)
 246    {
 0247        var version = await ObtenerVersionMaximaAsync(periodoId);
 248
 0249        var itemsQuery = _context.LiquidacionItems
 0250            .AsNoTracking()
 0251            .Where(i => i.PeriodoId == periodoId && i.Version == version);
 252
 0253        var items = await itemsQuery.ToListAsync();
 0254        var catalogoDict = await CargarCatalogoAsync();
 255        items = items.Where(i => ClasificarMdn(catalogoDict.GetValueOrDefault(i.CodigoConcepto)) != "DtoPerGrab").ToList
 256
 0257        var cedulas = items.Select(i => i.Cedula).Distinct().ToList();
 258
 0259        var personasDict = await _context.RelacionesLaborales
 0260            .AsNoTracking()
 0261            .Where(r => cedulas.Contains(r.Persona.Cedula) && r.Estado == EstadoRelacion.Activo)
 0262            .Select(r => new
 0263            {
 0264                r.Persona.Cedula,
 0265                Nombre = $"{r.Persona.PrimerApellido} {r.Persona.SegundoApellido}, {r.Persona.PrimerNombre}",
 0266                Grado = r.Grado.Denominacion,
 0267                Unidad = r.Unidad.Denominacion
 0268            })
 0269            .ToDictionaryAsync(x => x.Cedula);
 270
 0271        var filas = new List<ControlPorcentualMdnFilaDto>();
 272
 0273        foreach (var g in items.GroupBy(i => i.Cedula))
 274        {
 0275            var persona = personasDict.GetValueOrDefault(g.Key);
 0276            var haberesGrav = g.Where(i => ClasificarMdn(catalogoDict.GetValueOrDefault(i.CodigoConcepto)) is "HabNorGra
 0277            var haberesNoGrav = g.Where(i => ClasificarMdn(catalogoDict.GetValueOrDefault(i.CodigoConcepto)) is "HabsNoG
 0278            var beneficios = g.Where(i => ClasificarMdn(catalogoDict.GetValueOrDefault(i.CodigoConcepto)) is "BenSocGrab
 0279            var descuentos = g.Where(i => i.Importe < 0).Sum(i => i.Importe);
 0280            var liquido = g.Sum(i => i.Importe);
 281            // Legacy: liquido_30 = (((tot_hab + tot_habng) - tot_desl) * 30 / 100) + tot_ben
 0282            var ingresosNetos = haberesGrav + haberesNoGrav + descuentos; // descuentos es negativo
 0283            var calculo30 = ingresosNetos * 0.30m + beneficios;
 0284            var porcentajeReal = calculo30 > 0 ? (liquido * 30m) / calculo30 : 0m;
 285
 0286            filas.Add(new ControlPorcentualMdnFilaDto
 0287            {
 0288                Cedula = g.Key,
 0289                Nombre = persona?.Nombre ?? g.Key,
 0290                Grado = persona?.Grado ?? "",
 0291                Unidad = persona?.Unidad ?? "",
 0292                HaberesGravados = haberesGrav,
 0293                HaberesNoGravados = haberesNoGrav,
 0294                BeneficiosSociales = beneficios,
 0295                DescuentosLegales = Math.Abs(descuentos),
 0296                LiquidoMDN = liquido,
 0297                Calculo30Porciento = calculo30,
 0298                PorcentajeReal = porcentajeReal
 0299            });
 300        }
 301
 0302        var resumen = new ControlPorcentualMdnResumenDto();
 303
 0304        foreach (var f in filas)
 305        {
 0306            var porc = f.PorcentajeReal;
 0307            if (porc > 50m)
 308            {
 0309                resumen.CantidadSuperior50++;
 0310                resumen.ImporteSuperior50 += f.LiquidoMDN;
 311            }
 0312            else if (porc >= 40m)
 313            {
 0314                resumen.CantidadEntre40Y50++;
 0315                resumen.ImporteEntre40Y50 += f.LiquidoMDN;
 316            }
 0317            else if (porc >= 30m)
 318            {
 0319                resumen.CantidadEntre30Y40++;
 0320                resumen.ImporteEntre30Y40 += f.LiquidoMDN;
 321            }
 322            else
 323            {
 0324                resumen.CantidadInferior30++;
 0325                resumen.ImporteInferior30 += f.LiquidoMDN;
 0326                resumen.TotalDeudasNoAtendidas += (f.Calculo30Porciento - f.LiquidoMDN);
 327            }
 328        }
 329
 0330        return new ControlPorcentualMdnDto { Filas = filas, Resumen = resumen };
 0331    }
 332
 333    public async Task<byte[]> GenerarCsvControlPorcentualAsync(long periodoId, long? regimenId = null)
 334    {
 0335        var dto = await ObtenerControlPorcentualAsync(periodoId, regimenId);
 0336        var sb = new StringBuilder();
 337
 0338        sb.AppendLine("Cédula;Nombre;Grado;Unidad;Haberes Gravados;Haberes No Gravados;Beneficios Sociales;Descuentos Le
 0339        foreach (var f in dto.Filas)
 0340            sb.AppendLine($"{f.Cedula};{f.Nombre};{f.Grado};{f.Unidad};{F(f.HaberesGravados)};{F(f.HaberesNoGravados)};{
 341
 0342        sb.AppendLine();
 0343        sb.AppendLine("RANGO;CANTIDAD;IMPORTE NOMINAL");
 0344        sb.AppendLine($"> 50%;{dto.Resumen.CantidadSuperior50};{F(dto.Resumen.ImporteSuperior50)}");
 0345        sb.AppendLine($"40% - 50%;{dto.Resumen.CantidadEntre40Y50};{F(dto.Resumen.ImporteEntre40Y50)}");
 0346        sb.AppendLine($"30% - 40%;{dto.Resumen.CantidadEntre30Y40};{F(dto.Resumen.ImporteEntre30Y40)}");
 0347        sb.AppendLine($"< 30% (insuficiente);{dto.Resumen.CantidadInferior30};{F(dto.Resumen.ImporteInferior30)}");
 0348        sb.AppendLine($"Total deudas no atendidas;;;{F(dto.Resumen.TotalDeudasNoAtendidas)}");
 349
 0350        return Encoding.UTF8.GetBytes(sb.ToString());
 0351    }
 352
 353    // ─── RPT-MDN-4: Haberes Gravables / No Gravables ──────────────────────────
 354
 355    public async Task<List<HaberesGravablesMdnFilaDto>> ObtenerHaberesGravablesAsync(long periodoId, long? regimenId = n
 356    {
 0357        var version = await ObtenerVersionMaximaAsync(periodoId);
 358
 0359        var itemsQuery = _context.LiquidacionItems
 0360            .AsNoTracking()
 0361            .Where(i => i.PeriodoId == periodoId && i.Version == version);
 362
 0363        var items = await itemsQuery.ToListAsync();
 0364        var catalogoDict = await CargarCatalogoAsync();
 365        items = items.Where(i => ClasificarMdn(catalogoDict.GetValueOrDefault(i.CodigoConcepto)) != "DtoPerGrab").ToList
 366
 0367        var cedulas = items.Select(i => i.Cedula).Distinct().ToList();
 368
 0369        var personasDict = await _context.RelacionesLaborales
 0370            .AsNoTracking()
 0371            .Where(r => cedulas.Contains(r.Persona.Cedula))
 0372            .Select(r => new
 0373            {
 0374                r.Persona.Cedula,
 0375                Nombre = $"{r.Persona.PrimerApellido} {r.Persona.SegundoApellido}, {r.Persona.PrimerNombre}",
 0376                r.FechaInicio,
 0377                Situacion = r.Situacion.Denominacion,
 0378                Grado = r.Grado.Denominacion,
 0379                Escalafon = r.Escalafon.Denominacion
 0380            })
 0381            .ToDictionaryAsync(x => x.Cedula);
 382
 0383        return items
 0384            .GroupBy(i => i.Cedula)
 0385            .Select(g =>
 0386            {
 387                var p = personasDict.GetValueOrDefault(g.Key);
 388                return new HaberesGravablesMdnFilaDto
 389                {
 390                    Cedula = g.Key,
 391                    Nombre = p?.Nombre ?? g.Key,
 392                    FechaIngreso = p?.FechaInicio,
 393                    Situacion = p?.Situacion ?? "",
 394                    Grado = p?.Grado ?? "",
 395                    Escalafon = p?.Escalafon ?? "",
 0396                    HaberesGravados = g.Where(i => ClasificarMdn(catalogoDict.GetValueOrDefault(i.CodigoConcepto)) is "H
 0397                    HaberesNoGravados = g.Where(i => ClasificarMdn(catalogoDict.GetValueOrDefault(i.CodigoConcepto)) is 
 0398                    BeneficiosGravados = g.Where(i => ClasificarMdn(catalogoDict.GetValueOrDefault(i.CodigoConcepto)) ==
 399                };
 0400            })
 0401            .OrderBy(f => f.Nombre)
 0402            .ToList();
 0403    }
 404
 405    public async Task<byte[]> GenerarCsvHaberesGravablesAsync(long periodoId, long? regimenId = null)
 406    {
 0407        var data = await ObtenerHaberesGravablesAsync(periodoId, regimenId);
 0408        var sb = new StringBuilder();
 0409        sb.AppendLine("Cédula;Nombre;Fecha Ingreso;Situación;Grado;Escalafón;Haberes Gravados;Haberes No Gravados;Benefi
 410
 0411        foreach (var f in data)
 0412            sb.AppendLine($"{f.Cedula};{f.Nombre};{f.FechaIngreso:dd/MM/yyyy};{f.Situacion};{f.Grado};{f.Escalafon};{F(f
 413
 0414        return Encoding.UTF8.GetBytes(sb.ToString());
 0415    }
 416}