using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Distribucion; 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 NotaCreditoDebitoService : INotaCreditoDebitoService { private readonly INotaCreditoDebitoRepository _notaRepo; private readonly IDistribuidorRepository _distribuidorRepo; private readonly ICanillaRepository _canillaRepo; private readonly IEmpresaRepository _empresaRepo; private readonly ISaldoRepository _saldoRepo; private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public NotaCreditoDebitoService( INotaCreditoDebitoRepository notaRepo, IDistribuidorRepository distribuidorRepo, ICanillaRepository canillaRepo, IEmpresaRepository empresaRepo, ISaldoRepository saldoRepo, DbConnectionFactory connectionFactory, ILogger logger) { _notaRepo = notaRepo; _distribuidorRepo = distribuidorRepo; _canillaRepo = canillaRepo; _empresaRepo = empresaRepo; _saldoRepo = saldoRepo; _connectionFactory = connectionFactory; _logger = logger; } private async Task MapToDto(NotaCreditoDebito nota) { if (nota == null) return null!; string nombreDestinatario = "N/A"; if (nota.Destino == "Distribuidores") { var distData = await _distribuidorRepo.GetByIdAsync(nota.IdDestino); nombreDestinatario = distData.Distribuidor?.Nombre ?? "Distribuidor Desconocido"; } else if (nota.Destino == "Canillas") { var canData = await _canillaRepo.GetByIdAsync(nota.IdDestino); nombreDestinatario = canData.Canilla?.NomApe ?? "Canillita Desconocido"; } var empresa = await _empresaRepo.GetByIdAsync(nota.IdEmpresa); return new NotaCreditoDebitoDto { IdNota = nota.IdNota, Destino = nota.Destino, IdDestino = nota.IdDestino, NombreDestinatario = nombreDestinatario, Referencia = nota.Referencia, Tipo = nota.Tipo, Fecha = nota.Fecha.ToString("yyyy-MM-dd"), Monto = nota.Monto, Observaciones = nota.Observaciones, IdEmpresa = nota.IdEmpresa, NombreEmpresa = empresa?.Nombre ?? "Empresa Desconocida" }; } public async Task> ObtenerTodosAsync( DateTime? fechaDesde, DateTime? fechaHasta, string? destino, int? idDestino, int? idEmpresa, string? tipoNota) { var notas = await _notaRepo.GetAllAsync(fechaDesde, fechaHasta, destino, idDestino, idEmpresa, tipoNota); var dtos = new List(); foreach (var nota in notas) { dtos.Add(await MapToDto(nota)); } return dtos; } public async Task ObtenerPorIdAsync(int idNota) { var nota = await _notaRepo.GetByIdAsync(idNota); return nota == null ? null : await MapToDto(nota); } public async Task<(NotaCreditoDebitoDto? Nota, string? Error)> CrearAsync(CreateNotaDto createDto, int idUsuario) { if (createDto.Destino == "Distribuidores") { if (await _distribuidorRepo.GetByIdSimpleAsync(createDto.IdDestino) == null) return (null, "El distribuidor especificado no existe."); } else if (createDto.Destino == "Canillas") { if (await _canillaRepo.GetByIdSimpleAsync(createDto.IdDestino) == null) return (null, "El canillita especificado no existe."); } else { return (null, "Tipo de destino inválido."); } if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null) return (null, "La empresa especificada no existe."); var nuevaNota = new NotaCreditoDebito { Destino = createDto.Destino, IdDestino = createDto.IdDestino, Referencia = createDto.Referencia, Tipo = createDto.Tipo, Fecha = createDto.Fecha.Date, Monto = createDto.Monto, Observaciones = createDto.Observaciones, IdEmpresa = createDto.IdEmpresa }; 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 notaCreada = await _notaRepo.CreateAsync(nuevaNota, idUsuario, transaction); if (notaCreada == null) throw new DataException("Error al registrar la nota."); decimal montoParaSaldo; if (createDto.Tipo == "Credito") { montoParaSaldo = -createDto.Monto; } else { montoParaSaldo = createDto.Monto; } bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaCreada.Destino, notaCreada.IdDestino, notaCreada.IdEmpresa, montoParaSaldo, transaction); if (!saldoActualizado) throw new DataException($"Error al actualizar el saldo para {notaCreada.Destino} ID {notaCreada.IdDestino}."); transaction.Commit(); _logger.LogInformation("NotaC/D ID {Id} creada y saldo afectado por Usuario ID {UserId}.", notaCreada.IdNota, idUsuario); return (await MapToDto(notaCreada), null); } catch (Exception ex) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de CrearAsync NotaCreditoDebito."); } _logger.LogError(ex, "Error CrearAsync NotaCreditoDebito."); 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 idNota, UpdateNotaDto 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 notaExistente = await _notaRepo.GetByIdAsync(idNota); if (notaExistente == null) { transaction.Rollback(); return (false, "Nota no encontrada."); } decimal impactoOriginalSaldo = notaExistente.Tipo == "Credito" ? -notaExistente.Monto : notaExistente.Monto; decimal impactoNuevoSaldo = notaExistente.Tipo == "Credito" ? -updateDto.Monto : updateDto.Monto; decimal diferenciaAjusteSaldo = impactoNuevoSaldo - impactoOriginalSaldo; var notaParaActualizarEnRepo = new NotaCreditoDebito { IdNota = notaExistente.IdNota, Destino = notaExistente.Destino, IdDestino = notaExistente.IdDestino, Referencia = notaExistente.Referencia, Tipo = notaExistente.Tipo, Fecha = notaExistente.Fecha, Monto = updateDto.Monto, Observaciones = updateDto.Observaciones, IdEmpresa = notaExistente.IdEmpresa }; var actualizado = await _notaRepo.UpdateAsync(notaParaActualizarEnRepo, idUsuario, transaction); if (!actualizado) throw new DataException("Error al actualizar la nota en la base de datos."); if (diferenciaAjusteSaldo != 0) { bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaExistente.Destino, notaExistente.IdDestino, notaExistente.IdEmpresa, diferenciaAjusteSaldo, transaction); if (!saldoActualizado) throw new DataException("Error al ajustar el saldo tras la actualización de la nota."); } transaction.Commit(); _logger.LogInformation("NotaC/D ID {Id} actualizada por Usuario ID {UserId}.", idNota, idUsuario); return (true, null); } catch (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync NotaCreditoDebito (KeyNotFound)."); } return (false, "Nota no encontrada."); } catch (Exception ex) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync NotaCreditoDebito."); } _logger.LogError(ex, "Error ActualizarAsync Nota C/D ID: {Id}", idNota); 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 idNota, 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 notaExistente = await _notaRepo.GetByIdAsync(idNota); if (notaExistente == null) { transaction.Rollback(); return (false, "Nota no encontrada."); } decimal montoReversion = notaExistente.Tipo == "Credito" ? notaExistente.Monto : -notaExistente.Monto; var eliminado = await _notaRepo.DeleteAsync(idNota, idUsuario, transaction); if (!eliminado) throw new DataException("Error al eliminar la nota de la base de datos."); bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaExistente.Destino, notaExistente.IdDestino, notaExistente.IdEmpresa, montoReversion, transaction); if (!saldoActualizado) throw new DataException("Error al revertir el saldo tras la eliminación de la nota."); transaction.Commit(); _logger.LogInformation("NotaC/D ID {Id} eliminada y saldo revertido por Usuario ID {UserId}.", idNota, idUsuario); return (true, null); } catch (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync NotaCreditoDebito (KeyNotFound)."); } return (false, "Nota no encontrada."); } catch (Exception ex) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync NotaCreditoDebito."); } _logger.LogError(ex, "Error EliminarAsync NotaC/D ID: {Id}", idNota); 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(); } } } } }