Diseño de un AuditoriaController con un patrón para añadir endpoints de historial para diferentes entidades. Implementación de la lógica de servicio y repositorio para obtener datos de las tablas _H para: Usuarios (gral_Usuarios_H) Pagos de Distribuidores (cue_PagosDistribuidor_H) Notas de Crédito/Débito (cue_CreditosDebitos_H) Entradas/Salidas de Distribuidores (dist_EntradasSalidas_H) Entradas/Salidas de Canillitas (dist_EntradasSalidasCanillas_H) Novedades de Canillitas (dist_dtNovedadesCanillas_H) Ajustes Manuales de Saldo (cue_SaldoAjustesHistorial) Tipos de Pago (cue_dtTipopago_H) Canillitas (Maestro) (dist_dtCanillas_H) Distribuidores (Maestro) (dist_dtDistribuidores_H) Empresas (Maestro) (dist_dtEmpresas_H) DTOs específicos para cada tipo de historial, incluyendo NombreUsuarioModifico. Frontend: Servicio auditoriaService.ts con métodos para llamar a cada endpoint de historial. Página AuditoriaGeneralPage.tsx con: Selector de "Tipo de Entidad a Auditar". Filtros comunes (Fechas, Usuario Modificador, Tipo de Modificación, ID Entidad). Un DataGrid que muestra las columnas dinámicamente según el tipo de entidad seleccionada. Lógica para cargar los datos correspondientes. DTOs de historial en TypeScript. Actualizaciones en AppRoutes.tsx y MainLayout.tsx para la nueva sección de Auditoría (restringida a SuperAdmin).
308 lines
14 KiB
C#
308 lines
14 KiB
C#
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 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<NotaCreditoDebitoService> _logger;
|
|
|
|
public NotaCreditoDebitoService(
|
|
INotaCreditoDebitoRepository notaRepo,
|
|
IDistribuidorRepository distribuidorRepo,
|
|
ICanillaRepository canillaRepo,
|
|
IEmpresaRepository empresaRepo,
|
|
ISaldoRepository saldoRepo,
|
|
DbConnectionFactory connectionFactory,
|
|
ILogger<NotaCreditoDebitoService> logger)
|
|
{
|
|
_notaRepo = notaRepo;
|
|
_distribuidorRepo = distribuidorRepo;
|
|
_canillaRepo = canillaRepo;
|
|
_empresaRepo = empresaRepo;
|
|
_saldoRepo = saldoRepo;
|
|
_connectionFactory = connectionFactory;
|
|
_logger = logger;
|
|
}
|
|
|
|
private async Task<NotaCreditoDebitoDto> 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<IEnumerable<NotaCreditoDebitoDto>> 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<NotaCreditoDebitoDto>();
|
|
foreach (var nota in notas)
|
|
{
|
|
dtos.Add(await MapToDto(nota));
|
|
}
|
|
return dtos;
|
|
}
|
|
|
|
public async Task<NotaCreditoDebitoDto?> 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task<IEnumerable<NotaCreditoDebitoHistorialDto>> ObtenerHistorialAsync(
|
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
|
int? idUsuarioModifico, string? tipoModificacion,
|
|
int? idNotaAfectada)
|
|
{
|
|
var historialData = await _notaRepo.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idNotaAfectada);
|
|
|
|
return historialData.Select(h => new NotaCreditoDebitoHistorialDto
|
|
{
|
|
Id_Nota = h.Historial.Id_Nota,
|
|
Destino = h.Historial.Destino,
|
|
Id_Destino = h.Historial.Id_Destino,
|
|
Referencia = h.Historial.Referencia,
|
|
Tipo = h.Historial.Tipo,
|
|
Fecha = h.Historial.Fecha, // Fecha original de la nota
|
|
Monto = h.Historial.Monto,
|
|
Observaciones = h.Historial.Observaciones,
|
|
Id_Empresa = h.Historial.Id_Empresa,
|
|
Id_Usuario = h.Historial.Id_Usuario,
|
|
NombreUsuarioModifico = h.NombreUsuarioModifico,
|
|
FechaMod = h.Historial.FechaMod, // Fecha de la auditoría
|
|
TipoMod = h.Historial.TipoMod
|
|
}).ToList();
|
|
}
|
|
}
|
|
} |