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).
508 lines
27 KiB
C#
508 lines
27 KiB
C#
using GestionIntegral.Api.Data;
|
|
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
|
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
|
using GestionIntegral.Api.Dtos.Distribucion;
|
|
using GestionIntegral.Api.Models.Distribucion;
|
|
using Microsoft.Extensions.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Security.Claims;
|
|
using GestionIntegral.Api.Dtos.Auditoria;
|
|
|
|
namespace GestionIntegral.Api.Services.Distribucion
|
|
{
|
|
public class EntradaSalidaCanillaService : IEntradaSalidaCanillaService
|
|
{
|
|
private readonly IEntradaSalidaCanillaRepository _esCanillaRepository;
|
|
private readonly IPublicacionRepository _publicacionRepository;
|
|
private readonly ICanillaRepository _canillaRepository;
|
|
private readonly IPrecioRepository _precioRepository;
|
|
private readonly IRecargoZonaRepository _recargoZonaRepository;
|
|
private readonly IPorcMonCanillaRepository _porcMonCanillaRepository;
|
|
private readonly IUsuarioRepository _usuarioRepository;
|
|
private readonly DbConnectionFactory _connectionFactory;
|
|
private readonly ILogger<EntradaSalidaCanillaService> _logger;
|
|
|
|
public EntradaSalidaCanillaService(
|
|
IEntradaSalidaCanillaRepository esCanillaRepository,
|
|
IPublicacionRepository publicacionRepository,
|
|
ICanillaRepository canillaRepository,
|
|
IPrecioRepository precioRepository,
|
|
IRecargoZonaRepository recargoZonaRepository,
|
|
IPorcMonCanillaRepository porcMonCanillaRepository,
|
|
IUsuarioRepository usuarioRepository,
|
|
DbConnectionFactory connectionFactory,
|
|
ILogger<EntradaSalidaCanillaService> logger)
|
|
{
|
|
_esCanillaRepository = esCanillaRepository;
|
|
_publicacionRepository = publicacionRepository;
|
|
_canillaRepository = canillaRepository;
|
|
_precioRepository = precioRepository;
|
|
_recargoZonaRepository = recargoZonaRepository;
|
|
_porcMonCanillaRepository = porcMonCanillaRepository;
|
|
_usuarioRepository = usuarioRepository;
|
|
_connectionFactory = connectionFactory;
|
|
_logger = logger;
|
|
}
|
|
|
|
private async Task<EntradaSalidaCanillaDto?> MapToDto(EntradaSalidaCanilla? es)
|
|
{
|
|
if (es == null) return null;
|
|
|
|
var publicacionDataResult = await _publicacionRepository.GetByIdAsync(es.IdPublicacion);
|
|
var canillaDataResult = await _canillaRepository.GetByIdAsync(es.IdCanilla); // Devuelve tupla
|
|
|
|
Publicacion? publicacionEntity = publicacionDataResult.Publicacion; // Componente nullable de la tupla
|
|
Canilla? canillaEntity = canillaDataResult.Canilla; // Componente nullable de la tupla
|
|
|
|
var usuarioLiq = es.UserLiq.HasValue ? await _usuarioRepository.GetByIdAsync(es.UserLiq.Value) : null;
|
|
|
|
decimal montoARendir = await CalcularMontoARendir(es, canillaEntity, publicacionEntity);
|
|
|
|
return new EntradaSalidaCanillaDto
|
|
{
|
|
IdParte = es.IdParte,
|
|
IdPublicacion = es.IdPublicacion,
|
|
NombrePublicacion = publicacionEntity?.Nombre ?? "Pub. Desconocida",
|
|
IdCanilla = es.IdCanilla,
|
|
NomApeCanilla = canillaEntity?.NomApe ?? "Can. Desconocido",
|
|
CanillaEsAccionista = canillaEntity?.Accionista ?? false,
|
|
Fecha = es.Fecha.ToString("yyyy-MM-dd"),
|
|
CantSalida = es.CantSalida,
|
|
CantEntrada = es.CantEntrada,
|
|
Vendidos = es.CantSalida - es.CantEntrada,
|
|
Observacion = es.Observacion,
|
|
Liquidado = es.Liquidado,
|
|
FechaLiquidado = es.FechaLiquidado?.ToString("yyyy-MM-dd"),
|
|
UserLiq = es.UserLiq,
|
|
NombreUserLiq = usuarioLiq != null ? $"{usuarioLiq.Nombre} {usuarioLiq.Apellido}" : null,
|
|
MontoARendir = montoARendir,
|
|
PrecioUnitarioAplicado = (await _precioRepository.GetByIdAsync(es.IdPrecio))?.Lunes ?? 0,
|
|
RecargoAplicado = es.IdRecargo > 0 ? (await _recargoZonaRepository.GetByIdAsync(es.IdRecargo))?.Valor ?? 0 : 0,
|
|
PorcentajeOMontoCanillaAplicado = es.IdPorcMon > 0 ? (await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon))?.PorcMon ?? 0 : 0,
|
|
EsPorcentajeCanilla = es.IdPorcMon > 0 ? (await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon))?.EsPorcentaje ?? false : false
|
|
};
|
|
}
|
|
|
|
private async Task<decimal> CalcularMontoARendir(EntradaSalidaCanilla es, Canilla? canilla, Publicacion? publicacion) // Acepta nullable Canilla y Publicacion
|
|
{
|
|
if (es.CantSalida - es.CantEntrada <= 0) return 0;
|
|
|
|
var precioConfig = await _precioRepository.GetByIdAsync(es.IdPrecio);
|
|
if (precioConfig == null)
|
|
{
|
|
_logger.LogError("Configuración de precio ID {IdPrecio} no encontrada para movimiento ID {IdParte}.", es.IdPrecio, es.IdParte);
|
|
throw new InvalidOperationException($"Configuración de precio ID {es.IdPrecio} no encontrada para el movimiento.");
|
|
}
|
|
|
|
decimal precioDia = 0;
|
|
DayOfWeek diaSemana = es.Fecha.DayOfWeek;
|
|
switch (diaSemana)
|
|
{
|
|
case DayOfWeek.Monday: precioDia = precioConfig.Lunes ?? 0; break;
|
|
case DayOfWeek.Tuesday: precioDia = precioConfig.Martes ?? 0; break;
|
|
case DayOfWeek.Wednesday: precioDia = precioConfig.Miercoles ?? 0; break;
|
|
case DayOfWeek.Thursday: precioDia = precioConfig.Jueves ?? 0; break;
|
|
case DayOfWeek.Friday: precioDia = precioConfig.Viernes ?? 0; break;
|
|
case DayOfWeek.Saturday: precioDia = precioConfig.Sabado ?? 0; break;
|
|
case DayOfWeek.Sunday: precioDia = precioConfig.Domingo ?? 0; break;
|
|
}
|
|
|
|
decimal valorRecargo = 0;
|
|
if (es.IdRecargo > 0)
|
|
{
|
|
var recargoConfig = await _recargoZonaRepository.GetByIdAsync(es.IdRecargo);
|
|
if (recargoConfig != null) valorRecargo = recargoConfig.Valor;
|
|
}
|
|
|
|
decimal precioFinalUnitario = precioDia + valorRecargo;
|
|
int cantidadVendida = es.CantSalida - es.CantEntrada;
|
|
|
|
if (canilla != null && canilla.Accionista && es.IdPorcMon > 0) // Check null para canilla
|
|
{
|
|
var porcMonConfig = await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon);
|
|
if (porcMonConfig != null)
|
|
{
|
|
if (porcMonConfig.EsPorcentaje)
|
|
{
|
|
return Math.Round((precioFinalUnitario * cantidadVendida * porcMonConfig.PorcMon) / 100, 2);
|
|
}
|
|
else
|
|
{
|
|
return Math.Round(cantidadVendida * porcMonConfig.PorcMon, 2);
|
|
}
|
|
}
|
|
}
|
|
return Math.Round(precioFinalUnitario * cantidadVendida, 2);
|
|
}
|
|
|
|
public async Task<IEnumerable<EntradaSalidaCanillaDto>> ObtenerTodosAsync(
|
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
|
int? idPublicacion, int? idCanilla, bool? liquidados, bool? incluirNoLiquidados)
|
|
{
|
|
bool? filtroLiquidadoFinal = null;
|
|
if (liquidados.HasValue)
|
|
{
|
|
filtroLiquidadoFinal = liquidados.Value;
|
|
}
|
|
else
|
|
{
|
|
if (incluirNoLiquidados.HasValue && !incluirNoLiquidados.Value)
|
|
{
|
|
filtroLiquidadoFinal = true;
|
|
}
|
|
}
|
|
|
|
var movimientos = await _esCanillaRepository.GetAllAsync(fechaDesde, fechaHasta, idPublicacion, idCanilla, filtroLiquidadoFinal);
|
|
var dtos = new List<EntradaSalidaCanillaDto>();
|
|
foreach (var mov in movimientos)
|
|
{
|
|
var dto = await MapToDto(mov);
|
|
if (dto != null) dtos.Add(dto);
|
|
}
|
|
return dtos;
|
|
}
|
|
|
|
public async Task<EntradaSalidaCanillaDto?> ObtenerPorIdAsync(int idParte)
|
|
{
|
|
var movimiento = await _esCanillaRepository.GetByIdAsync(idParte);
|
|
return await MapToDto(movimiento);
|
|
}
|
|
|
|
public async Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaCanillaDto updateDto, int idUsuario)
|
|
{
|
|
using var connection = _connectionFactory.CreateConnection();
|
|
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
|
|
using var transaction = connection.BeginTransaction();
|
|
try
|
|
{
|
|
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte);
|
|
if (esExistente == null) return (false, "Movimiento no encontrado.");
|
|
if (esExistente.Liquidado) return (false, "No se puede modificar un movimiento ya liquidado.");
|
|
|
|
esExistente.CantSalida = updateDto.CantSalida;
|
|
esExistente.CantEntrada = updateDto.CantEntrada;
|
|
esExistente.Observacion = updateDto.Observacion;
|
|
|
|
var actualizado = await _esCanillaRepository.UpdateAsync(esExistente, idUsuario, transaction);
|
|
if (!actualizado) throw new DataException("Error al actualizar el movimiento.");
|
|
|
|
transaction.Commit();
|
|
_logger.LogInformation("Movimiento Canillita ID {Id} actualizado por Usuario ID {UserId}.", idParte, idUsuario);
|
|
return (true, null);
|
|
}
|
|
catch (KeyNotFoundException) { if (transaction.Connection != null) try { transaction.Rollback(); } catch { } return (false, "Movimiento no encontrado."); }
|
|
catch (Exception ex)
|
|
{
|
|
if (transaction.Connection != null) try { transaction.Rollback(); } catch { }
|
|
_logger.LogError(ex, "Error ActualizarMovimientoAsync Canillita ID: {Id}", idParte);
|
|
return (false, $"Error interno: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
if (connection?.State == ConnectionState.Open)
|
|
{
|
|
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario, ClaimsPrincipal userPrincipal)
|
|
{
|
|
// Helper interno para verificar permisos desde el ClaimsPrincipal proporcionado
|
|
bool TienePermisoEspecifico(string codAcc) => userPrincipal.IsInRole("SuperAdmin") || userPrincipal.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
|
|
|
using var connection = _connectionFactory.CreateConnection();
|
|
IDbTransaction? transaction = null;
|
|
|
|
try
|
|
{
|
|
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync();
|
|
else if (connection.State == ConnectionState.Closed) connection.Open();
|
|
|
|
transaction = connection.BeginTransaction();
|
|
|
|
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte); // Obtener el estado actual
|
|
if (esExistente == null)
|
|
{
|
|
if (transaction?.Connection != null) transaction.Rollback();
|
|
return (false, "Movimiento no encontrado.");
|
|
}
|
|
|
|
if (esExistente.Liquidado)
|
|
{
|
|
if (!TienePermisoEspecifico("MC006")) // <--- AQUÍ ESTÁ LA VERIFICACIÓN
|
|
{
|
|
if (transaction?.Connection != null) transaction.Rollback();
|
|
return (false, "No tiene permiso para eliminar movimientos ya liquidados. Se requiere permiso especial (MC006) o ser SuperAdmin.");
|
|
}
|
|
_logger.LogWarning("Usuario ID {IdUsuario} está eliminando un movimiento LIQUIDADO (IDParte: {IdParte}). Permiso MC006 verificado.", idUsuario, idParte);
|
|
}
|
|
// Si no está liquidado, el permiso MC004 ya fue verificado en el controlador (o debería serlo).
|
|
|
|
var eliminado = await _esCanillaRepository.DeleteAsync(idParte, idUsuario, transaction); // Ahora esto no lanzará la excepción por liquidado
|
|
if (!eliminado)
|
|
{
|
|
// No es necesario hacer rollback aquí si DeleteAsync lanza una excepción,
|
|
// ya que el bloque catch lo manejará. Si DeleteAsync devuelve false sin lanzar,
|
|
// entonces sí sería necesario un rollback.
|
|
if (transaction?.Connection != null) transaction.Rollback();
|
|
throw new DataException("Error al eliminar el movimiento desde el repositorio.");
|
|
}
|
|
|
|
if (transaction?.Connection != null) transaction.Commit();
|
|
_logger.LogInformation("Movimiento Canillita ID {IdParte} eliminado por Usuario ID {IdUsuario}.", idParte, idUsuario);
|
|
return (true, null);
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
if (transaction?.Connection != null) try { transaction.Rollback(); } catch (Exception exR) { _logger.LogError(exR, "Rollback fallido KeyNotFoundException."); }
|
|
return (false, "Movimiento no encontrado.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (transaction?.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
|
|
_logger.LogError(ex, "Error EliminarMovimientoAsync Canillita ID: {IdParte}", idParte);
|
|
return (false, $"Error interno: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
if (transaction != null) transaction.Dispose();
|
|
if (connection?.State == ConnectionState.Open)
|
|
{
|
|
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task<(bool Exito, string? Error)> LiquidarMovimientosAsync(LiquidarMovimientosCanillaRequestDto liquidarDto, int idUsuarioLiquidador)
|
|
{
|
|
if (liquidarDto.IdsPartesALiquidar == null || !liquidarDto.IdsPartesALiquidar.Any())
|
|
return (false, "No se especificaron movimientos para liquidar.");
|
|
|
|
using var connection = _connectionFactory.CreateConnection();
|
|
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
|
|
using var transaction = connection.BeginTransaction();
|
|
try
|
|
{
|
|
bool liquidacionExitosa = await _esCanillaRepository.LiquidarAsync(liquidarDto.IdsPartesALiquidar, liquidarDto.FechaLiquidacion.Date, idUsuarioLiquidador, transaction);
|
|
|
|
if (!liquidacionExitosa)
|
|
{
|
|
_logger.LogWarning("Liquidación de movimientos de canillita pudo no haber afectado a todos los IDs solicitados. IDs: {Ids}", string.Join(",", liquidarDto.IdsPartesALiquidar));
|
|
}
|
|
|
|
if (transaction.Connection != null) transaction.Commit();
|
|
_logger.LogInformation("Movimientos de Canillita liquidados. Ids: {Ids} por Usuario ID {UserId}.", string.Join(",", liquidarDto.IdsPartesALiquidar), idUsuarioLiquidador);
|
|
return (true, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (transaction.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
|
|
_logger.LogError(ex, "Error al liquidar movimientos de canillita.");
|
|
return (false, $"Error interno al liquidar movimientos: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
if (connection?.State == ConnectionState.Open)
|
|
{
|
|
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task<(IEnumerable<EntradaSalidaCanillaDto>? MovimientosCreados, string? Error)> CrearMovimientosEnLoteAsync(CreateBulkEntradaSalidaCanillaDto createBulkDto, int idUsuario)
|
|
{
|
|
var canillaDataResult = await _canillaRepository.GetByIdAsync(createBulkDto.IdCanilla);
|
|
Canilla? canillaActual = canillaDataResult.Canilla;
|
|
if (canillaActual == null) return (null, "Canillita no válido.");
|
|
if (canillaActual.Baja) return (null, "El canillita está dado de baja.");
|
|
|
|
List<EntradaSalidaCanilla> movimientosCreadosEntidades = new List<EntradaSalidaCanilla>();
|
|
List<string> erroresItems = new List<string>();
|
|
|
|
using var connection = _connectionFactory.CreateConnection();
|
|
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
|
|
using var transaction = connection.BeginTransaction();
|
|
|
|
try
|
|
{
|
|
foreach (var itemDto in createBulkDto.Items.Where(i => i.IdPublicacion > 0))
|
|
{
|
|
if (await _esCanillaRepository.ExistsByPublicacionCanillaFechaAsync(itemDto.IdPublicacion, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, transaction: transaction))
|
|
{
|
|
var pubInfo = await _publicacionRepository.GetByIdSimpleAsync(itemDto.IdPublicacion);
|
|
erroresItems.Add($"Ya existe un registro para la publicación '{pubInfo?.Nombre ?? itemDto.IdPublicacion.ToString()}' para {canillaActual.NomApe} en la fecha {createBulkDto.Fecha:dd/MM/yyyy}.");
|
|
}
|
|
}
|
|
if (erroresItems.Any())
|
|
{
|
|
if (transaction.Connection != null) transaction.Rollback();
|
|
return (null, string.Join(" ", erroresItems));
|
|
}
|
|
|
|
foreach (var itemDto in createBulkDto.Items)
|
|
{
|
|
if (itemDto.IdPublicacion == 0 && itemDto.CantSalida == 0 && itemDto.CantEntrada == 0 && string.IsNullOrWhiteSpace(itemDto.Observacion)) continue;
|
|
if (itemDto.IdPublicacion == 0)
|
|
{
|
|
if (itemDto.CantSalida > 0 || itemDto.CantEntrada > 0 || !string.IsNullOrWhiteSpace(itemDto.Observacion))
|
|
{
|
|
erroresItems.Add($"Falta seleccionar la publicación para una de las líneas con cantidades/observación.");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
var publicacionItem = await _publicacionRepository.GetByIdSimpleAsync(itemDto.IdPublicacion);
|
|
bool noEsValidaONoHabilitada = false;
|
|
if (publicacionItem == null)
|
|
{
|
|
noEsValidaONoHabilitada = true;
|
|
}
|
|
else
|
|
{
|
|
// Si Habilitada es bool? y NULL significa que toma el DEFAULT 1 (true) de la BD
|
|
// entonces solo consideramos error si es explícitamente false.
|
|
if (publicacionItem.Habilitada.HasValue && publicacionItem.Habilitada.Value == false)
|
|
{
|
|
noEsValidaONoHabilitada = true;
|
|
}
|
|
// Si publicacionItem.Habilitada es null o true, noEsValidaONoHabilitada permanece false.
|
|
}
|
|
|
|
if (noEsValidaONoHabilitada)
|
|
{
|
|
erroresItems.Add($"Publicación ID {itemDto.IdPublicacion} no es válida o no está habilitada.");
|
|
continue;
|
|
}
|
|
|
|
var precioActivo = await _precioRepository.GetActiveByPublicacionAndDateAsync(itemDto.IdPublicacion, createBulkDto.Fecha.Date, transaction);
|
|
if (precioActivo == null)
|
|
{
|
|
string nombrePubParaError = publicacionItem?.Nombre ?? $"ID {itemDto.IdPublicacion}";
|
|
erroresItems.Add($"No hay precio definido para '{nombrePubParaError}' en {createBulkDto.Fecha:dd/MM/yyyy}.");
|
|
continue;
|
|
}
|
|
|
|
RecargoZona? recargoActivo = null;
|
|
// Aquí usamos canillaActual! porque ya verificamos que no es null al inicio del método.
|
|
if (canillaActual!.IdZona > 0)
|
|
{
|
|
recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(itemDto.IdPublicacion, canillaActual.IdZona, createBulkDto.Fecha.Date, transaction);
|
|
}
|
|
|
|
PorcMonCanilla? porcMonActivo = null;
|
|
// Aquí usamos canillaActual! porque ya verificamos que no es null al inicio del método.
|
|
if (canillaActual!.Accionista)
|
|
{
|
|
porcMonActivo = await _porcMonCanillaRepository.GetActiveByPublicacionCanillaAndDateAsync(itemDto.IdPublicacion, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, transaction);
|
|
if (porcMonActivo == null)
|
|
{
|
|
// Dentro de este bloque, canillaActual.NomApe es seguro porque Accionista era true.
|
|
string nombreCanParaError = canillaActual.NomApe;
|
|
string nombrePubParaError = publicacionItem?.Nombre ?? $"Publicación ID {itemDto.IdPublicacion}";
|
|
erroresItems.Add($"'{nombreCanParaError}' es accionista pero no tiene %/monto para '{nombrePubParaError}' en {createBulkDto.Fecha:dd/MM/yyyy}.");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
var nuevoES = new EntradaSalidaCanilla
|
|
{
|
|
IdPublicacion = itemDto.IdPublicacion,
|
|
IdCanilla = createBulkDto.IdCanilla,
|
|
Fecha = createBulkDto.Fecha.Date,
|
|
CantSalida = itemDto.CantSalida,
|
|
CantEntrada = itemDto.CantEntrada,
|
|
Observacion = itemDto.Observacion,
|
|
IdPrecio = precioActivo.IdPrecio,
|
|
IdRecargo = recargoActivo?.IdRecargo ?? 0,
|
|
IdPorcMon = porcMonActivo?.IdPorcMon ?? 0,
|
|
Liquidado = false,
|
|
FechaLiquidado = null,
|
|
UserLiq = null
|
|
};
|
|
|
|
var esCreada = await _esCanillaRepository.CreateAsync(nuevoES, idUsuario, transaction);
|
|
if (esCreada == null) throw new DataException($"Error al registrar movimiento para Publicación ID {itemDto.IdPublicacion}.");
|
|
movimientosCreadosEntidades.Add(esCreada);
|
|
}
|
|
|
|
if (erroresItems.Any())
|
|
{
|
|
if (transaction.Connection != null) transaction.Rollback();
|
|
return (null, string.Join(" ", erroresItems));
|
|
}
|
|
|
|
// CORRECCIÓN PARA CS0019 (línea 394 original):
|
|
bool tieneItemsSignificativos = false;
|
|
if (createBulkDto.Items != null) // Checkear si Items es null antes de llamar a Any()
|
|
{
|
|
tieneItemsSignificativos = createBulkDto.Items.Any(i => i.IdPublicacion > 0 &&
|
|
(i.CantSalida > 0 || i.CantEntrada > 0 || !string.IsNullOrWhiteSpace(i.Observacion)));
|
|
}
|
|
|
|
if (!movimientosCreadosEntidades.Any() && tieneItemsSignificativos)
|
|
{
|
|
if (transaction.Connection != null) transaction.Rollback();
|
|
return (null, "No se pudo procesar ningún ítem válido con datos significativos.");
|
|
}
|
|
|
|
if (transaction.Connection != null) transaction.Commit();
|
|
_logger.LogInformation("Lote de {Count} movimientos Canillita para Canilla ID {IdCanilla} en Fecha {Fecha} creados por Usuario ID {UserId}.",
|
|
movimientosCreadosEntidades.Count, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, idUsuario);
|
|
|
|
var dtosCreados = new List<EntradaSalidaCanillaDto>();
|
|
foreach (var entidad in movimientosCreadosEntidades)
|
|
{
|
|
var dto = await MapToDto(entidad);
|
|
if (dto != null) dtosCreados.Add(dto);
|
|
}
|
|
return (dtosCreados, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (transaction.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
|
|
_logger.LogError(ex, "Error CrearMovimientosEnLoteAsync para Canilla ID {IdCanilla}, Fecha {Fecha}", createBulkDto.IdCanilla, createBulkDto.Fecha);
|
|
return (null, $"Error interno al procesar el lote: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
if (connection?.State == ConnectionState.Open)
|
|
{
|
|
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task<IEnumerable<EntradaSalidaCanillaHistorialDto>> ObtenerHistorialAsync(
|
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
|
int? idUsuarioModifico, string? tipoModificacion,
|
|
int? idParteAfectada)
|
|
{
|
|
// Asumiendo que _esCanillaRepository es tu IEntradaSalidaCanillaRepository
|
|
var historialData = await _esCanillaRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idParteAfectada);
|
|
|
|
return historialData.Select(h => new EntradaSalidaCanillaHistorialDto
|
|
{
|
|
Id_Parte = h.Historial.Id_Parte,
|
|
Id_Publicacion = h.Historial.Id_Publicacion,
|
|
Id_Canilla = h.Historial.Id_Canilla,
|
|
Fecha = h.Historial.Fecha,
|
|
CantSalida = h.Historial.CantSalida,
|
|
CantEntrada = h.Historial.CantEntrada,
|
|
Id_Precio = h.Historial.Id_Precio,
|
|
Id_Recargo = h.Historial.Id_Recargo,
|
|
Id_PorcMon = h.Historial.Id_PorcMon,
|
|
Observacion = h.Historial.Observacion,
|
|
Id_Usuario = h.Historial.Id_Usuario,
|
|
NombreUsuarioModifico = h.NombreUsuarioModifico,
|
|
FechaMod = h.Historial.FechaMod,
|
|
TipoMod = h.Historial.TipoMod
|
|
}).ToList();
|
|
}
|
|
}
|
|
} |