Files
GestionIntegralWeb/Backend/GestionIntegral.Api/Data/Repositories/Distribucion/EntradaSalidaCanillaRepository.cs
eldiadmolinari 437b1e8864 Backend:
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).
2025-06-09 19:37:07 -03:00

339 lines
18 KiB
C#

using Dapper;
using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Data.Repositories.Distribucion
{
public class EntradaSalidaCanillaRepository : IEntradaSalidaCanillaRepository
{
private readonly DbConnectionFactory _cf;
private readonly ILogger<EntradaSalidaCanillaRepository> _log;
public EntradaSalidaCanillaRepository(DbConnectionFactory cf, ILogger<EntradaSalidaCanillaRepository> log)
{
_cf = cf;
_log = log;
}
private string SelectQueryBase() => @"
SELECT
Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Canilla AS IdCanilla,
Fecha, CantSalida, CantEntrada, Id_Precio AS IdPrecio, Id_Recargo AS IdRecargo,
Id_PorcMon AS IdPorcMon, Observacion, Liquidado, FechaLiquidado, UserLiq
FROM dbo.dist_EntradasSalidasCanillas";
public async Task<IEnumerable<EntradaSalidaCanilla>> GetAllAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idPublicacion, int? idCanilla, bool? liquidados)
{
var sqlBuilder = new StringBuilder(SelectQueryBase());
sqlBuilder.Append(" WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND Fecha >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND Fecha <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date); }
if (idPublicacion.HasValue) { sqlBuilder.Append(" AND Id_Publicacion = @IdPublicacionParam"); parameters.Add("IdPublicacionParam", idPublicacion.Value); }
if (idCanilla.HasValue) { sqlBuilder.Append(" AND Id_Canilla = @IdCanillaParam"); parameters.Add("IdCanillaParam", idCanilla.Value); }
if (liquidados.HasValue) { sqlBuilder.Append(" AND Liquidado = @LiquidadoParam"); parameters.Add("LiquidadoParam", liquidados.Value); }
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Canilla, Id_Publicacion, Id_Parte DESC;");
try
{
using var connection = _cf.CreateConnection();
return await connection.QueryAsync<EntradaSalidaCanilla>(sqlBuilder.ToString(), parameters);
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener Entradas/Salidas de Canillitas.");
return Enumerable.Empty<EntradaSalidaCanilla>();
}
}
public async Task<EntradaSalidaCanilla?> GetByIdAsync(int idParte)
{
var sql = SelectQueryBase() + " WHERE Id_Parte = @IdParteParam";
try
{
using var connection = _cf.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<EntradaSalidaCanilla>(sql, new { IdParteParam = idParte });
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener EntradaSalidaCanilla por ID: {IdParte}", idParte);
return null;
}
}
public async Task<IEnumerable<EntradaSalidaCanilla>> GetByIdsAsync(IEnumerable<int> idsPartes, IDbTransaction? transaction = null)
{
if (idsPartes == null || !idsPartes.Any()) return Enumerable.Empty<EntradaSalidaCanilla>();
var sql = SelectQueryBase() + " WHERE Id_Parte IN @IdsPartesParam";
var cn = transaction?.Connection ?? _cf.CreateConnection();
bool ownConnection = transaction == null;
IEnumerable<EntradaSalidaCanilla> result = Enumerable.Empty<EntradaSalidaCanilla>();
try
{
if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
result = await cn.QueryAsync<EntradaSalidaCanilla>(sql, new { IdsPartesParam = idsPartes }, transaction);
}
finally
{
if (ownConnection && cn.State == ConnectionState.Open && cn is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync();
if (ownConnection) (cn as IDisposable)?.Dispose();
}
return result;
}
public async Task<bool> ExistsByPublicacionCanillaFechaAsync(int idPublicacion, int idCanilla, DateTime fecha, IDbTransaction? transaction = null, int? excludeIdParte = null)
{
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_EntradasSalidasCanillas WHERE Id_Publicacion = @IdPubParam AND Id_Canilla = @IdCanParam AND Fecha = @FechaParam");
var parameters = new DynamicParameters();
parameters.Add("IdPubParam", idPublicacion);
parameters.Add("IdCanParam", idCanilla);
parameters.Add("FechaParam", fecha.Date);
if (excludeIdParte.HasValue)
{
sqlBuilder.Append(" AND Id_Parte != @ExcludeIdParteParam");
parameters.Add("ExcludeIdParteParam", excludeIdParte.Value);
}
var connection = transaction?.Connection ?? _cf.CreateConnection();
bool ownConnection = transaction == null;
bool result = false;
try
{
if (ownConnection && connection.State == ConnectionState.Closed && connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
result = await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters, transaction);
}
catch (Exception ex)
{
_log.LogError(ex, "Error en ExistsByPublicacionCanillaFechaAsync.");
return true; // Asumir que existe en caso de error
}
finally
{
if (ownConnection && connection.State == ConnectionState.Open && connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync();
if (ownConnection) (connection as IDisposable)?.Dispose();
}
return result;
}
public async Task<EntradaSalidaCanilla?> CreateAsync(EntradaSalidaCanilla nuevoES, int idUsuario, IDbTransaction transaction)
{
const string sqlInsert = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas
(Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Liquidado, FechaLiquidado, UserLiq)
OUTPUT INSERTED.Id_Parte AS IdParte, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Canilla AS IdCanilla, INSERTED.Fecha,
INSERTED.CantSalida, INSERTED.CantEntrada, INSERTED.Id_Precio AS IdPrecio, INSERTED.Id_Recargo AS IdRecargo,
INSERTED.Id_PorcMon AS IdPorcMon, INSERTED.Observacion, INSERTED.Liquidado, INSERTED.FechaLiquidado, INSERTED.UserLiq
VALUES (@IdPublicacion, @IdCanilla, @Fecha, @CantSalida, @CantEntrada, @IdPrecio, @IdRecargo, @IdPorcMon, @Observacion, @Liquidado, @FechaLiquidado, @UserLiq);";
var inserted = await transaction.Connection!.QuerySingleAsync<EntradaSalidaCanilla>(sqlInsert, nuevoES, transaction);
if (inserted == null || inserted.IdParte == 0) throw new DataException("Error al crear E/S Canilla o ID no generado.");
const string sqlHistorico = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
// Liquidado, FechaLiquidado, UserLiq no van al historial de creación, sino al de liquidación.
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = inserted.IdParte,
IdPubHist = inserted.IdPublicacion,
IdCanillaHist = inserted.IdCanilla,
FechaHist = inserted.Fecha,
CantSalidaHist = inserted.CantSalida,
CantEntradaHist = inserted.CantEntrada,
IdPrecioHist = inserted.IdPrecio,
IdRecargoHist = inserted.IdRecargo,
IdPorcMonHist = inserted.IdPorcMon,
ObsHist = inserted.Observacion,
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Creada"
}, transaction);
return inserted;
}
public async Task<bool> UpdateAsync(EntradaSalidaCanilla esAActualizar, int idUsuario, IDbTransaction transaction, string tipoMod = "Actualizada")
{
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<EntradaSalidaCanilla>(SelectQueryBase() + " WHERE Id_Parte = @IdParteParam",
new { IdParteParam = esAActualizar.IdParte }, transaction);
if (actual == null) throw new KeyNotFoundException("Registro E/S Canilla no encontrado.");
const string sqlUpdate = @"
UPDATE dbo.dist_EntradasSalidasCanillas SET
CantSalida = @CantSalida, CantEntrada = @CantEntrada, Observacion = @Observacion,
Liquidado = @Liquidado, FechaLiquidado = @FechaLiquidado, UserLiq = @UserLiq,
Id_Precio = @IdPrecio, Id_Recargo = @IdRecargo, Id_PorcMon = @IdPorcMon
-- No se permite cambiar Publicacion, Canilla, Fecha directamente aquí.
WHERE Id_Parte = @IdParte;";
const string sqlHistorico = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = actual.IdParte,
IdPubHist = actual.IdPublicacion,
IdCanillaHist = actual.IdCanilla,
FechaHist = actual.Fecha,
CantSalidaHist = actual.CantSalida,
CantEntradaHist = actual.CantEntrada,
IdPrecioHist = actual.IdPrecio,
IdRecargoHist = actual.IdRecargo,
IdPorcMonHist = actual.IdPorcMon,
ObsHist = actual.Observacion, // Valores ANTERIORES
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = tipoMod
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, esAActualizar, transaction);
return rowsAffected == 1;
}
public async Task<bool> LiquidarAsync(IEnumerable<int> idsPartes, DateTime fechaLiquidacion, int idUsuarioLiquidador, IDbTransaction transaction)
{
// Primero, obtener los registros actuales para el historial
var movimientosALiquidar = await GetByIdsAsync(idsPartes, transaction);
if (!movimientosALiquidar.Any() || movimientosALiquidar.Count() != idsPartes.Distinct().Count())
{
_log.LogWarning("Intento de liquidar IdsPartes no encontrados o inconsistentes.");
return false; // O lanzar excepción
}
const string sqlUpdate = @"
UPDATE dbo.dist_EntradasSalidasCanillas SET
Liquidado = 1, FechaLiquidado = @FechaLiquidacionParam, UserLiq = @UserLiqParam
WHERE Id_Parte = @IdParteParam AND Liquidado = 0;"; // Solo liquidar los no liquidados
const string sqlHistorico = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
int totalRowsAffected = 0;
foreach (var mov in movimientosALiquidar)
{
if (mov.Liquidado) continue; // Ya estaba liquidado, no hacer nada ni registrar historial
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = mov.IdParte,
IdPubHist = mov.IdPublicacion,
IdCanillaHist = mov.IdCanilla,
FechaHist = mov.Fecha,
CantSalidaHist = mov.CantSalida,
CantEntradaHist = mov.CantEntrada,
IdPrecioHist = mov.IdPrecio,
IdRecargoHist = mov.IdRecargo,
IdPorcMonHist = mov.IdPorcMon,
ObsHist = mov.Observacion,
IdUsuarioHist = idUsuarioLiquidador,
FechaModHist = DateTime.Now,
TipoModHist = "Liquidada"
}, transaction);
var rows = await transaction.Connection!.ExecuteAsync(sqlUpdate,
new { FechaLiquidacionParam = fechaLiquidacion.Date, UserLiqParam = idUsuarioLiquidador, IdParteParam = mov.IdParte },
transaction);
totalRowsAffected += rows;
}
// Se considera éxito si al menos una fila fue afectada (o si todas ya estaban liquidadas y no hubo errores)
// Si se pasaron IDs que no existen, GetByIdsAsync ya devolvería menos elementos.
return totalRowsAffected >= 0;
}
public async Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction)
{
var actual = await GetByIdAsync(idParte); // Sigue siendo útil para el historial
if (actual == null) throw new KeyNotFoundException("Registro E/S Canilla no encontrado para eliminar.");
const string sqlDelete = "DELETE FROM dbo.dist_EntradasSalidasCanillas WHERE Id_Parte = @IdParteParam";
const string sqlHistorico = @"
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
{
IdParteHist = actual.IdParte,
IdPubHist = actual.IdPublicacion,
IdCanillaHist = actual.IdCanilla,
FechaHist = actual.Fecha,
CantSalidaHist = actual.CantSalida,
CantEntradaHist = actual.CantEntrada,
IdPrecioHist = actual.IdPrecio,
IdRecargoHist = actual.IdRecargo,
IdPorcMonHist = actual.IdPorcMon,
ObsHist = actual.Observacion,
IdUsuarioHist = idUsuario,
FechaModHist = DateTime.Now,
TipoModHist = "Eliminada"
}, transaction);
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction);
return rowsAffected == 1;
}
public async Task<IEnumerable<(EntradaSalidaCanillaHistorico Historial, string NombreUsuarioModifico)>> GetHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idParteOriginal)
{
using var connection = _cf.CreateConnection();
var sqlBuilder = new StringBuilder(@"
SELECT
h.Id_Parte, h.Id_Publicacion, h.Id_Canilla, h.Fecha,
h.CantSalida, h.CantEntrada, h.Id_Precio, h.Id_Recargo, h.Id_PorcMon, h.Observacion,
h.Id_Usuario, h.FechaMod, h.TipoMod,
u.Nombre + ' ' + u.Apellido AS NombreUsuarioModifico
FROM dbo.dist_EntradasSalidasCanillas_H h
JOIN dbo.gral_Usuarios u ON h.Id_Usuario = u.Id
WHERE 1=1");
var parameters = new DynamicParameters();
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); }
if (idUsuarioModifico.HasValue) { sqlBuilder.Append(" AND h.Id_Usuario = @IdUsuarioModificoParam"); parameters.Add("IdUsuarioModificoParam", idUsuarioModifico.Value); }
if (!string.IsNullOrWhiteSpace(tipoModificacion)) { sqlBuilder.Append(" AND h.TipoMod = @TipoModParam"); parameters.Add("TipoModParam", tipoModificacion); }
if (idParteOriginal.HasValue) { sqlBuilder.Append(" AND h.Id_Parte = @IdParteOriginalParam"); parameters.Add("IdParteOriginalParam", idParteOriginal.Value); }
sqlBuilder.Append(" ORDER BY h.FechaMod DESC;");
try
{
var result = await connection.QueryAsync<EntradaSalidaCanillaHistorico, string, (EntradaSalidaCanillaHistorico, string)>(
sqlBuilder.ToString(),
(hist, userName) => (hist, userName),
parameters,
splitOn: "NombreUsuarioModifico"
);
return result;
}
catch (Exception ex)
{
_log.LogError(ex, "Error al obtener historial de Entradas/Salidas Canillitas.");
return Enumerable.Empty<(EntradaSalidaCanillaHistorico, string)>();
}
}
}
}