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.
292 lines
14 KiB
C#
292 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.Collections.Generic;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace GestionIntegral.Api.Services.Distribucion
|
|
{
|
|
public class CanillaService : ICanillaService
|
|
{
|
|
private readonly ICanillaRepository _canillaRepository;
|
|
private readonly IZonaRepository _zonaRepository;
|
|
private readonly IEmpresaRepository _empresaRepository;
|
|
private readonly DbConnectionFactory _connectionFactory;
|
|
private readonly ILogger<CanillaService> _logger;
|
|
|
|
public CanillaService(
|
|
ICanillaRepository canillaRepository,
|
|
IZonaRepository zonaRepository,
|
|
IEmpresaRepository empresaRepository,
|
|
DbConnectionFactory connectionFactory,
|
|
ILogger<CanillaService> logger)
|
|
{
|
|
_canillaRepository = canillaRepository;
|
|
_zonaRepository = zonaRepository;
|
|
_empresaRepository = empresaRepository;
|
|
_connectionFactory = connectionFactory;
|
|
_logger = logger;
|
|
}
|
|
|
|
// CORREGIDO: MapToDto ahora acepta una tupla con tipos anulables
|
|
private CanillaDto? MapToDto((Canilla? Canilla, string? NombreZona, string? NombreEmpresa) data)
|
|
{
|
|
if (data.Canilla == null) return null;
|
|
|
|
return new CanillaDto
|
|
{
|
|
IdCanilla = data.Canilla.IdCanilla,
|
|
Legajo = data.Canilla.Legajo,
|
|
NomApe = data.Canilla.NomApe,
|
|
Parada = data.Canilla.Parada,
|
|
IdZona = data.Canilla.IdZona,
|
|
NombreZona = data.NombreZona ?? "N/A", // Manejar null
|
|
Accionista = data.Canilla.Accionista,
|
|
Obs = data.Canilla.Obs,
|
|
Empresa = data.Canilla.Empresa,
|
|
NombreEmpresa = data.NombreEmpresa ?? "N/A (Accionista)", // Manejar null
|
|
Baja = data.Canilla.Baja,
|
|
FechaBaja = data.Canilla.FechaBaja?.ToString("dd/MM/yyyy")
|
|
};
|
|
}
|
|
|
|
public async Task<IEnumerable<CanillaDto>> ObtenerTodosAsync(string? nomApeFilter, int? legajoFilter, bool? esAccionista, bool? soloActivos)
|
|
{
|
|
var data = await _canillaRepository.GetAllAsync(nomApeFilter, legajoFilter, soloActivos, esAccionista);
|
|
// Filtrar nulos y asegurar al compilador que no hay nulos en la lista final
|
|
return data.Select(MapToDto).Where(dto => dto != null).Select(dto => dto!);
|
|
}
|
|
|
|
public async Task<CanillaDto?> ObtenerPorIdAsync(int id)
|
|
{
|
|
var data = await _canillaRepository.GetByIdAsync(id);
|
|
// MapToDto ahora devuelve CanillaDto? así que esto es correcto
|
|
return MapToDto(data);
|
|
}
|
|
|
|
public async Task<(CanillaDto? Canilla, string? Error)> CrearAsync(CreateCanillaDto createDto, int idUsuario)
|
|
{
|
|
if (createDto.Legajo.HasValue && createDto.Legajo != 0 && await _canillaRepository.ExistsByLegajoAsync(createDto.Legajo.Value))
|
|
{
|
|
return (null, "El legajo ingresado ya existe para otro canillita.");
|
|
}
|
|
var zona = await _zonaRepository.GetByIdAsync(createDto.IdZona); // GetByIdAsync de Zona ya considera solo activas
|
|
if (zona == null)
|
|
{
|
|
return (null, "La zona seleccionada no es válida o no está activa.");
|
|
}
|
|
if (createDto.Empresa != 0) // Solo validar empresa si no es 0
|
|
{
|
|
var empresa = await _empresaRepository.GetByIdAsync(createDto.Empresa);
|
|
if (empresa == null)
|
|
{
|
|
return (null, "La empresa seleccionada no es válida.");
|
|
}
|
|
}
|
|
|
|
// CORREGIDO: Usar directamente el valor booleano
|
|
if (createDto.Accionista == true && createDto.Empresa != 0)
|
|
{
|
|
return (null, "Un canillita accionista no debe tener una empresa asignada (Empresa debe ser 0).");
|
|
}
|
|
if (createDto.Accionista == false && createDto.Empresa == 0)
|
|
{
|
|
return (null, "Un canillita no accionista debe tener una empresa asignada (Empresa no puede ser 0).");
|
|
}
|
|
|
|
|
|
var nuevoCanilla = new Canilla
|
|
{
|
|
Legajo = createDto.Legajo == 0 ? null : createDto.Legajo,
|
|
NomApe = createDto.NomApe,
|
|
Parada = createDto.Parada,
|
|
IdZona = createDto.IdZona,
|
|
Accionista = createDto.Accionista,
|
|
Obs = createDto.Obs,
|
|
Empresa = createDto.Empresa,
|
|
Baja = false,
|
|
FechaBaja = null
|
|
};
|
|
|
|
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 canillaCreado = await _canillaRepository.CreateAsync(nuevoCanilla, idUsuario, transaction);
|
|
if (canillaCreado == null) throw new DataException("Error al crear el canillita.");
|
|
|
|
transaction.Commit();
|
|
|
|
// Para el DTO de respuesta, necesitamos NombreZona y NombreEmpresa
|
|
string nombreEmpresaParaDto = "N/A (Accionista)";
|
|
if (canillaCreado.Empresa != 0)
|
|
{
|
|
var empresaData = await _empresaRepository.GetByIdAsync(canillaCreado.Empresa);
|
|
nombreEmpresaParaDto = empresaData?.Nombre ?? "Empresa Desconocida";
|
|
}
|
|
|
|
var dtoCreado = new CanillaDto
|
|
{
|
|
IdCanilla = canillaCreado.IdCanilla,
|
|
Legajo = canillaCreado.Legajo,
|
|
NomApe = canillaCreado.NomApe,
|
|
Parada = canillaCreado.Parada,
|
|
IdZona = canillaCreado.IdZona,
|
|
NombreZona = zona.Nombre, // Usar nombre de zona ya obtenido
|
|
Accionista = canillaCreado.Accionista,
|
|
Obs = canillaCreado.Obs,
|
|
Empresa = canillaCreado.Empresa,
|
|
NombreEmpresa = nombreEmpresaParaDto,
|
|
Baja = canillaCreado.Baja,
|
|
FechaBaja = null
|
|
};
|
|
|
|
_logger.LogInformation("Canilla ID {IdCanilla} creado por Usuario ID {IdUsuario}.", canillaCreado.IdCanilla, idUsuario);
|
|
return (dtoCreado, null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
try { transaction.Rollback(); } catch { }
|
|
_logger.LogError(ex, "Error CrearAsync Canilla: {NomApe}", createDto.NomApe);
|
|
return (null, $"Error interno al crear el canillita: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateCanillaDto updateDto, int idUsuario)
|
|
{
|
|
var canillaExistente = await _canillaRepository.GetByIdSimpleAsync(id);
|
|
if (canillaExistente == null) return (false, "Canillita no encontrado.");
|
|
|
|
if (updateDto.Legajo.HasValue && updateDto.Legajo != 0 && await _canillaRepository.ExistsByLegajoAsync(updateDto.Legajo.Value, id))
|
|
{
|
|
return (false, "El legajo ingresado ya existe para otro canillita.");
|
|
}
|
|
if (await _zonaRepository.GetByIdAsync(updateDto.IdZona) == null) // GetByIdAsync de Zona ya considera solo activas
|
|
{
|
|
return (false, "La zona seleccionada no es válida o no está activa.");
|
|
}
|
|
if (updateDto.Empresa != 0) // Solo validar empresa si no es 0
|
|
{
|
|
var empresa = await _empresaRepository.GetByIdAsync(updateDto.Empresa);
|
|
if (empresa == null)
|
|
{
|
|
return (false, "La empresa seleccionada no es válida.");
|
|
}
|
|
}
|
|
|
|
// Usar directamente el valor booleano para Accionista
|
|
if (updateDto.Accionista == true && updateDto.Empresa != 0)
|
|
{
|
|
// Al ser 'bool', no puede ser null. La comparación explícita con 'true'/'false' es para claridad.
|
|
return (false, "Un canillita accionista no debe tener una empresa asignada (Empresa debe ser 0).");
|
|
}
|
|
if (updateDto.Accionista == false && updateDto.Empresa == 0)
|
|
{
|
|
return (false, "Un canillita no accionista debe tener una empresa asignada (Empresa no puede ser 0).");
|
|
}
|
|
|
|
// Mapear DTO a entidad existente
|
|
canillaExistente.Legajo = updateDto.Legajo == 0 ? null : updateDto.Legajo;
|
|
canillaExistente.NomApe = updateDto.NomApe;
|
|
canillaExistente.Parada = updateDto.Parada;
|
|
canillaExistente.IdZona = updateDto.IdZona;
|
|
canillaExistente.Accionista = updateDto.Accionista; // Aquí Accionista ya es bool
|
|
canillaExistente.Obs = updateDto.Obs;
|
|
canillaExistente.Empresa = updateDto.Empresa;
|
|
// Baja y FechaBaja se manejan por ToggleBajaAsync
|
|
|
|
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 actualizado = await _canillaRepository.UpdateAsync(canillaExistente, idUsuario, transaction);
|
|
if (!actualizado) throw new DataException("Error al actualizar el canillita.");
|
|
transaction.Commit();
|
|
_logger.LogInformation("Canilla ID {IdCanilla} actualizado por Usuario ID {IdUsuario}.", id, idUsuario);
|
|
return (true, null);
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
try { transaction.Rollback(); } catch { }
|
|
return (false, "Canillita no encontrado durante la actualización.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
try { transaction.Rollback(); } catch { }
|
|
_logger.LogError(ex, "Error ActualizarAsync Canilla ID: {IdCanilla}", id);
|
|
return (false, $"Error interno al actualizar el canillita: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<(bool Exito, string? Error)> ToggleBajaAsync(int id, bool darDeBaja, int idUsuario)
|
|
{
|
|
var canilla = await _canillaRepository.GetByIdSimpleAsync(id);
|
|
if (canilla == null) return (false, "Canillita no encontrado.");
|
|
|
|
if (canilla.Baja == darDeBaja)
|
|
{
|
|
return (false, darDeBaja ? "El canillita ya está dado de baja." : "El canillita ya está activo.");
|
|
}
|
|
|
|
DateTime? fechaBaja = darDeBaja ? DateTime.Now : (DateTime?)null;
|
|
|
|
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 success = await _canillaRepository.ToggleBajaAsync(id, darDeBaja, fechaBaja, idUsuario, transaction);
|
|
if (!success) throw new DataException("Error al cambiar estado de baja del canillita.");
|
|
transaction.Commit();
|
|
_logger.LogInformation("Estado de baja cambiado a {EstadoBaja} para Canilla ID {IdCanilla} por Usuario ID {IdUsuario}.", darDeBaja, id, idUsuario);
|
|
return (true, null);
|
|
}
|
|
catch (KeyNotFoundException)
|
|
{
|
|
try { transaction.Rollback(); } catch { }
|
|
return (false, "Canillita no encontrado durante el cambio de estado de baja.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
try { transaction.Rollback(); } catch { }
|
|
_logger.LogError(ex, "Error ToggleBajaAsync Canilla ID: {IdCanilla}", id);
|
|
return (false, $"Error interno al cambiar estado de baja: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<IEnumerable<CanillaHistorialDto>> ObtenerHistorialAsync(
|
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
|
int? idUsuarioModifico, string? tipoModificacion,
|
|
int? idCanillaAfectado)
|
|
{
|
|
var historialData = await _canillaRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idCanillaAfectado);
|
|
|
|
return historialData.Select(h => new CanillaHistorialDto
|
|
{
|
|
Id_Canilla = h.Historial.Id_Canilla,
|
|
Legajo = h.Historial.Legajo,
|
|
NomApe = h.Historial.NomApe,
|
|
Parada = h.Historial.Parada,
|
|
Id_Zona = h.Historial.Id_Zona,
|
|
Accionista = h.Historial.Accionista,
|
|
Obs = h.Historial.Obs,
|
|
Empresa = h.Historial.Empresa,
|
|
Baja = h.Historial.Baja,
|
|
FechaBaja = h.Historial.FechaBaja,
|
|
Id_Usuario = h.Historial.Id_Usuario,
|
|
NombreUsuarioModifico = h.NombreUsuarioModifico,
|
|
FechaMod = h.Historial.FechaMod,
|
|
TipoMod = h.Historial.TipoMod
|
|
}).ToList();
|
|
}
|
|
}
|
|
} |