Files
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

255 lines
13 KiB
C#

// src/Services/Distribucion/DistribuidorService.cs
using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Contables;
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 DistribuidorService : IDistribuidorService
{
private readonly IDistribuidorRepository _distribuidorRepository;
private readonly ISaldoRepository _saldoRepository;
private readonly IEmpresaRepository _empresaRepository;
private readonly IZonaRepository _zonaRepository;
private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<DistribuidorService> _logger;
public DistribuidorService(
IDistribuidorRepository distribuidorRepository,
ISaldoRepository saldoRepository,
IEmpresaRepository empresaRepository,
IZonaRepository zonaRepository,
DbConnectionFactory connectionFactory,
ILogger<DistribuidorService> logger)
{
_distribuidorRepository = distribuidorRepository;
_saldoRepository = saldoRepository;
_empresaRepository = empresaRepository;
_zonaRepository = zonaRepository;
_connectionFactory = connectionFactory;
_logger = logger;
}
private DistribuidorDto? MapToDto((Distribuidor? Distribuidor, string? NombreZona) data)
{
if (data.Distribuidor == null) return null;
return new DistribuidorDto
{
IdDistribuidor = data.Distribuidor.IdDistribuidor,
Nombre = data.Distribuidor.Nombre,
Contacto = data.Distribuidor.Contacto,
NroDoc = data.Distribuidor.NroDoc,
IdZona = data.Distribuidor.IdZona,
NombreZona = data.NombreZona, // Ya es anulable en el DTO y en la tupla
Calle = data.Distribuidor.Calle,
Numero = data.Distribuidor.Numero,
Piso = data.Distribuidor.Piso,
Depto = data.Distribuidor.Depto,
Telefono = data.Distribuidor.Telefono,
Email = data.Distribuidor.Email,
Localidad = data.Distribuidor.Localidad
};
}
public async Task<IEnumerable<DistribuidorDto>> ObtenerTodosAsync(string? nombreFilter, string? nroDocFilter)
{
var data = await _distribuidorRepository.GetAllAsync(nombreFilter, nroDocFilter);
// 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<IEnumerable<DistribuidorDropdownDto>> GetAllDropdownAsync()
{
var data = await _distribuidorRepository.GetAllDropdownAsync();
// Asegurar que el resultado no sea nulo y no contiene elementos nulos
if (data == null)
{
return new List<DistribuidorDropdownDto>
{
new DistribuidorDropdownDto { IdDistribuidor = 0, Nombre = "No hay distribuidores disponibles" }
};
}
return data.Where(x => x != null)!;
}
public async Task<DistribuidorDto?> ObtenerPorIdAsync(int id)
{
var data = await _distribuidorRepository.GetByIdAsync(id);
// MapToDto ahora devuelve DistribuidorDto?
return MapToDto(data);
}
public async Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id)
{
var data = await _distribuidorRepository.ObtenerLookupPorIdAsync(id);
return data;
}
public async Task<(DistribuidorDto? Distribuidor, string? Error)> CrearAsync(CreateDistribuidorDto createDto, int idUsuario)
{
if (await _distribuidorRepository.ExistsByNroDocAsync(createDto.NroDoc))
return (null, "El número de documento ya existe para otro distribuidor.");
if (await _distribuidorRepository.ExistsByNameAsync(createDto.Nombre))
return (null, "El nombre del distribuidor ya existe.");
if (createDto.IdZona.HasValue && await _zonaRepository.GetByIdAsync(createDto.IdZona.Value) == null)
return (null, "La zona seleccionada no es válida o no está activa.");
var nuevoDistribuidor = new Distribuidor
{
Nombre = createDto.Nombre,
Contacto = createDto.Contacto,
NroDoc = createDto.NroDoc,
IdZona = createDto.IdZona,
Calle = createDto.Calle,
Numero = createDto.Numero,
Piso = createDto.Piso,
Depto = createDto.Depto,
Telefono = createDto.Telefono,
Email = createDto.Email,
Localidad = createDto.Localidad
};
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 distribuidorCreado = await _distribuidorRepository.CreateAsync(nuevoDistribuidor, idUsuario, transaction);
if (distribuidorCreado == null) throw new DataException("Error al crear el distribuidor.");
var empresas = await _empresaRepository.GetAllAsync(null, null);
foreach (var empresa in empresas)
{
bool saldoCreado = await _saldoRepository.CreateSaldoInicialAsync("Distribuidores", distribuidorCreado.IdDistribuidor, empresa.IdEmpresa, transaction);
if (!saldoCreado)
{
throw new DataException($"Falló al crear saldo inicial para el distribuidor {distribuidorCreado.IdDistribuidor} en la empresa {empresa.IdEmpresa}.");
}
}
transaction.Commit();
var dataCompleta = await _distribuidorRepository.GetByIdAsync(distribuidorCreado.IdDistribuidor);
_logger.LogInformation("Distribuidor ID {IdDistribuidor} creado por Usuario ID {IdUsuario}.", distribuidorCreado.IdDistribuidor, idUsuario);
// MapToDto ahora devuelve DistribuidorDto?
return (MapToDto(dataCompleta), null);
}
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error CrearAsync Distribuidor: {Nombre}", createDto.Nombre);
return (null, $"Error interno al crear el distribuidor: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateDistribuidorDto updateDto, int idUsuario)
{
var distribuidorExistente = await _distribuidorRepository.GetByIdSimpleAsync(id);
if (distribuidorExistente == null) return (false, "Distribuidor no encontrado.");
if (await _distribuidorRepository.ExistsByNroDocAsync(updateDto.NroDoc, id))
return (false, "El número de documento ya existe para otro distribuidor.");
// CORREGIDO: La tupla de retorno para la validación de nombre debe ser (bool, string?)
if (await _distribuidorRepository.ExistsByNameAsync(updateDto.Nombre, id))
return (false, "El nombre del distribuidor ya existe."); // Antes (null, ...)
if (updateDto.IdZona.HasValue && await _zonaRepository.GetByIdAsync(updateDto.IdZona.Value) == null)
return (false, "La zona seleccionada no es válida o no está activa.");
distribuidorExistente.Nombre = updateDto.Nombre; distribuidorExistente.Contacto = updateDto.Contacto;
distribuidorExistente.NroDoc = updateDto.NroDoc; distribuidorExistente.IdZona = updateDto.IdZona;
distribuidorExistente.Calle = updateDto.Calle; distribuidorExistente.Numero = updateDto.Numero;
distribuidorExistente.Piso = updateDto.Piso; distribuidorExistente.Depto = updateDto.Depto;
distribuidorExistente.Telefono = updateDto.Telefono; distribuidorExistente.Email = updateDto.Email;
distribuidorExistente.Localidad = updateDto.Localidad;
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 _distribuidorRepository.UpdateAsync(distribuidorExistente, idUsuario, transaction);
if (!actualizado) throw new DataException("Error al actualizar.");
transaction.Commit();
_logger.LogInformation("Distribuidor ID {IdDistribuidor} actualizado por Usuario ID {IdUsuario}.", id, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Distribuidor no encontrado."); }
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error ActualizarAsync Distribuidor ID: {IdDistribuidor}", id);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
{
var distribuidorExistente = await _distribuidorRepository.GetByIdSimpleAsync(id);
if (distribuidorExistente == null) return (false, "Distribuidor no encontrado.");
if (await _distribuidorRepository.IsInUseAsync(id))
return (false, "No se puede eliminar. El distribuidor tiene movimientos o configuraciones asociadas.");
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
{
// Lógica para eliminar saldos asociados (si se implementa)
// var saldosEliminados = await _saldoRepository.DeleteSaldosByDestinoAsync("Distribuidores", id, transaction);
// if (!saldosEliminados) throw new DataException("Error al eliminar saldos del distribuidor.");
var eliminado = await _distribuidorRepository.DeleteAsync(id, idUsuario, transaction);
if (!eliminado) throw new DataException("Error al eliminar.");
transaction.Commit();
_logger.LogInformation("Distribuidor ID {IdDistribuidor} eliminado por Usuario ID {IdUsuario}.", id, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Distribuidor no encontrado."); }
catch (Exception ex)
{
try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error EliminarAsync Distribuidor ID: {IdDistribuidor}", id);
return (false, $"Error interno: {ex.Message}");
}
}
public async Task<IEnumerable<DistribuidorHistorialDto>> ObtenerHistorialAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idUsuarioModifico, string? tipoModificacion,
int? idDistribuidorAfectado)
{
var historialData = await _distribuidorRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idDistribuidorAfectado);
// Si necesitas NombreZona, harías el JOIN en el repo o una llamada aquí a _zonaRepository
return historialData.Select(h => new DistribuidorHistorialDto
{
Id_Distribuidor = h.Historial.Id_Distribuidor,
Nombre = h.Historial.Nombre,
Contacto = h.Historial.Contacto,
NroDoc = h.Historial.NroDoc,
Id_Zona = h.Historial.Id_Zona,
Calle = h.Historial.Calle,
Numero = h.Historial.Numero,
Piso = h.Historial.Piso,
Depto = h.Historial.Depto,
Telefono = h.Historial.Telefono,
Email = h.Historial.Email,
Localidad = h.Historial.Localidad,
Id_Usuario = h.Historial.Id_Usuario,
NombreUsuarioModifico = h.NombreUsuarioModifico,
FechaMod = h.Historial.FechaMod,
TipoMod = h.Historial.TipoMod
}).ToList();
}
}
}