using Microsoft.EntityFrameworkCore; using Rs_system.Data; using Rs_system.Models; using Rs_system.Models.ViewModels; namespace Rs_system.Services; public class DiezmoCierreService : IDiezmoCierreService { private readonly ApplicationDbContext _context; private readonly IDiezmoCalculoService _calculo; public DiezmoCierreService(ApplicationDbContext context, IDiezmoCalculoService calculo) { _context = context; _calculo = calculo; } // ────────────────────────────────────────────────────────────────────────── // Catálogos // ────────────────────────────────────────────────────────────────────────── public async Task> GetTiposSalidaActivosAsync() => await _context.DiezmoTiposSalida .Where(t => t.Activo && !t.Eliminado) .OrderBy(t => t.Nombre) .ToListAsync(); public async Task> GetBeneficiariosActivosAsync() => await _context.DiezmoBeneficiarios .Where(b => b.Activo && !b.Eliminado) .OrderBy(b => b.Nombre) .ToListAsync(); // ────────────────────────────────────────────────────────────────────────── // Cierres // ────────────────────────────────────────────────────────────────────────── public async Task> GetCierresAsync(int? anio = null) { var query = _context.DiezmoCierres .Where(c => !c.Eliminado); if (anio.HasValue) query = query.Where(c => c.Fecha.Year == anio.Value); return await query .OrderByDescending(c => c.Fecha) .ToListAsync(); } public async Task GetCierreByIdAsync(long id) => await _context.DiezmoCierres .Include(c => c.Detalles.Where(d => !d.Eliminado)) .ThenInclude(d => d.Miembro) .ThenInclude(m => m.Persona) .Include(c => c.Salidas.Where(s => !s.Eliminado)) .ThenInclude(s => s.TipoSalida) .Include(c => c.Salidas.Where(s => !s.Eliminado)) .ThenInclude(s => s.Beneficiario) .FirstOrDefaultAsync(c => c.Id == id && !c.Eliminado); public async Task CrearCierreAsync(DateOnly fecha, string? observaciones, string creadoPor) { // Verificar que no exista ya un cierre para esa fecha var yaExiste = await _context.DiezmoCierres .AnyAsync(c => c.Fecha == fecha && !c.Eliminado); if (yaExiste) throw new InvalidOperationException($"Ya existe un cierre para la fecha {fecha:dd/MM/yyyy}."); var cierre = new DiezmoCierre { Fecha = fecha, Observaciones = observaciones, CreadoPor = creadoPor, CreadoEn = DateTime.UtcNow, ActualizadoEn = DateTime.UtcNow }; _context.DiezmoCierres.Add(cierre); await _context.SaveChangesAsync(); return cierre; } // ────────────────────────────────────────────────────────────────────────── // Detalles // ────────────────────────────────────────────────────────────────────────── public async Task AgregarDetalleAsync(long cierreId, DiezmoDetalleFormViewModel vm, string usuario) { var cierre = await GetCierreOrThrowAsync(cierreId); GuardarSiAbierto(cierre); // Se invierte la lógica: El net (Diezmo) se introduce manual. El cambio es derivado. var neto = vm.MontoNeto; var cambio = vm.MontoEntregado - neto; if (cambio < 0) cambio = 0; // Prevenir errores; si entregó menos del neto se asume cambio 0 var detalle = new DiezmoDetalle { DiezmoCierreId = cierreId, MiembroId = vm.MiembroId, MontoEntregado = vm.MontoEntregado, CambioEntregado = cambio, MontoNeto = neto, Observaciones = vm.Observaciones, Fecha = DateTime.UtcNow, CreadoPor = usuario, CreadoEn = DateTime.UtcNow, ActualizadoEn = DateTime.UtcNow }; _context.DiezmoDetalles.Add(detalle); await _context.SaveChangesAsync(); await RecalcularTotalesAsync(cierreId); } public async Task EliminarDetalleAsync(long detalleId, string usuario) { var detalle = await _context.DiezmoDetalles .FirstOrDefaultAsync(d => d.Id == detalleId && !d.Eliminado) ?? throw new InvalidOperationException("Detalle no encontrado."); var cierre = await GetCierreOrThrowAsync(detalle.DiezmoCierreId); GuardarSiAbierto(cierre); detalle.Eliminado = true; detalle.ActualizadoEn = DateTime.UtcNow; detalle.ActualizadoPor = usuario; await _context.SaveChangesAsync(); await RegistrarBitacoraAsync(detalle.DiezmoCierreId, "ELIMINAR_DETALLE", $"Detalle #{detalleId} eliminado", usuario); await RecalcularTotalesAsync(detalle.DiezmoCierreId); } // ────────────────────────────────────────────────────────────────────────── // Salidas // ────────────────────────────────────────────────────────────────────────── public async Task AgregarSalidaAsync(long cierreId, DiezmoSalidaFormViewModel vm, string usuario) { var cierre = await GetCierreOrThrowAsync(cierreId); GuardarSiAbierto(cierre); var salida = new DiezmoSalida { DiezmoCierreId = cierreId, TipoSalidaId = vm.TipoSalidaId, BeneficiarioId = vm.BeneficiarioId, Monto = vm.Monto, Concepto = vm.Concepto, Fecha = DateTime.UtcNow, CreadoPor = usuario, CreadoEn = DateTime.UtcNow, ActualizadoEn = DateTime.UtcNow }; _context.DiezmoSalidas.Add(salida); await _context.SaveChangesAsync(); await RecalcularTotalesAsync(cierreId); } public async Task EliminarSalidaAsync(long salidaId, string usuario) { var salida = await _context.DiezmoSalidas .FirstOrDefaultAsync(s => s.Id == salidaId && !s.Eliminado) ?? throw new InvalidOperationException("Salida no encontrada."); var cierre = await GetCierreOrThrowAsync(salida.DiezmoCierreId); GuardarSiAbierto(cierre); salida.Eliminado = true; salida.ActualizadoEn = DateTime.UtcNow; await _context.SaveChangesAsync(); await RegistrarBitacoraAsync(salida.DiezmoCierreId, "ELIMINAR_SALIDA", $"Salida #{salidaId} eliminada", usuario); await RecalcularTotalesAsync(salida.DiezmoCierreId); } // ────────────────────────────────────────────────────────────────────────── // Flujo de cierre / reapertura // ────────────────────────────────────────────────────────────────────────── public async Task CerrarCierreAsync(long cierreId, string usuario) { var cierre = await GetCierreByIdAsync(cierreId) ?? throw new InvalidOperationException("Cierre no encontrado."); if (cierre.Cerrado) throw new InvalidOperationException("El cierre ya se encuentra cerrado."); // Recalcular por si hay cambios recientes antes de sellar _calculo.RecalcularTotales(cierre); cierre.Cerrado = true; cierre.FechaCierre = DateTime.UtcNow; cierre.CerradoPor = usuario; cierre.ActualizadoEn = DateTime.UtcNow; cierre.ActualizadoPor = usuario; await _context.SaveChangesAsync(); await RegistrarBitacoraAsync(cierreId, "CIERRE", $"Cierre sellado. Saldo final: {cierre.SaldoFinal:C}", usuario); } public async Task ReabrirCierreAsync(long cierreId, string usuario) { var cierre = await GetCierreOrThrowAsync(cierreId); if (!cierre.Cerrado) throw new InvalidOperationException("El cierre ya se encuentra abierto."); cierre.Cerrado = false; cierre.FechaCierre = null; cierre.CerradoPor = null; cierre.ActualizadoEn = DateTime.UtcNow; cierre.ActualizadoPor = usuario; await _context.SaveChangesAsync(); await RegistrarBitacoraAsync(cierreId, "REAPERTURA", "Cierre reabierto", usuario); } // ────────────────────────────────────────────────────────────────────────── // Totales // ────────────────────────────────────────────────────────────────────────── public async Task RecalcularTotalesAsync(long cierreId) { var cierre = await GetCierreByIdAsync(cierreId) ?? throw new InvalidOperationException("Cierre no encontrado."); _calculo.RecalcularTotales(cierre); cierre.ActualizadoEn = DateTime.UtcNow; await _context.SaveChangesAsync(); } // ────────────────────────────────────────────────────────────────────────── // Helpers privados // ────────────────────────────────────────────────────────────────────────── private async Task GetCierreOrThrowAsync(long id) => await _context.DiezmoCierres.FirstOrDefaultAsync(c => c.Id == id && !c.Eliminado) ?? throw new InvalidOperationException("Cierre no encontrado."); private static void GuardarSiAbierto(DiezmoCierre cierre) { if (cierre.Cerrado) throw new InvalidOperationException("No se puede modificar un cierre que ya está cerrado."); } private async Task RegistrarBitacoraAsync(long cierreId, string accion, string detalle, string usuario) { await _context.Database.ExecuteSqlRawAsync( """ INSERT INTO public.diezmo_bitacora (diezmo_cierre_id, accion, detalle, realizado_por, realizado_en) VALUES ({0}, {1}, {2}, {3}, {4}) """, cierreId, accion, detalle, usuario, DateTime.UtcNow); } }