| | | 1 | | using FAU.DataAccess; |
| | | 2 | | using FAU.Entidades; |
| | | 3 | | using FAU.Logica.DTOs; |
| | | 4 | | using Microsoft.EntityFrameworkCore; |
| | | 5 | | |
| | | 6 | | namespace FAU.Logica.Services; |
| | | 7 | | |
| | | 8 | | public class InsumosPeriodoLoader : IInsumosPeriodoLoader |
| | | 9 | | { |
| | | 10 | | private readonly ApplicationDbContext _context; |
| | | 11 | | |
| | 16 | 12 | | public InsumosPeriodoLoader(ApplicationDbContext context) |
| | | 13 | | { |
| | 16 | 14 | | _context = context; |
| | 16 | 15 | | } |
| | | 16 | | |
| | | 17 | | public async Task<InsumosPeriodo> CargarAsync(long periodoId) |
| | | 18 | | { |
| | 16 | 19 | | var periodo = await _context.PeriodosLiquidacion |
| | 16 | 20 | | .AsNoTracking() |
| | 16 | 21 | | .FirstOrDefaultAsync(p => p.Id == periodoId && p.Activo); |
| | | 22 | | |
| | 16 | 23 | | if (periodo is null) |
| | | 24 | | { |
| | 0 | 25 | | throw new InvalidOperationException($"Periodo de liquidación {periodoId} no encontrado o no está activo."); |
| | | 26 | | } |
| | | 27 | | |
| | 16 | 28 | | var periodoDate = new DateTime(periodo.Anio, periodo.Mes, 1); |
| | 16 | 29 | | var ultimoDiaPeriodo = new DateTime(periodo.Anio, periodo.Mes, DateTime.DaysInMonth(periodo.Anio, periodo.Mes)); |
| | | 30 | | |
| | 16 | 31 | | var parametros = await _context.ParametrosPeriodo |
| | 16 | 32 | | .AsNoTracking() |
| | 16 | 33 | | .FirstOrDefaultAsync(pp => pp.Anio == periodo.Anio && pp.Mes == periodo.Mes); |
| | | 34 | | |
| | 16 | 35 | | if (parametros is null) |
| | | 36 | | { |
| | 0 | 37 | | throw new InvalidOperationException($"Parámetros de período para {periodo.Anio}/{periodo.Mes} no encontrados |
| | | 38 | | } |
| | | 39 | | |
| | 16 | 40 | | var catalogoItems = await _context.CatalogoItems |
| | 16 | 41 | | .AsNoTracking() |
| | 16 | 42 | | .ToListAsync(); |
| | | 43 | | |
| | 16 | 44 | | var remuneraciones = await _context.RemuneracionesGrado |
| | 16 | 45 | | .AsNoTracking() |
| | 16 | 46 | | .Include(r => r.Item) |
| | 16 | 47 | | .Where(r => r.VigenteDesde <= periodoDate && (r.VigenteHasta == null || r.VigenteHasta >= periodoDate)) |
| | 16 | 48 | | .ToListAsync(); |
| | | 49 | | |
| | 16 | 50 | | var permanencias = await _context.Set<TablaPermanencia>() |
| | 16 | 51 | | .AsNoTracking() |
| | 16 | 52 | | .Where(t => t.VigenteDesde <= periodoDate && (t.VigenteHasta == null || t.VigenteHasta >= periodoDate)) |
| | 16 | 53 | | .ToListAsync(); |
| | | 54 | | |
| | 16 | 55 | | var franjasIrpfRaw = await _context.FranjasIrpf |
| | 16 | 56 | | .AsNoTracking() |
| | 16 | 57 | | .Where(f => f.VigenteDesde <= periodoDate && (f.VigenteHasta == null || f.VigenteHasta >= periodoDate)) |
| | 16 | 58 | | .ToListAsync(); |
| | | 59 | | |
| | | 60 | | // Deduplicar: una franja por NumeroFranja, la más reciente según VigenteDesde |
| | 16 | 61 | | var franjasIrpf = franjasIrpfRaw |
| | 32 | 62 | | .GroupBy(f => f.NumeroFranja) |
| | 64 | 63 | | .Select(g => g.OrderByDescending(f => f.VigenteDesde).First()) |
| | 32 | 64 | | .OrderBy(f => f.NumeroFranja) |
| | 16 | 65 | | .ToList(); |
| | | 66 | | |
| | | 67 | | // Tramos de Sanidad Militar vigentes (Art. 94 Ley 18.834) |
| | 16 | 68 | | var tramosSanidadRaw = await _context.TramosSanidadMilitar |
| | 16 | 69 | | .AsNoTracking() |
| | 16 | 70 | | .Where(t => t.VigenteDesde <= periodoDate && (t.VigenteHasta == null || t.VigenteHasta >= periodoDate)) |
| | 16 | 71 | | .ToListAsync(); |
| | | 72 | | |
| | | 73 | | // Deduplicar por código de retención, el más reciente según VigenteDesde; ordenar por Desde |
| | 16 | 74 | | var tramosSanidad = tramosSanidadRaw |
| | 0 | 75 | | .GroupBy(t => t.CodigoRetencion) |
| | 0 | 76 | | .Select(g => g.OrderByDescending(t => t.VigenteDesde).First()) |
| | 0 | 77 | | .OrderBy(t => t.Desde) |
| | 16 | 78 | | .ToList(); |
| | | 79 | | |
| | 16 | 80 | | var beneficios = await _context.BeneficiosSociales |
| | 16 | 81 | | .AsNoTracking() |
| | 16 | 82 | | .Where(b => b.Estado == "activo" |
| | 16 | 83 | | && b.VigenteDesde <= ultimoDiaPeriodo // inició antes del fin del período |
| | 16 | 84 | | && (b.VigenteHasta == null || b.VigenteHasta >= periodoDate)) // no terminó antes del inicio |
| | 16 | 85 | | .ToListAsync(); |
| | | 86 | | |
| | 16 | 87 | | var tiposBeneficio = await _context.TiposBeneficio |
| | 16 | 88 | | .AsNoTracking() |
| | 16 | 89 | | .ToDictionaryAsync(t => t.Id); |
| | | 90 | | |
| | 34 | 91 | | foreach (var b in beneficios) |
| | | 92 | | { |
| | 1 | 93 | | if (tiposBeneficio.TryGetValue(b.TipoBeneficioId, out var tipo)) |
| | 0 | 94 | | b.TipoBeneficio = tipo; |
| | | 95 | | } |
| | | 96 | | |
| | | 97 | | // Cargar hijos registrados para beneficios de tipo Asignación Familiar |
| | 16 | 98 | | var idsAsigFamiliar = beneficios |
| | 1 | 99 | | .Where(b => b.TipoBeneficio is not null && |
| | 1 | 100 | | (b.TipoBeneficio.Codigo == "ASIGNACION_FAMILIAR" || |
| | 1 | 101 | | b.TipoBeneficio.Codigo.StartsWith("300") || |
| | 1 | 102 | | b.TipoBeneficio.Nombre.ToUpperInvariant().Contains("ASIGNACION FAMILIAR") || |
| | 1 | 103 | | b.TipoBeneficio.Nombre.ToUpperInvariant().Contains("ASIGNACIÓN FAMILIAR"))) |
| | 0 | 104 | | .Select(b => b.Id) |
| | 16 | 105 | | .ToHashSet(); |
| | | 106 | | |
| | 16 | 107 | | var beneficiariosAsigFam = idsAsigFamiliar.Count > 0 |
| | 16 | 108 | | ? await _context.BeneficioSocialBeneficiarios |
| | 16 | 109 | | .AsNoTracking() |
| | 16 | 110 | | .Where(bh => idsAsigFamiliar.Contains(bh.BeneficioId) && |
| | 16 | 111 | | bh.Activo && |
| | 16 | 112 | | bh.VigenteDesde <= ultimoDiaPeriodo && |
| | 16 | 113 | | (bh.VigenteHasta == null || bh.VigenteHasta >= periodoDate)) |
| | 16 | 114 | | .ToListAsync() |
| | 16 | 115 | | : []; |
| | | 116 | | |
| | 16 | 117 | | var beneficiariosPorBeneficio = beneficiariosAsigFam |
| | 0 | 118 | | .GroupBy(bh => bh.BeneficioId) |
| | 16 | 119 | | .ToDictionary(g => g.Key, g => (IReadOnlyList<BeneficioSocialBeneficiario>)g.ToList()); |
| | | 120 | | |
| | 16 | 121 | | var compensaciones = await _context.ItemsLoteCompensacion |
| | 16 | 122 | | .AsNoTracking() |
| | 16 | 123 | | .Include(i => i.LoteCompensacion) |
| | 16 | 124 | | .ThenInclude(l => l.TipoCompensacion) |
| | 16 | 125 | | .ThenInclude(t => t.Configuraciones) |
| | 16 | 126 | | .ThenInclude(c => c.CatalogoItem) |
| | 16 | 127 | | .Where(i => i.LoteCompensacion.Estado == "aprobado" |
| | 16 | 128 | | && i.LoteCompensacion.Periodo.Year == periodo.Anio |
| | 16 | 129 | | && i.LoteCompensacion.Periodo.Month == periodo.Mes) |
| | 16 | 130 | | .ToListAsync(); |
| | | 131 | | |
| | 16 | 132 | | var novedadesConfirmadas = await _context.NovedadesPeriodo |
| | 16 | 133 | | .AsNoTracking() |
| | 16 | 134 | | .Where(n => n.PeriodoId == periodoId && n.Estado == "confirmada") |
| | 16 | 135 | | .ToListAsync(); |
| | | 136 | | |
| | 16 | 137 | | var remuneracionesPorGrado = remuneraciones |
| | 37 | 138 | | .GroupBy(r => r.GradoId) |
| | 16 | 139 | | .ToDictionary( |
| | 17 | 140 | | g => g.Key, |
| | 33 | 141 | | g => (IReadOnlyDictionary<int, RemuneracionGrado>)g |
| | 37 | 142 | | .GroupBy(r => r.Item.Codigo) |
| | 33 | 143 | | .ToDictionary( |
| | 37 | 144 | | gg => gg.Key, |
| | 107 | 145 | | gg => gg.OrderByDescending(r => r.VigenteDesde).First())); |
| | | 146 | | |
| | 16 | 147 | | var permanenciasPorEscalafon = permanencias |
| | 1 | 148 | | .GroupBy(t => t.EscalafonId) |
| | 19 | 149 | | .ToDictionary(g => g.Key, g => (IReadOnlyList<TablaPermanencia>)g.OrderBy(t => t.AniosDesde).ToList()); |
| | | 150 | | |
| | 16 | 151 | | var beneficiosPorPersona = beneficios |
| | 1 | 152 | | .GroupBy(b => b.PersonaId) |
| | 18 | 153 | | .ToDictionary(g => g.Key, g => (IReadOnlyList<BeneficioSocial>)g.ToList()); |
| | | 154 | | |
| | 16 | 155 | | var compensacionesPorPersona = compensaciones |
| | 3 | 156 | | .GroupBy(i => i.PersonaId) |
| | 22 | 157 | | .ToDictionary(g => g.Key, g => (IReadOnlyList<ItemLoteCompensacion>)g.ToList()); |
| | | 158 | | |
| | 16 | 159 | | var novedadesPorPersona = novedadesConfirmadas |
| | 4 | 160 | | .GroupBy(n => n.PersonaId) |
| | 24 | 161 | | .ToDictionary(g => g.Key, g => (IReadOnlyList<NovedadPeriodo>)g.ToList()); |
| | | 162 | | |
| | | 163 | | // Instructivo Asignación Familiar: fila más reciente por tipo_franja |
| | 16 | 164 | | var instructivoAsigFamiliarRaw = await _context.InstructivosAsigFamiliar |
| | 16 | 165 | | .AsNoTracking() |
| | 16 | 166 | | .Where(i => i.VigenteDesde <= periodoDate) |
| | 16 | 167 | | .ToListAsync(); |
| | | 168 | | |
| | 16 | 169 | | var instructivoAsigFamiliar = instructivoAsigFamiliarRaw |
| | 0 | 170 | | .GroupBy(i => i.TipoFranja) |
| | 16 | 171 | | .ToDictionary( |
| | 0 | 172 | | g => g.Key, |
| | 16 | 173 | | g => g.OrderByDescending(i => i.VigenteDesde).First()); |
| | | 174 | | |
| | | 175 | | // Instructivo PSF: fila más reciente por numero_categoria |
| | 16 | 176 | | var instructivoPsfRaw = await _context.InstructivosPsf |
| | 16 | 177 | | .AsNoTracking() |
| | 16 | 178 | | .Where(i => i.VigenteDesde <= periodoDate) |
| | 16 | 179 | | .ToListAsync(); |
| | | 180 | | |
| | 16 | 181 | | var instructivoPsf = instructivoPsfRaw |
| | 0 | 182 | | .GroupBy(i => (int)i.NumeroCategoria) |
| | 16 | 183 | | .ToDictionary( |
| | 0 | 184 | | g => g.Key, |
| | 16 | 185 | | g => g.OrderByDescending(i => i.VigenteDesde).First()); |
| | | 186 | | |
| | | 187 | | // Tabla Hogar: tramos vigentes, ordenados ASC con NULL al final |
| | 16 | 188 | | var tablaPorHogarRaw = await _context.TablasHogarConstituido |
| | 16 | 189 | | .AsNoTracking() |
| | 16 | 190 | | .Where(t => t.VigenteDesde <= periodoDate) |
| | 16 | 191 | | .ToListAsync(); |
| | | 192 | | |
| | 16 | 193 | | var tablaPorHogar = tablaPorHogarRaw |
| | 0 | 194 | | .GroupBy(t => t.HastaHaberes) |
| | 0 | 195 | | .Select(g => g.OrderByDescending(t => t.VigenteDesde).First()) |
| | 0 | 196 | | .OrderBy(t => t.HastaHaberes.HasValue ? 0 : 1) |
| | 0 | 197 | | .ThenBy(t => t.HastaHaberes ?? decimal.MaxValue) |
| | 16 | 198 | | .ToList(); |
| | | 199 | | |
| | 16 | 200 | | var aguinaldos = await _context.Set<Aguinaldo>() |
| | 16 | 201 | | .AsNoTracking() |
| | 16 | 202 | | .Where(a => a.Anio == periodo.Anio) |
| | 16 | 203 | | .ToListAsync(); |
| | | 204 | | |
| | 16 | 205 | | var aguinaldosPorPersona = aguinaldos |
| | 0 | 206 | | .GroupBy(a => a.PersonaId) |
| | 16 | 207 | | .ToDictionary(g => g.Key, g => (IReadOnlyList<Aguinaldo>)g.ToList()); |
| | | 208 | | |
| | 16 | 209 | | var ordenPorGrado = await _context.Grados |
| | 16 | 210 | | .AsNoTracking() |
| | 16 | 211 | | .Where(g => g.Vigente) |
| | 48 | 212 | | .ToDictionaryAsync(g => g.Id, g => g.Orden); |
| | | 213 | | |
| | 16 | 214 | | var diferenciaAscenso = await _context.CompensacionesDiferenciaAscenso |
| | 16 | 215 | | .AsNoTracking() |
| | 16 | 216 | | .Where(c => c.PeriodoId == periodoId && c.Estado == "pendiente") |
| | 17 | 217 | | .ToDictionaryAsync(c => c.PersonaId); |
| | | 218 | | |
| | 16 | 219 | | var descuentosPersonales = await _context.DescuentosPersonalesPeriodo |
| | 16 | 220 | | .AsNoTracking() |
| | 16 | 221 | | .Where(d => d.PeriodoId == periodoId && d.Estado == "aprobado") |
| | 16 | 222 | | .ToListAsync(); |
| | | 223 | | |
| | 16 | 224 | | var descuentosPersonalesPorPersona = descuentosPersonales |
| | 0 | 225 | | .GroupBy(d => d.PersonaId) |
| | 16 | 226 | | .ToDictionary(g => g.Key, g => (IReadOnlyList<DescuentoPersonalPeriodo>)g.ToList()); |
| | | 227 | | |
| | 16 | 228 | | return new InsumosPeriodo |
| | 16 | 229 | | { |
| | 16 | 230 | | PeriodoId = periodoId, |
| | 16 | 231 | | ParametroPeriodo = parametros, |
| | 16 | 232 | | CatalogoItems = catalogoItems, |
| | 16 | 233 | | RemuneracionesPorGrado = remuneracionesPorGrado, |
| | 16 | 234 | | PermanenciasPorEscalafon = permanenciasPorEscalafon, |
| | 16 | 235 | | FranjasIrpf = franjasIrpf, |
| | 16 | 236 | | TramosSanidad = tramosSanidad, |
| | 16 | 237 | | BeneficiosPorPersona = beneficiosPorPersona, |
| | 16 | 238 | | CompensacionesPorPersona = compensacionesPorPersona, |
| | 16 | 239 | | NovedadesPorPersona = novedadesPorPersona, |
| | 16 | 240 | | BeneficiariosPorBeneficio = beneficiariosPorBeneficio, |
| | 16 | 241 | | InstructivoAsigFamiliar = instructivoAsigFamiliar, |
| | 16 | 242 | | InstructivoPsf = instructivoPsf, |
| | 16 | 243 | | TablaPorHogar = tablaPorHogar, |
| | 16 | 244 | | AguinaldosPorPersona = aguinaldosPorPersona, |
| | 16 | 245 | | OrdenPorGrado = ordenPorGrado, |
| | 16 | 246 | | CompensacionesDiferenciaAscensoPorPersona = diferenciaAscenso, |
| | 16 | 247 | | DescuentosPersonalesPorPersona = descuentosPersonalesPorPersona |
| | 16 | 248 | | }; |
| | 16 | 249 | | } |
| | | 250 | | } |