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); // Asumiendo que GetByIdAsync devuelve una tupla 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) { // Validar Destinatario 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) // Asumiendo GetByIdSimpleAsync en ICanillaRepository 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(); if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); using var transaction = connection.BeginTransaction(); try { var notaCreada = await _notaRepo.CreateAsync(nuevaNota, idUsuario, transaction); if (notaCreada == null) throw new DataException("Error al registrar la nota."); // Afectar Saldo // Nota de Crédito: Disminuye la deuda del destinatario (monto positivo para el servicio de saldo) // Nota de Débito: Aumenta la deuda del destinatario (monto negativo para el servicio de saldo) decimal montoAjusteSaldo = createDto.Tipo == "Credito" ? createDto.Monto : -createDto.Monto; bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaCreada.Destino, notaCreada.IdDestino, notaCreada.IdEmpresa, montoAjusteSaldo, 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 { } _logger.LogError(ex, "Error CrearAsync NotaCreditoDebito."); return (null, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> ActualizarAsync(int idNota, UpdateNotaDto updateDto, int idUsuario) { using var connection = _connectionFactory.CreateConnection(); if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); using var transaction = connection.BeginTransaction(); try { var notaExistente = await _notaRepo.GetByIdAsync(idNota); if (notaExistente == null) return (false, "Nota no encontrada."); // Calcular diferencia de monto para ajustar saldo decimal montoOriginal = notaExistente.Tipo == "Credito" ? notaExistente.Monto : -notaExistente.Monto; decimal montoNuevo = notaExistente.Tipo == "Credito" ? updateDto.Monto : -updateDto.Monto; // Tipo no cambia decimal diferenciaAjusteSaldo = montoNuevo - montoOriginal; notaExistente.Monto = updateDto.Monto; notaExistente.Observaciones = updateDto.Observaciones; var actualizado = await _notaRepo.UpdateAsync(notaExistente, idUsuario, transaction); if (!actualizado) throw new DataException("Error al actualizar la nota."); 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 { } return (false, "Nota no encontrada."); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error ActualizarAsync NotaC/D ID: {Id}", idNota); return (false, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> EliminarAsync(int idNota, int idUsuario) { using var connection = _connectionFactory.CreateConnection(); if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); using var transaction = connection.BeginTransaction(); try { var notaExistente = await _notaRepo.GetByIdAsync(idNota); if (notaExistente == null) return (false, "Nota no encontrada."); // Revertir el efecto en el saldo 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."); 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 { } return (false, "Nota no encontrada."); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error EliminarAsync NotaC/D ID: {Id}", idNota); return (false, $"Error interno: {ex.Message}"); } } } }