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 _log; public EntradaSalidaCanillaRepository(DbConnectionFactory cf, ILogger 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> 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(sqlBuilder.ToString(), parameters); } catch (Exception ex) { _log.LogError(ex, "Error al obtener Entradas/Salidas de Canillitas."); return Enumerable.Empty(); } } public async Task GetByIdAsync(int idParte) { var sql = SelectQueryBase() + " WHERE Id_Parte = @IdParteParam"; try { using var connection = _cf.CreateConnection(); return await connection.QuerySingleOrDefaultAsync(sql, new { IdParteParam = idParte }); } catch (Exception ex) { _log.LogError(ex, "Error al obtener EntradaSalidaCanilla por ID: {IdParte}", idParte); return null; } } public async Task> GetByIdsAsync(IEnumerable idsPartes, IDbTransaction? transaction = null) { if (idsPartes == null || !idsPartes.Any()) return Enumerable.Empty(); var sql = SelectQueryBase() + " WHERE Id_Parte IN @IdsPartesParam"; var cn = transaction?.Connection ?? _cf.CreateConnection(); bool ownConnection = transaction == null; IEnumerable result = Enumerable.Empty(); try { if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); result = await cn.QueryAsync(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 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(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 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(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 UpdateAsync(EntradaSalidaCanilla esAActualizar, int idUsuario, IDbTransaction transaction, string tipoMod = "Actualizada") { var actual = await transaction.Connection!.QuerySingleOrDefaultAsync(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 LiquidarAsync(IEnumerable 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 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; } } }