Refinamiento de permisos y ajustes en controles. Añade gestión sobre saldos y visualización. Entre otros..

This commit is contained in:
2025-06-06 18:33:09 -03:00
parent 8fb94f8cef
commit 35e24ab7d2
104 changed files with 5917 additions and 1205 deletions

View File

@@ -0,0 +1,12 @@
using GestionIntegral.Api.Dtos.Contables;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Contables
{
public interface ISaldoService
{
Task<IEnumerable<SaldoGestionDto>> ObtenerSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter);
Task<(bool Exito, string? Error, SaldoGestionDto? SaldoActualizado)> RealizarAjusteManualSaldoAsync(AjusteSaldoRequestDto ajusteDto, int idUsuarioAjuste);
}
}

View File

@@ -52,7 +52,7 @@ namespace GestionIntegral.Api.Services.Contables
}
else if (nota.Destino == "Canillas")
{
var canData = await _canillaRepo.GetByIdAsync(nota.IdDestino); // Asumiendo que GetByIdAsync devuelve una tupla
var canData = await _canillaRepo.GetByIdAsync(nota.IdDestino);
nombreDestinatario = canData.Canilla?.NomApe ?? "Canillita Desconocido";
}
@@ -95,7 +95,6 @@ namespace GestionIntegral.Api.Services.Contables
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)
@@ -103,7 +102,7 @@ namespace GestionIntegral.Api.Services.Contables
}
else if (createDto.Destino == "Canillas")
{
if (await _canillaRepo.GetByIdSimpleAsync(createDto.IdDestino) == null) // Asumiendo GetByIdSimpleAsync en ICanillaRepository
if (await _canillaRepo.GetByIdSimpleAsync(createDto.IdDestino) == null)
return (null, "El canillita especificado no existe.");
}
else { return (null, "Tipo de destino inválido."); }
@@ -124,19 +123,29 @@ namespace GestionIntegral.Api.Services.Contables
};
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
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.");
// 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;
decimal montoParaSaldo;
if (createDto.Tipo == "Credito")
{
montoParaSaldo = -createDto.Monto;
}
else
{
montoParaSaldo = createDto.Monto;
}
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaCreada.Destino, notaCreada.IdDestino, notaCreada.IdEmpresa, montoAjusteSaldo, transaction);
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();
@@ -145,32 +154,57 @@ namespace GestionIntegral.Api.Services.Contables
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
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();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
IDbTransaction? transaction = null;
try
{
var notaExistente = await _notaRepo.GetByIdAsync(idNota);
if (notaExistente == null) return (false, "Nota no encontrada.");
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;
// 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;
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
};
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.");
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)
{
@@ -182,30 +216,45 @@ namespace GestionIntegral.Api.Services.Contables
_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 (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 { }
_logger.LogError(ex, "Error ActualizarAsync NotaC/D ID: {Id}", idNota);
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();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
IDbTransaction? transaction = null;
try
{
var notaExistente = await _notaRepo.GetByIdAsync(idNota);
if (notaExistente == null) return (false, "Nota no encontrada.");
if (connection.State != ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
}
transaction = connection.BeginTransaction();
// Revertir el efecto en el saldo
decimal montoReversion = notaExistente.Tipo == "Credito" ? -notaExistente.Monto : notaExistente.Monto;
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.");
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.");
@@ -214,13 +263,20 @@ namespace GestionIntegral.Api.Services.Contables
_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 (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 { }
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();
}
}
}
}
}

View File

@@ -95,7 +95,6 @@ namespace GestionIntegral.Api.Services.Contables
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,
@@ -109,19 +108,29 @@ namespace GestionIntegral.Api.Services.Contables
};
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
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.");
// Afectar Saldo
// Si TipoMovimiento es "Recibido", el monto DISMINUYE la deuda del distribuidor (monto positivo para el servicio de saldo).
// Si TipoMovimiento es "Realizado" (empresa paga a distribuidor), el monto AUMENTA la deuda (monto negativo para el servicio de saldo).
decimal montoAjusteSaldo = createDto.TipoMovimiento == "Recibido" ? createDto.Monto : -createDto.Monto;
decimal montoParaSaldo;
if (createDto.TipoMovimiento == "Recibido")
{
montoParaSaldo = -createDto.Monto;
}
else
{
montoParaSaldo = createDto.Monto;
}
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoCreado.IdDistribuidor, pagoCreado.IdEmpresa, montoAjusteSaldo, transaction);
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();
@@ -130,37 +139,63 @@ namespace GestionIntegral.Api.Services.Contables
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
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();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
IDbTransaction? transaction = null;
try
{
var pagoExistente = await _pagoRepo.GetByIdAsync(idPago);
if (pagoExistente == null) return (false, "Pago no encontrado.");
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;
// Calcular la diferencia de monto para ajustar el saldo
decimal montoOriginal = pagoExistente.TipoMovimiento == "Recibido" ? pagoExistente.Monto : -pagoExistente.Monto;
decimal montoNuevo = pagoExistente.TipoMovimiento == "Recibido" ? updateDto.Monto : -updateDto.Monto;
decimal diferenciaAjusteSaldo = montoNuevo - montoOriginal;
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
};
// Actualizar campos permitidos
pagoExistente.Monto = updateDto.Monto;
pagoExistente.IdTipoPago = updateDto.IdTipoPago;
pagoExistente.Detalle = updateDto.Detalle;
var actualizado = await _pagoRepo.UpdateAsync(pagoExistente, idUsuario, transaction);
if (!actualizado) throw new DataException("Error al actualizar el pago.");
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)
{
@@ -172,32 +207,45 @@ namespace GestionIntegral.Api.Services.Contables
_logger.LogInformation("PagoDistribuidor ID {Id} actualizado por Usuario ID {UserId}.", idPago, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Pago no encontrado."); }
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 { }
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();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
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) return (false, "Pago no encontrado.");
// Revertir el efecto en el saldo
// Si fue "Recibido", el saldo disminuyó (montoAjusteSaldo fue +Monto). Al eliminar, revertimos sumando -Monto (o restando +Monto).
// Si fue "Realizado", el saldo aumentó (montoAjusteSaldo fue -Monto). Al eliminar, revertimos sumando +Monto (o restando -Monto).
decimal montoReversion = pagoExistente.TipoMovimiento == "Recibido" ? -pagoExistente.Monto : pagoExistente.Monto;
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.");
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.");
@@ -206,13 +254,20 @@ namespace GestionIntegral.Api.Services.Contables
_logger.LogInformation("PagoDistribuidor ID {Id} eliminado por Usuario ID {UserId}.", idPago, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Pago no encontrado."); }
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 { }
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();
}
}
}
}
}

View File

@@ -0,0 +1,164 @@
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<SaldoService> _logger;
public SaldoService(
ISaldoRepository saldoRepo,
IDistribuidorRepository distribuidorRepo,
ICanillaRepository canillaRepo,
IEmpresaRepository empresaRepo,
DbConnectionFactory connectionFactory,
ILogger<SaldoService> logger)
{
_saldoRepo = saldoRepo;
_distribuidorRepo = distribuidorRepo;
_canillaRepo = canillaRepo;
_empresaRepo = empresaRepo;
_connectionFactory = connectionFactory;
_logger = logger;
}
private async Task<SaldoGestionDto> 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<IEnumerable<SaldoGestionDto>> ObtenerSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter)
{
var saldos = await _saldoRepo.GetSaldosParaGestionAsync(destinoFilter, idDestinoFilter, idEmpresaFilter);
var dtos = new List<SaldoGestionDto>();
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(); }
}
}
}
}