| | | 1 | | using System.Security.Cryptography; |
| | | 2 | | using System.Text; |
| | | 3 | | using System.Text.Json; |
| | | 4 | | using FAU.DataAccess; |
| | | 5 | | using FAU.DataAccess.Repositories; |
| | | 6 | | using FAU.Entidades; |
| | | 7 | | using Microsoft.EntityFrameworkCore; |
| | | 8 | | |
| | | 9 | | namespace FAU.Logica.Services; |
| | | 10 | | |
| | | 11 | | public class PeriodoService : IPeriodoService |
| | | 12 | | { |
| | | 13 | | private readonly IPeriodosRepository _periodosRepository; |
| | | 14 | | private readonly ISnapshotsRepository _snapshotsRepository; |
| | | 15 | | private readonly ApplicationDbContext _context; |
| | | 16 | | private readonly IAuditoriaService _auditoriaService; |
| | | 17 | | private readonly ICurrentUserContext _currentUser; |
| | | 18 | | |
| | 4 | 19 | | public PeriodoService( |
| | 4 | 20 | | IPeriodosRepository periodosRepository, |
| | 4 | 21 | | ISnapshotsRepository snapshotsRepository, |
| | 4 | 22 | | ApplicationDbContext context, |
| | 4 | 23 | | IAuditoriaService auditoriaService, |
| | 4 | 24 | | ICurrentUserContext currentUser) |
| | | 25 | | { |
| | 4 | 26 | | _periodosRepository = periodosRepository; |
| | 4 | 27 | | _snapshotsRepository = snapshotsRepository; |
| | 4 | 28 | | _context = context; |
| | 4 | 29 | | _auditoriaService = auditoriaService; |
| | 4 | 30 | | _currentUser = currentUser; |
| | 4 | 31 | | } |
| | | 32 | | |
| | | 33 | | public async Task<(bool Exito, Periodo? Periodo, string? Error)> AbrirPeriodoAsync(short anio, short mes) |
| | | 34 | | { |
| | 5 | 35 | | if (anio < 2000 || anio > 2100) |
| | | 36 | | { |
| | 0 | 37 | | return (false, null, "El año debe estar entre 2000 y 2100"); |
| | | 38 | | } |
| | | 39 | | |
| | 5 | 40 | | if (mes < 1 || mes > 12) |
| | | 41 | | { |
| | 0 | 42 | | return (false, null, "El mes debe estar entre 1 y 12"); |
| | | 43 | | } |
| | | 44 | | |
| | 5 | 45 | | var periodoExistente = await _periodosRepository.ObtenerPorAnioMesAsync(anio, mes); |
| | 5 | 46 | | if (periodoExistente is not null && periodoExistente.Estado == "ABIERTA" && periodoExistente.Activo) |
| | | 47 | | { |
| | 1 | 48 | | return (true, periodoExistente, null); |
| | | 49 | | } |
| | | 50 | | |
| | 4 | 51 | | var periodoActivo = await _periodosRepository.ObtenerActivoAsync(); |
| | 4 | 52 | | if (periodoActivo is not null) |
| | | 53 | | { |
| | 0 | 54 | | return (false, null, "Ya existe un período abierto"); |
| | | 55 | | } |
| | | 56 | | |
| | 4 | 57 | | var parametrosPeriodo = await _context.ParametrosPeriodo |
| | 4 | 58 | | .AsNoTracking() |
| | 4 | 59 | | .FirstOrDefaultAsync(p => p.Anio == anio && p.Mes == mes); |
| | | 60 | | |
| | 4 | 61 | | if (parametrosPeriodo is null) |
| | | 62 | | { |
| | 1 | 63 | | return (false, null, "No existen parámetros de liquidación para el período"); |
| | | 64 | | } |
| | | 65 | | |
| | 3 | 66 | | var fechaInicioPeriodo = new DateTime(anio, mes, 1); |
| | 3 | 67 | | var fechaFinPeriodo = fechaInicioPeriodo.AddMonths(1).AddTicks(-1); |
| | | 68 | | |
| | 3 | 69 | | var tieneMovimientosLaborales = await _context.MovimientosLaborales |
| | 3 | 70 | | .AsNoTracking() |
| | 3 | 71 | | .AnyAsync(m => m.FechaMovimiento.Year == anio && m.FechaMovimiento.Month == mes); |
| | | 72 | | |
| | 3 | 73 | | if (tieneMovimientosLaborales) |
| | | 74 | | { |
| | 0 | 75 | | return (false, null, "No se puede abrir el período porque ya existen novedades laborales para ese mes"); |
| | | 76 | | } |
| | | 77 | | |
| | 3 | 78 | | var existeLoteAprobado = await _context.LotesCompensacion |
| | 3 | 79 | | .AsNoTracking() |
| | 3 | 80 | | .AnyAsync(l => l.Periodo.Year == anio && l.Periodo.Month == mes && l.Estado == "aprobado"); |
| | | 81 | | |
| | 3 | 82 | | if (!existeLoteAprobado) |
| | | 83 | | { |
| | 0 | 84 | | return (false, null, "Debe existir al menos un lote de compensación aprobado para el período"); |
| | | 85 | | } |
| | | 86 | | |
| | 3 | 87 | | var existeBeneficio = await _context.BeneficiosSociales |
| | 3 | 88 | | .AsNoTracking() |
| | 3 | 89 | | .AnyAsync(b => b.Estado == "activo" && b.VigenteDesde <= fechaFinPeriodo && (b.VigenteHasta == null || b.Vig |
| | | 90 | | |
| | 3 | 91 | | if (!existeBeneficio) |
| | | 92 | | { |
| | 0 | 93 | | return (false, null, "Debe existir al menos un beneficio social vigente para el período"); |
| | | 94 | | } |
| | | 95 | | |
| | 3 | 96 | | var existeRemuneraciones = await _context.RemuneracionesGrado |
| | 3 | 97 | | .AsNoTracking() |
| | 3 | 98 | | .AnyAsync(r => r.VigenteDesde <= fechaInicioPeriodo && (r.VigenteHasta == null || r.VigenteHasta >= fechaIni |
| | | 99 | | |
| | 3 | 100 | | if (!existeRemuneraciones) |
| | | 101 | | { |
| | 0 | 102 | | return (false, null, "No existen tablas de remuneración vigentes para el período"); |
| | | 103 | | } |
| | | 104 | | |
| | 3 | 105 | | var existeFranjaIrpf = await _context.FranjasIrpf |
| | 3 | 106 | | .AsNoTracking() |
| | 3 | 107 | | .AnyAsync(f => f.VigenteDesde <= fechaInicioPeriodo && (f.VigenteHasta == null || f.VigenteHasta >= fechaIni |
| | | 108 | | |
| | 3 | 109 | | if (!existeFranjaIrpf) |
| | | 110 | | { |
| | 0 | 111 | | return (false, null, "No existen franjas IRPF vigentes para el período"); |
| | | 112 | | } |
| | | 113 | | |
| | 3 | 114 | | var snapshotJson = await SerializarSnapshotAsync(anio, mes, parametrosPeriodo, fechaInicioPeriodo, fechaFinPerio |
| | 3 | 115 | | var hashSnapshot = CalcularHash(snapshotJson); |
| | | 116 | | |
| | 3 | 117 | | var snapshot = new SnapshotInsumos |
| | 3 | 118 | | { |
| | 3 | 119 | | ContenidoJson = snapshotJson, |
| | 3 | 120 | | HashIntegridad = hashSnapshot |
| | 3 | 121 | | }; |
| | | 122 | | |
| | 3 | 123 | | var snapshotCreado = await _snapshotsRepository.CrearAsync(snapshot); |
| | | 124 | | |
| | 3 | 125 | | var periodo = new Periodo |
| | 3 | 126 | | { |
| | 3 | 127 | | Anio = anio, |
| | 3 | 128 | | Mes = mes, |
| | 3 | 129 | | Estado = "ABIERTA", |
| | 3 | 130 | | SnapshotId = snapshotCreado.Id, |
| | 3 | 131 | | FechaApertura = DateTime.UtcNow, |
| | 3 | 132 | | UsuarioApertura = _currentUser.UserId ?? 0, |
| | 3 | 133 | | HashSnapshot = hashSnapshot, |
| | 3 | 134 | | Activo = true |
| | 3 | 135 | | }; |
| | | 136 | | |
| | 3 | 137 | | var periodoCreado = await _periodosRepository.CrearAsync(periodo); |
| | | 138 | | |
| | 3 | 139 | | await _auditoriaService.LogAuditoriaAsync( |
| | 3 | 140 | | _currentUser.UserId ?? 0, |
| | 3 | 141 | | AccionEnum.AbrirPeriodoLiquidacion, |
| | 3 | 142 | | ContextoEnum.Liquidaciones, |
| | 3 | 143 | | _currentUser.Host, |
| | 3 | 144 | | entidad: nameof(Periodo), |
| | 3 | 145 | | entidadId: periodoCreado.Id, |
| | 3 | 146 | | detalle: new |
| | 3 | 147 | | { |
| | 3 | 148 | | anio, |
| | 3 | 149 | | mes, |
| | 3 | 150 | | snapshotId = snapshotCreado.Id, |
| | 3 | 151 | | hashSnapshot |
| | 3 | 152 | | }); |
| | | 153 | | |
| | 3 | 154 | | return (true, periodoCreado, null); |
| | 5 | 155 | | } |
| | | 156 | | |
| | | 157 | | public Task<Periodo?> ObtenerActivoAsync() |
| | | 158 | | { |
| | 0 | 159 | | return _periodosRepository.ObtenerActivoAsync(); |
| | | 160 | | } |
| | | 161 | | |
| | | 162 | | public Task<Periodo?> ObtenerPorIdAsync(long id) |
| | | 163 | | { |
| | 0 | 164 | | return _periodosRepository.ObtenerPorIdAsync(id); |
| | | 165 | | } |
| | | 166 | | |
| | | 167 | | private async Task<string> SerializarSnapshotAsync( |
| | | 168 | | short anio, |
| | | 169 | | short mes, |
| | | 170 | | ParametroPeriodo parametrosPeriodo, |
| | | 171 | | DateTime fechaInicioPeriodo, |
| | | 172 | | DateTime fechaFinPeriodo) |
| | | 173 | | { |
| | 3 | 174 | | var lotes = await _context.LotesCompensacion |
| | 3 | 175 | | .AsNoTracking() |
| | 3 | 176 | | .Where(l => l.Periodo.Year == anio && l.Periodo.Month == mes && l.Estado == "aprobado") |
| | 3 | 177 | | .Select(l => new |
| | 3 | 178 | | { |
| | 3 | 179 | | l.Id, |
| | 3 | 180 | | l.TipoCompensacionId, |
| | 3 | 181 | | l.Periodo, |
| | 3 | 182 | | l.Estado, |
| | 3 | 183 | | l.Fuente, |
| | 3 | 184 | | l.NombreArchivo, |
| | 3 | 185 | | l.HashArchivo, |
| | 3 | 186 | | l.UsuarioId, |
| | 3 | 187 | | l.AprobadoPor, |
| | 3 | 188 | | l.AprobadoEn |
| | 3 | 189 | | }) |
| | 3 | 190 | | .ToListAsync(); |
| | | 191 | | |
| | 3 | 192 | | var beneficios = await _context.BeneficiosSociales |
| | 3 | 193 | | .AsNoTracking() |
| | 3 | 194 | | .Where(b => b.Estado == "activo" && b.VigenteDesde <= fechaFinPeriodo && (b.VigenteHasta == null || b.Vigent |
| | 3 | 195 | | .Select(b => new |
| | 3 | 196 | | { |
| | 3 | 197 | | b.Id, |
| | 3 | 198 | | b.PersonaId, |
| | 3 | 199 | | b.TipoBeneficioId, |
| | 3 | 200 | | b.Cantidad, |
| | 3 | 201 | | b.VigenteDesde, |
| | 3 | 202 | | b.VigenteHasta, |
| | 3 | 203 | | b.Estado, |
| | 3 | 204 | | b.ClaveEvento, |
| | 3 | 205 | | b.FechaEvento, |
| | 3 | 206 | | b.RetroactividadPendiente |
| | 3 | 207 | | }) |
| | 3 | 208 | | .ToListAsync(); |
| | | 209 | | |
| | 3 | 210 | | var remuneraciones = await _context.RemuneracionesGrado |
| | 3 | 211 | | .AsNoTracking() |
| | 3 | 212 | | .Where(r => r.VigenteDesde <= fechaInicioPeriodo && (r.VigenteHasta == null || r.VigenteHasta >= fechaInicio |
| | 3 | 213 | | .Select(r => new |
| | 3 | 214 | | { |
| | 3 | 215 | | r.Id, |
| | 3 | 216 | | r.GradoId, |
| | 3 | 217 | | r.ItemId, |
| | 3 | 218 | | r.Monto, |
| | 3 | 219 | | r.VigenteDesde, |
| | 3 | 220 | | r.VigenteHasta |
| | 3 | 221 | | }) |
| | 3 | 222 | | .ToListAsync(); |
| | | 223 | | |
| | 3 | 224 | | var franjasIrpf = await _context.FranjasIrpf |
| | 3 | 225 | | .AsNoTracking() |
| | 3 | 226 | | .Where(f => f.VigenteDesde <= fechaInicioPeriodo && (f.VigenteHasta == null || f.VigenteHasta >= fechaInicio |
| | 3 | 227 | | .Select(f => new |
| | 3 | 228 | | { |
| | 3 | 229 | | f.Id, |
| | 3 | 230 | | f.NumeroFranja, |
| | 3 | 231 | | f.DesdeBpc, |
| | 3 | 232 | | f.HastaBpc, |
| | 3 | 233 | | f.Tasa, |
| | 3 | 234 | | f.VigenteDesde, |
| | 3 | 235 | | f.VigenteHasta |
| | 3 | 236 | | }) |
| | 3 | 237 | | .ToListAsync(); |
| | | 238 | | |
| | 3 | 239 | | var snapshot = new |
| | 3 | 240 | | { |
| | 3 | 241 | | anio, |
| | 3 | 242 | | mes, |
| | 3 | 243 | | fechaCreacion = DateTime.UtcNow, |
| | 3 | 244 | | parametros = new |
| | 3 | 245 | | { |
| | 3 | 246 | | parametrosPeriodo.Anio, |
| | 3 | 247 | | parametrosPeriodo.Mes, |
| | 3 | 248 | | parametrosPeriodo.BpcAnual, |
| | 3 | 249 | | parametrosPeriodo.TasaMontepioLeyVieja, |
| | 3 | 250 | | parametrosPeriodo.TasaMontepioLeyNueva, |
| | 3 | 251 | | parametrosPeriodo.TasaFonasa, |
| | 3 | 252 | | parametrosPeriodo.TasaFrl, |
| | 3 | 253 | | parametrosPeriodo.UmbralDeduccionBpc, |
| | 3 | 254 | | parametrosPeriodo.TasaDeduccionAlta, |
| | 3 | 255 | | parametrosPeriodo.TasaDeduccionBaja, |
| | 3 | 256 | | parametrosPeriodo.PorcentajeMinimoDeficit |
| | 3 | 257 | | }, |
| | 3 | 258 | | lotesCompensacion = lotes, |
| | 3 | 259 | | beneficiosSociales = beneficios, |
| | 3 | 260 | | remuneracionesGrado = remuneraciones, |
| | 3 | 261 | | franjasIrpf = franjasIrpf |
| | 3 | 262 | | }; |
| | | 263 | | |
| | 3 | 264 | | return JsonSerializer.Serialize(snapshot, new JsonSerializerOptions |
| | 3 | 265 | | { |
| | 3 | 266 | | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, |
| | 3 | 267 | | WriteIndented = false |
| | 3 | 268 | | }); |
| | 3 | 269 | | } |
| | | 270 | | |
| | | 271 | | private static string CalcularHash(string contenidoJson) |
| | | 272 | | { |
| | 3 | 273 | | using var sha256 = SHA256.Create(); |
| | 3 | 274 | | var bytes = Encoding.UTF8.GetBytes(contenidoJson); |
| | 3 | 275 | | return Convert.ToHexString(sha256.ComputeHash(bytes)).ToLowerInvariant(); |
| | 3 | 276 | | } |
| | | 277 | | } |