using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Data.Repositories.Distribucion; // Para IDistribuidorRepository, ICanillaRepository 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 SaldoService : ISaldoService { private readonly ISaldoRepository _saldoRepo; private readonly IDistribuidorRepository _distribuidorRepo; // Para nombres private readonly ICanillaRepository _canillaRepo; // Para nombres private readonly IEmpresaRepository _empresaRepo; // Para nombres private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public SaldoService( ISaldoRepository saldoRepo, IDistribuidorRepository distribuidorRepo, ICanillaRepository canillaRepo, IEmpresaRepository empresaRepo, DbConnectionFactory connectionFactory, ILogger logger) { _saldoRepo = saldoRepo; _distribuidorRepo = distribuidorRepo; _canillaRepo = canillaRepo; _empresaRepo = empresaRepo; _connectionFactory = connectionFactory; _logger = logger; } private async Task MapToGestionDto(Saldo saldo) { if (saldo == null) return null!; string nombreDestinatario = "N/A"; if (saldo.Destino == "Distribuidores") { var distData = await _distribuidorRepo.GetByIdAsync(saldo.IdDestino); nombreDestinatario = distData.Distribuidor?.Nombre ?? $"Dist. ID {saldo.IdDestino}"; } else if (saldo.Destino == "Canillas") { var canData = await _canillaRepo.GetByIdAsync(saldo.IdDestino); nombreDestinatario = canData.Canilla?.NomApe ?? $"Can. ID {saldo.IdDestino}"; } var empresa = await _empresaRepo.GetByIdAsync(saldo.IdEmpresa); return new SaldoGestionDto { IdSaldo = saldo.IdSaldo, Destino = saldo.Destino, IdDestino = saldo.IdDestino, NombreDestinatario = nombreDestinatario, IdEmpresa = saldo.IdEmpresa, NombreEmpresa = empresa?.Nombre ?? $"Emp. ID {saldo.IdEmpresa}", Monto = saldo.Monto, FechaUltimaModificacion = saldo.FechaUltimaModificacion }; } public async Task> ObtenerSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter) { var saldos = await _saldoRepo.GetSaldosParaGestionAsync(destinoFilter, idDestinoFilter, idEmpresaFilter); var dtos = new List(); foreach (var saldo in saldos) { dtos.Add(await MapToGestionDto(saldo)); } return dtos; } public async Task<(bool Exito, string? Error, SaldoGestionDto? SaldoActualizado)> RealizarAjusteManualSaldoAsync(AjusteSaldoRequestDto ajusteDto, int idUsuarioAjuste) { if (ajusteDto.MontoAjuste == 0) return (false, "El monto de ajuste no puede ser cero.", null); // Validar existencia de Destino y Empresa if (ajusteDto.Destino == "Distribuidores") { if (await _distribuidorRepo.GetByIdSimpleAsync(ajusteDto.IdDestino) == null) return (false, "El distribuidor especificado no existe.", null); } else if (ajusteDto.Destino == "Canillas") { if (await _canillaRepo.GetByIdSimpleAsync(ajusteDto.IdDestino) == null) return (false, "El canillita especificado no existe.", null); } else { return (false, "Tipo de destino inválido.", null); } if (await _empresaRepo.GetByIdAsync(ajusteDto.IdEmpresa) == null) return (false, "La empresa especificada no existe.", null); using var connection = _connectionFactory.CreateConnection(); if (connection.State != ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); } using var transaction = connection.BeginTransaction(); try { var saldoActual = await _saldoRepo.GetSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, transaction); if (saldoActual == null) { // Podríamos crear el saldo aquí si no existe y se quiere permitir un ajuste sobre un saldo nuevo. // O devolver error. Por ahora, error. transaction.Rollback(); return (false, "No se encontró un saldo existente para el destinatario y empresa especificados.", null); } decimal saldoAnterior = saldoActual.Monto; bool modificado = await _saldoRepo.ModificarSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, ajusteDto.MontoAjuste, transaction); if (!modificado) { throw new DataException("No se pudo modificar el saldo principal."); } // Obtener el saldo después de la modificación para el historial var saldoDespuesDeModificacion = await _saldoRepo.GetSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, transaction); if(saldoDespuesDeModificacion == null) throw new DataException("No se pudo obtener el saldo después de la modificación."); var historial = new SaldoAjusteHistorial { Destino = ajusteDto.Destino, IdDestino = ajusteDto.IdDestino, IdEmpresa = ajusteDto.IdEmpresa, MontoAjuste = ajusteDto.MontoAjuste, SaldoAnterior = saldoAnterior, SaldoNuevo = saldoDespuesDeModificacion.Monto, // saldoActual.Monto + ajusteDto.MontoAjuste, Justificacion = ajusteDto.Justificacion, FechaAjuste = DateTime.Now, // O UtcNow IdUsuarioAjuste = idUsuarioAjuste }; await _saldoRepo.CreateSaldoAjusteHistorialAsync(historial, transaction); transaction.Commit(); _logger.LogInformation("Ajuste manual de saldo realizado para {Destino} ID {IdDestino}, Empresa ID {IdEmpresa} por Usuario ID {IdUsuarioAjuste}. Monto: {MontoAjuste}", ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, idUsuarioAjuste, ajusteDto.MontoAjuste); var saldoDtoActualizado = await MapToGestionDto(saldoDespuesDeModificacion); return (true, null, saldoDtoActualizado); } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception rbEx){ _logger.LogError(rbEx, "Error en Rollback de RealizarAjusteManualSaldoAsync."); } _logger.LogError(ex, "Error en RealizarAjusteManualSaldoAsync."); return (false, $"Error interno al realizar el ajuste: {ex.Message}", null); } finally { if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); } } } } }