using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Dtos.Auditoria; using GestionIntegral.Api.Dtos.Contables; using GestionIntegral.Api.Models.Contables; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; namespace GestionIntegral.Api.Services.Contables { public class PagoDistribuidorService : IPagoDistribuidorService { private readonly IPagoDistribuidorRepository _pagoRepo; private readonly IDistribuidorRepository _distribuidorRepo; private readonly ITipoPagoRepository _tipoPagoRepo; private readonly IEmpresaRepository _empresaRepo; private readonly ISaldoRepository _saldoRepo; private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public PagoDistribuidorService( IPagoDistribuidorRepository pagoRepo, IDistribuidorRepository distribuidorRepo, ITipoPagoRepository tipoPagoRepo, IEmpresaRepository empresaRepo, ISaldoRepository saldoRepo, DbConnectionFactory connectionFactory, ILogger logger) { _pagoRepo = pagoRepo; _distribuidorRepo = distribuidorRepo; _tipoPagoRepo = tipoPagoRepo; _empresaRepo = empresaRepo; _saldoRepo = saldoRepo; _connectionFactory = connectionFactory; _logger = logger; } private async Task MapToDto(PagoDistribuidor pago) { if (pago == null) return null!; var distribuidorData = await _distribuidorRepo.GetByIdAsync(pago.IdDistribuidor); var tipoPago = await _tipoPagoRepo.GetByIdAsync(pago.IdTipoPago); var empresa = await _empresaRepo.GetByIdAsync(pago.IdEmpresa); return new PagoDistribuidorDto { IdPago = pago.IdPago, IdDistribuidor = pago.IdDistribuidor, NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "N/A", Fecha = pago.Fecha.ToString("yyyy-MM-dd"), TipoMovimiento = pago.TipoMovimiento, Recibo = pago.Recibo, Monto = pago.Monto, IdTipoPago = pago.IdTipoPago, NombreTipoPago = tipoPago?.Nombre ?? "N/A", Detalle = pago.Detalle, IdEmpresa = pago.IdEmpresa, NombreEmpresa = empresa?.Nombre ?? "N/A" }; } public async Task> ObtenerTodosAsync( DateTime? fechaDesde, DateTime? fechaHasta, int? idDistribuidor, int? idEmpresa, string? tipoMovimiento) { var pagos = await _pagoRepo.GetAllAsync(fechaDesde, fechaHasta, idDistribuidor, idEmpresa, tipoMovimiento); var dtos = new List(); foreach (var pago in pagos) { dtos.Add(await MapToDto(pago)); } return dtos; } public async Task ObtenerPorIdAsync(int idPago) { var pago = await _pagoRepo.GetByIdAsync(idPago); return pago == null ? null : await MapToDto(pago); } public async Task<(PagoDistribuidorDto? Pago, string? Error)> CrearAsync(CreatePagoDistribuidorDto createDto, int idUsuario) { if (await _distribuidorRepo.GetByIdSimpleAsync(createDto.IdDistribuidor) == null) return (null, "Distribuidor no válido."); if (await _tipoPagoRepo.GetByIdAsync(createDto.IdTipoPago) == null) return (null, "Tipo de pago no válido."); if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null) return (null, "Empresa no válida."); if (await _pagoRepo.ExistsByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento)) return (null, $"Ya existe un pago '{createDto.TipoMovimiento}' con el número de recibo '{createDto.Recibo}'."); var nuevoPago = new PagoDistribuidor { IdDistribuidor = createDto.IdDistribuidor, Fecha = createDto.Fecha.Date, TipoMovimiento = createDto.TipoMovimiento, Recibo = createDto.Recibo, Monto = createDto.Monto, IdTipoPago = createDto.IdTipoPago, Detalle = createDto.Detalle, IdEmpresa = createDto.IdEmpresa }; using var connection = _connectionFactory.CreateConnection(); IDbTransaction? transaction = null; // Declarar fuera para el finally try { if (connection.State != ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } transaction = connection.BeginTransaction(); var pagoCreado = await _pagoRepo.CreateAsync(nuevoPago, idUsuario, transaction); if (pagoCreado == null) throw new DataException("Error al registrar el pago."); decimal montoParaSaldo; if (createDto.TipoMovimiento == "Recibido") { montoParaSaldo = -createDto.Monto; } else { montoParaSaldo = createDto.Monto; } bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoCreado.IdDistribuidor, pagoCreado.IdEmpresa, montoParaSaldo, transaction); if (!saldoActualizado) throw new DataException("Error al actualizar el saldo del distribuidor."); transaction.Commit(); _logger.LogInformation("PagoDistribuidor ID {Id} creado y saldo afectado por Usuario ID {UserId}.", pagoCreado.IdPago, idUsuario); return (await MapToDto(pagoCreado), null); } catch (Exception ex) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de CrearAsync PagoDistribuidor."); } _logger.LogError(ex, "Error CrearAsync PagoDistribuidor."); return (null, $"Error interno: {ex.Message}"); } finally { if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); } } } public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPago, UpdatePagoDistribuidorDto updateDto, int idUsuario) { using var connection = _connectionFactory.CreateConnection(); IDbTransaction? transaction = null; try { if (connection.State != ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } transaction = connection.BeginTransaction(); var pagoExistente = await _pagoRepo.GetByIdAsync(idPago); if (pagoExistente == null) { transaction.Rollback(); // Rollback si no se encuentra return (false, "Pago no encontrado."); } if (await _tipoPagoRepo.GetByIdAsync(updateDto.IdTipoPago) == null) { transaction.Rollback(); return (false, "Tipo de pago no válido."); } decimal impactoOriginalSaldo = pagoExistente.TipoMovimiento == "Recibido" ? -pagoExistente.Monto : pagoExistente.Monto; decimal impactoNuevoSaldo = pagoExistente.TipoMovimiento == "Recibido" ? -updateDto.Monto : updateDto.Monto; decimal diferenciaAjusteSaldo = impactoNuevoSaldo - impactoOriginalSaldo; var pagoParaActualizarEnRepo = new PagoDistribuidor { IdPago = pagoExistente.IdPago, IdDistribuidor = pagoExistente.IdDistribuidor, Fecha = pagoExistente.Fecha, TipoMovimiento = pagoExistente.TipoMovimiento, Recibo = pagoExistente.Recibo, Monto = updateDto.Monto, IdTipoPago = updateDto.IdTipoPago, Detalle = updateDto.Detalle, IdEmpresa = pagoExistente.IdEmpresa }; var actualizado = await _pagoRepo.UpdateAsync(pagoParaActualizarEnRepo, idUsuario, transaction); if (!actualizado) throw new DataException("Error al actualizar el pago en la base de datos."); if (diferenciaAjusteSaldo != 0) { bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, diferenciaAjusteSaldo, transaction); if (!saldoActualizado) throw new DataException("Error al ajustar el saldo del distribuidor tras la actualización del pago."); } transaction.Commit(); _logger.LogInformation("PagoDistribuidor ID {Id} actualizado por Usuario ID {UserId}.", idPago, idUsuario); return (true, null); } catch (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync PagoDistribuidor (KeyNotFound)."); } return (false, "Pago no encontrado."); } catch (Exception ex) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync PagoDistribuidor."); } _logger.LogError(ex, "Error ActualizarAsync PagoDistribuidor ID: {Id}", idPago); return (false, $"Error interno: {ex.Message}"); } finally { if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); } } } public async Task<(bool Exito, string? Error)> EliminarAsync(int idPago, int idUsuario) { using var connection = _connectionFactory.CreateConnection(); IDbTransaction? transaction = null; try { if (connection.State != ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } transaction = connection.BeginTransaction(); var pagoExistente = await _pagoRepo.GetByIdAsync(idPago); if (pagoExistente == null) { transaction.Rollback(); return (false, "Pago no encontrado."); } decimal montoReversion = pagoExistente.TipoMovimiento == "Recibido" ? pagoExistente.Monto : -pagoExistente.Monto; var eliminado = await _pagoRepo.DeleteAsync(idPago, idUsuario, transaction); if (!eliminado) throw new DataException("Error al eliminar el pago de la base de datos."); bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, montoReversion, transaction); if (!saldoActualizado) throw new DataException("Error al revertir el saldo del distribuidor tras la eliminación del pago."); transaction.Commit(); _logger.LogInformation("PagoDistribuidor ID {Id} eliminado por Usuario ID {UserId}.", idPago, idUsuario); return (true, null); } catch (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync PagoDistribuidor (KeyNotFound)."); } return (false, "Pago no encontrado."); } catch (Exception ex) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync PagoDistribuidor."); } _logger.LogError(ex, "Error EliminarAsync PagoDistribuidor ID: {Id}", idPago); return (false, $"Error interno: {ex.Message}"); } finally { if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); } } } public async Task> ObtenerHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModifico, string? tipoModificacion, int? idPagoAfectado) { var historialData = await _pagoRepo.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPagoAfectado); return historialData.Select(h => new PagoDistribuidorHistorialDto { Id_Pago = h.Historial.Id_Pago, Id_Distribuidor = h.Historial.Id_Distribuidor, Fecha = h.Historial.Fecha, TipoMovimiento = h.Historial.TipoMovimiento, Recibo = h.Historial.Recibo, Monto = h.Historial.Monto, Id_TipoPago = h.Historial.Id_TipoPago, Detalle = h.Historial.Detalle, Id_Empresa = h.Historial.Id_Empresa, Id_Usuario = h.Historial.Id_Usuario, NombreUsuarioModifico = h.NombreUsuarioModifico, FechaMod = h.Historial.FechaMod, TipoMod = h.Historial.TipoMod }).ToList(); } } }