Files
GestionIntegralWeb/Backend/GestionIntegral.Api/Services/Distribucion/PorcMonCanillaService.cs
eldiadmolinari b04a3b99bf 1. Funcionalidad Principal: Auditoría General
Se creó una nueva sección de "Auditoría" en la aplicación, diseñada para ser accedida por SuperAdmins.
Se implementó una página AuditoriaGeneralPage.tsx que actúa como un visor centralizado para el historial de cambios de múltiples entidades del sistema.
2. Backend:
Nuevo Controlador (AuditoriaController.cs): Centraliza los endpoints para obtener datos de las tablas de historial (_H).
Servicios y Repositorios Extendidos:
Se añadieron métodos GetHistorialAsync y ObtenerHistorialAsync a las capas de repositorio y servicio para cada una de las siguientes entidades, permitiendo consultar sus tablas _H con filtros:
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)
Tipos de Pago (cue_dtTipopago_H)
Canillitas (Maestro) (dist_dtCanillas_H)
Distribuidores (Maestro) (dist_dtDistribuidores_H)
Empresas (Maestro) (dist_dtEmpresas_H)
Zonas (Maestro) (dist_dtZonas_H)
Otros Destinos (Maestro) (dist_dtOtrosDestinos_H)
Publicaciones (Maestro) (dist_dtPublicaciones_H)
Secciones de Publicación (dist_dtPubliSecciones_H)
Precios de Publicación (dist_Precios_H)
Recargos por Zona (dist_RecargoZona_H)
Porcentajes Pago Distribuidores (dist_PorcPago_H)
Porcentajes/Montos Canillita (dist_PorcMonPagoCanilla_H)
Control de Devoluciones (dist_dtCtrlDevoluciones_H)
Tipos de Bobina (bob_dtBobinas_H)
Estados de Bobina (bob_dtEstadosBobinas_H)
Plantas de Impresión (bob_dtPlantas_H)
Stock de Bobinas (bob_StockBobinas_H)
Tiradas (Registro Principal) (bob_RegTiradas_H)
Secciones de Tirada (bob_RegPublicaciones_H)
Cambios de Parada de Canillitas (dist_CambiosParadasCanillas_H)
Ajustes Manuales de Saldo (cue_SaldoAjustesHistorial)
DTOs de Historial: Se crearon DTOs específicos para cada tabla de historial (ej. UsuarioHistorialDto, PagoDistribuidorHistorialDto, etc.) para transferir los datos al frontend, incluyendo el nombre del usuario que realizó la modificación.
Corrección de Lógica de Saldos: Se revisó y corrigió la lógica de afectación de saldos en los servicios PagoDistribuidorService y NotaCreditoDebitoService para asegurar que los débitos y créditos se apliquen correctamente.
3. Frontend:
Nuevo Servicio (auditoriaService.ts): Contiene métodos para llamar a cada uno de los nuevos endpoints de auditoría del backend.
Nueva Página (AuditoriaGeneralPage.tsx):
Permite al SuperAdmin seleccionar el "Tipo de Entidad" a auditar desde un dropdown.
Ofrece filtros comunes (rango de fechas, usuario modificador, tipo de acción) y filtros específicos que aparecen dinámicamente según la entidad seleccionada.
Utiliza un DataGrid de Material-UI para mostrar el historial, con columnas que se adaptan dinámicamente al tipo de entidad consultada.
Nuevos DTOs en TypeScript: Se crearon las interfaces correspondientes a los DTOs de historial del backend.
Gestión de Permisos:
La sección de Auditoría en MainLayout.tsx y su ruta en AppRoutes.tsx están protegidas para ser visibles y accesibles solo por SuperAdmins.
Se añadió un permiso de ejemplo AU_GENERAL_VIEW para ser usado si se decide extender el acceso en el futuro.
Corrección de Errores Menores: Se solucionó el problema del "parpadeo" del selector de fecha en GestionarNovedadesCanillaPage al adoptar un patrón de carga de datos más controlado, similar a otras páginas funcionales.
2025-06-12 19:36:21 -03:00

264 lines
14 KiB
C#

using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Dtos.Auditoria;
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.Linq;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Distribucion
{
public class PorcMonCanillaService : IPorcMonCanillaService
{
private readonly IPorcMonCanillaRepository _porcMonCanillaRepository;
private readonly IPublicacionRepository _publicacionRepository;
private readonly ICanillaRepository _canillaRepository; // Para validar IdCanilla y obtener nombre
private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<PorcMonCanillaService> _logger;
public PorcMonCanillaService(
IPorcMonCanillaRepository porcMonCanillaRepository,
IPublicacionRepository publicacionRepository,
ICanillaRepository canillaRepository,
DbConnectionFactory connectionFactory,
ILogger<PorcMonCanillaService> logger)
{
_porcMonCanillaRepository = porcMonCanillaRepository;
_publicacionRepository = publicacionRepository;
_canillaRepository = canillaRepository;
_connectionFactory = connectionFactory;
_logger = logger;
}
private PorcMonCanillaDto MapToDto((PorcMonCanilla Item, string NomApeCanilla) data) => new PorcMonCanillaDto
{
IdPorcMon = data.Item.IdPorcMon,
IdPublicacion = data.Item.IdPublicacion,
IdCanilla = data.Item.IdCanilla,
NomApeCanilla = data.NomApeCanilla,
VigenciaD = data.Item.VigenciaD.ToString("yyyy-MM-dd"),
VigenciaH = data.Item.VigenciaH?.ToString("yyyy-MM-dd"),
PorcMon = data.Item.PorcMon,
EsPorcentaje = data.Item.EsPorcentaje
};
private async Task<PorcMonCanillaDto?> MapToDtoWithLookup(PorcMonCanilla? item)
{
if (item == null) return null; // Si el item es null, devuelve null
var canillaData = await _canillaRepository.GetByIdAsync(item.IdCanilla);
return new PorcMonCanillaDto
{
IdPorcMon = item.IdPorcMon,
IdPublicacion = item.IdPublicacion,
IdCanilla = item.IdCanilla,
NomApeCanilla = canillaData.Canilla?.NomApe ?? "Canillita Desconocido",
VigenciaD = item.VigenciaD.ToString("yyyy-MM-dd"),
VigenciaH = item.VigenciaH?.ToString("yyyy-MM-dd"),
PorcMon = item.PorcMon,
EsPorcentaje = item.EsPorcentaje
};
}
public async Task<IEnumerable<PorcMonCanillaDto>> ObtenerPorPublicacionIdAsync(int idPublicacion)
{
var data = await _porcMonCanillaRepository.GetByPublicacionIdAsync(idPublicacion);
// Filtrar los nulos que MapToDto podría devolver (aunque no debería en este caso si GetAllWithProfileNameAsync no devuelve usuarios nulos en la tupla)
return data.Select(MapToDto).Where(dto => dto != null).Select(dto => dto!);
}
public async Task<PorcMonCanillaDto?> ObtenerPorIdAsync(int idPorcMon)
{
var item = await _porcMonCanillaRepository.GetByIdAsync(idPorcMon);
return await MapToDtoWithLookup(item);
}
public async Task<(PorcMonCanillaDto? Item, string? Error)> CrearAsync(CreatePorcMonCanillaDto createDto, int idUsuario)
{
if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null)
return (null, "La publicación especificada no existe.");
var canillaData = await _canillaRepository.GetByIdAsync(createDto.IdCanilla);
if (canillaData.Canilla == null) // GetByIdAsync devuelve una tupla
return (null, "El canillita especificado no existe o no está activo.");
// Validar que solo canillitas accionistas pueden tener porcentaje/monto (o la regla de negocio que aplique)
// Por ejemplo, si solo los Accionistas pueden tener esta configuración:
// if (!canillaData.Canilla.Accionista) {
// return (null, "Solo los canillitas accionistas pueden tener un porcentaje/monto de pago configurado.");
// }
var itemActivo = await _porcMonCanillaRepository.GetActiveByPublicacionCanillaAndDateAsync(createDto.IdPublicacion, createDto.IdCanilla, createDto.VigenciaD.Date);
if (itemActivo != null)
{
return (null, $"Ya existe un porcentaje/monto activo para esta publicación y canillita en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Cierre el período anterior.");
}
var nuevoItem = new PorcMonCanilla
{
IdPublicacion = createDto.IdPublicacion,
IdCanilla = createDto.IdCanilla,
VigenciaD = createDto.VigenciaD.Date,
VigenciaH = null,
PorcMon = createDto.PorcMon,
EsPorcentaje = createDto.EsPorcentaje
};
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var itemAnterior = await _porcMonCanillaRepository.GetPreviousActiveAsync(createDto.IdPublicacion, createDto.IdCanilla, nuevoItem.VigenciaD, transaction);
if (itemAnterior != null)
{
if (itemAnterior.VigenciaD.Date >= nuevoItem.VigenciaD.Date)
{
transaction.Rollback();
return (null, $"La fecha de inicio ({nuevoItem.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último período vigente ({itemAnterior.VigenciaD:dd/MM/yyyy}).");
}
itemAnterior.VigenciaH = nuevoItem.VigenciaD.AddDays(-1);
await _porcMonCanillaRepository.UpdateAsync(itemAnterior, idUsuario, transaction);
}
var itemCreado = await _porcMonCanillaRepository.CreateAsync(nuevoItem, idUsuario, transaction);
if (itemCreado == null) throw new DataException("Error al crear el registro.");
transaction.Commit();
_logger.LogInformation("PorcMonCanilla ID {Id} creado por Usuario ID {UserId}.", itemCreado.IdPorcMon, idUsuario);
return (await MapToDtoWithLookup(itemCreado), null);
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error CrearAsync PorcMonCanilla para Pub ID {IdPub}, Canilla ID {IdCan}", createDto.IdPublicacion, createDto.IdCanilla);
return (null, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPorcMon, UpdatePorcMonCanillaDto 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();
try
{
var itemExistente = await _porcMonCanillaRepository.GetByIdAsync(idPorcMon);
if (itemExistente == null) return (false, "Registro de porcentaje/monto no encontrado.");
if (updateDto.VigenciaH.HasValue && updateDto.VigenciaH.Value.Date < itemExistente.VigenciaD.Date)
return (false, "Vigencia Hasta no puede ser anterior a Vigencia Desde.");
if (updateDto.VigenciaH.HasValue)
{
var itemsPubCanillaData = await _porcMonCanillaRepository.GetByPublicacionIdAsync(itemExistente.IdPublicacion);
var itemsPosteriores = itemsPubCanillaData
.Where(i => i.Item.IdCanilla == itemExistente.IdCanilla &&
i.Item.IdPorcMon != idPorcMon &&
i.Item.VigenciaD.Date <= updateDto.VigenciaH.Value.Date &&
i.Item.VigenciaD.Date > itemExistente.VigenciaD.Date);
if (itemsPosteriores.Any())
{
return (false, "No se puede cerrar este período porque existen configuraciones posteriores para este canillita que se solaparían.");
}
}
itemExistente.PorcMon = updateDto.PorcMon;
itemExistente.EsPorcentaje = updateDto.EsPorcentaje;
if (updateDto.VigenciaH.HasValue)
{
itemExistente.VigenciaH = updateDto.VigenciaH.Value.Date;
}
var actualizado = await _porcMonCanillaRepository.UpdateAsync(itemExistente, idUsuario, transaction);
if (!actualizado) throw new DataException("Error al actualizar registro.");
transaction.Commit();
_logger.LogInformation("PorcMonCanilla ID {Id} actualizado por Usuario ID {UserId}.", idPorcMon, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Registro no encontrado."); }
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error ActualizarAsync PorcMonCanilla ID: {Id}", idPorcMon);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> EliminarAsync(int idPorcMon, 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();
try
{
var itemAEliminar = await _porcMonCanillaRepository.GetByIdAsync(idPorcMon);
if (itemAEliminar == null) return (false, "Registro no encontrado.");
if (itemAEliminar.VigenciaH == null)
{
var todosItemsPubCanillaData = await _porcMonCanillaRepository.GetByPublicacionIdAsync(itemAEliminar.IdPublicacion);
var todosItemsPubCanilla = todosItemsPubCanillaData
.Where(i => i.Item.IdCanilla == itemAEliminar.IdCanilla)
.Select(i => i.Item)
.OrderByDescending(i => i.VigenciaD).ToList();
var indiceActual = todosItemsPubCanilla.FindIndex(i => i.IdPorcMon == idPorcMon);
if (indiceActual != -1 && (indiceActual + 1) < todosItemsPubCanilla.Count)
{
var itemAnteriorDirecto = todosItemsPubCanilla[indiceActual + 1];
if (itemAnteriorDirecto.VigenciaH.HasValue &&
itemAnteriorDirecto.VigenciaH.Value.Date == itemAEliminar.VigenciaD.AddDays(-1).Date)
{
itemAnteriorDirecto.VigenciaH = null;
await _porcMonCanillaRepository.UpdateAsync(itemAnteriorDirecto, idUsuario, transaction);
}
}
}
var eliminado = await _porcMonCanillaRepository.DeleteAsync(idPorcMon, idUsuario, transaction);
if (!eliminado) throw new DataException("Error al eliminar registro.");
transaction.Commit();
_logger.LogInformation("PorcMonCanilla ID {Id} eliminado por Usuario ID {UserId}.", idPorcMon, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Registro no encontrado."); }
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error EliminarAsync PorcMonCanilla ID: {Id}", idPorcMon);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<IEnumerable<PorcMonCanillaHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idPorcMonAfectado, int? idPublicacionAfectada, int? idCanillaAfectado)
{
var historialData = await _porcMonCanillaRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idPorcMonAfectado, idPublicacionAfectada, idCanillaAfectado);
return historialData.Select(h => new PorcMonCanillaHistorialDto
{
Id_PorcMon = h.Historial.Id_PorcMon,
Id_Publicacion = h.Historial.Id_Publicacion,
Id_Canilla = h.Historial.Id_Canilla,
VigenciaD = h.Historial.VigenciaD,
VigenciaH = h.Historial.VigenciaH,
PorcMon = h.Historial.PorcMon,
EsPorcentaje = h.Historial.EsPorcentaje,
Id_Usuario = h.Historial.Id_Usuario,
NombreUsuarioModifico = h.NombreUsuarioModifico,
FechaMod = h.Historial.FechaMod,
TipoMod = h.Historial.TipoMod
}).ToList();
}
}
}