Backend API:
- Recargos por Zona (`dist_RecargoZona`):
- CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/recargos`.
- Lógica de negocio para vigencias (cierre/reapertura de períodos).
- Auditoría en `dist_RecargoZona_H`.
- Porcentajes de Pago Distribuidores (`dist_PorcPago`):
- CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/porcentajespago`.
- Lógica de negocio para vigencias.
- Auditoría en `dist_PorcPago_H`.
- Porcentajes/Montos Pago Canillitas (`dist_PorcMonPagoCanilla`):
- CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/porcentajesmoncanilla`.
- Lógica de negocio para vigencias.
- Auditoría en `dist_PorcMonPagoCanilla_H`.
- Secciones de Publicación (`dist_dtPubliSecciones`):
- CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/secciones`.
- Auditoría en `dist_dtPubliSecciones_H`.
- Entradas/Salidas Distribuidores (`dist_EntradasSalidas`):
- Implementado backend (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Lógica para determinar precios/recargos/porcentajes aplicables.
- Cálculo de monto y afectación de saldos de distribuidores en `cue_Saldos`.
- Auditoría en `dist_EntradasSalidas_H`.
- Correcciones de Mapeo Dapper:
- Aplicados alias explícitos en repositorios de RecargoZona, PorcPago, PorcMonCanilla, PubliSeccion,
Canilla, Distribuidor y Precio para asegurar mapeo correcto de IDs y columnas.
Frontend React:
- Recargos por Zona:
- `recargoZonaService.ts`.
- `RecargoZonaFormModal.tsx` para crear/editar períodos de recargos.
- `GestionarRecargosPublicacionPage.tsx` para listar y gestionar recargos por publicación.
- Porcentajes de Pago Distribuidores:
- `porcPagoService.ts`.
- `PorcPagoFormModal.tsx`.
- `GestionarPorcentajesPagoPage.tsx`.
- Porcentajes/Montos Pago Canillitas:
- `porcMonCanillaService.ts`.
- `PorcMonCanillaFormModal.tsx`.
- `GestionarPorcMonCanillaPage.tsx`.
- Secciones de Publicación:
- `publiSeccionService.ts`.
- `PubliSeccionFormModal.tsx`.
- `GestionarSeccionesPublicacionPage.tsx`.
- Navegación:
- Actualizadas rutas y menús para acceder a la gestión de recargos, porcentajes (dist. y canillita) y secciones desde la vista de una publicación.
- Layout:
- Uso consistente de `Box` con Flexbox en lugar de `Grid` en nuevos modales y páginas para evitar errores de tipo.
278 lines
15 KiB
C#
278 lines
15 KiB
C#
using GestionIntegral.Api.Data;
|
|
using GestionIntegral.Api.Data.Repositories.Impresion;
|
|
using GestionIntegral.Api.Dtos.Impresion;
|
|
using GestionIntegral.Api.Models.Impresion;
|
|
using GestionIntegral.Api.Models.Distribucion; // Para Publicacion, PubliSeccion
|
|
using GestionIntegral.Api.Data.Repositories.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.Impresion
|
|
{
|
|
public class StockBobinaService : IStockBobinaService
|
|
{
|
|
private readonly IStockBobinaRepository _stockBobinaRepository;
|
|
private readonly ITipoBobinaRepository _tipoBobinaRepository;
|
|
private readonly IPlantaRepository _plantaRepository;
|
|
private readonly IEstadoBobinaRepository _estadoBobinaRepository;
|
|
private readonly IPublicacionRepository _publicacionRepository; // Para validar IdPublicacion
|
|
private readonly IPubliSeccionRepository _publiSeccionRepository; // Para validar IdSeccion
|
|
private readonly DbConnectionFactory _connectionFactory;
|
|
private readonly ILogger<StockBobinaService> _logger;
|
|
|
|
public StockBobinaService(
|
|
IStockBobinaRepository stockBobinaRepository,
|
|
ITipoBobinaRepository tipoBobinaRepository,
|
|
IPlantaRepository plantaRepository,
|
|
IEstadoBobinaRepository estadoBobinaRepository,
|
|
IPublicacionRepository publicacionRepository,
|
|
IPubliSeccionRepository publiSeccionRepository,
|
|
DbConnectionFactory connectionFactory,
|
|
ILogger<StockBobinaService> logger)
|
|
{
|
|
_stockBobinaRepository = stockBobinaRepository;
|
|
_tipoBobinaRepository = tipoBobinaRepository;
|
|
_plantaRepository = plantaRepository;
|
|
_estadoBobinaRepository = estadoBobinaRepository;
|
|
_publicacionRepository = publicacionRepository;
|
|
_publiSeccionRepository = publiSeccionRepository;
|
|
_connectionFactory = connectionFactory;
|
|
_logger = logger;
|
|
}
|
|
|
|
// Mapeo complejo porque necesitamos nombres de entidades relacionadas
|
|
private async Task<StockBobinaDto> MapToDto(StockBobina bobina)
|
|
{
|
|
if (bobina == null) return null!; // Debería ser manejado por el llamador
|
|
|
|
var tipoBobina = await _tipoBobinaRepository.GetByIdAsync(bobina.IdTipoBobina);
|
|
var planta = await _plantaRepository.GetByIdAsync(bobina.IdPlanta);
|
|
var estado = await _estadoBobinaRepository.GetByIdAsync(bobina.IdEstadoBobina);
|
|
Publicacion? publicacion = null;
|
|
PubliSeccion? seccion = null;
|
|
|
|
if (bobina.IdPublicacion.HasValue)
|
|
publicacion = await _publicacionRepository.GetByIdSimpleAsync(bobina.IdPublicacion.Value);
|
|
if (bobina.IdSeccion.HasValue)
|
|
seccion = await _publiSeccionRepository.GetByIdAsync(bobina.IdSeccion.Value); // Asume que GetByIdAsync existe
|
|
|
|
return new StockBobinaDto
|
|
{
|
|
IdBobina = bobina.IdBobina,
|
|
IdTipoBobina = bobina.IdTipoBobina,
|
|
NombreTipoBobina = tipoBobina?.Denominacion ?? "N/A",
|
|
NroBobina = bobina.NroBobina,
|
|
Peso = bobina.Peso,
|
|
IdPlanta = bobina.IdPlanta,
|
|
NombrePlanta = planta?.Nombre ?? "N/A",
|
|
IdEstadoBobina = bobina.IdEstadoBobina,
|
|
NombreEstadoBobina = estado?.Denominacion ?? "N/A",
|
|
Remito = bobina.Remito,
|
|
FechaRemito = bobina.FechaRemito.ToString("yyyy-MM-dd"),
|
|
FechaEstado = bobina.FechaEstado?.ToString("yyyy-MM-dd"),
|
|
IdPublicacion = bobina.IdPublicacion,
|
|
NombrePublicacion = publicacion?.Nombre,
|
|
IdSeccion = bobina.IdSeccion,
|
|
NombreSeccion = seccion?.Nombre,
|
|
Obs = bobina.Obs
|
|
};
|
|
}
|
|
|
|
public async Task<IEnumerable<StockBobinaDto>> ObtenerTodosAsync(
|
|
int? idTipoBobina, string? nroBobinaFilter, int? idPlanta,
|
|
int? idEstadoBobina, string? remitoFilter, DateTime? fechaDesde, DateTime? fechaHasta)
|
|
{
|
|
var bobinas = await _stockBobinaRepository.GetAllAsync(idTipoBobina, nroBobinaFilter, idPlanta, idEstadoBobina, remitoFilter, fechaDesde, fechaHasta);
|
|
var dtos = new List<StockBobinaDto>();
|
|
foreach (var bobina in bobinas)
|
|
{
|
|
dtos.Add(await MapToDto(bobina));
|
|
}
|
|
return dtos;
|
|
}
|
|
|
|
public async Task<StockBobinaDto?> ObtenerPorIdAsync(int idBobina)
|
|
{
|
|
var bobina = await _stockBobinaRepository.GetByIdAsync(idBobina);
|
|
return bobina == null ? null : await MapToDto(bobina);
|
|
}
|
|
|
|
public async Task<(StockBobinaDto? Bobina, string? Error)> IngresarBobinaAsync(CreateStockBobinaDto createDto, int idUsuario)
|
|
{
|
|
if (await _tipoBobinaRepository.GetByIdAsync(createDto.IdTipoBobina) == null)
|
|
return (null, "Tipo de bobina inválido.");
|
|
if (await _plantaRepository.GetByIdAsync(createDto.IdPlanta) == null)
|
|
return (null, "Planta inválida.");
|
|
if (await _stockBobinaRepository.GetByNroBobinaAsync(createDto.NroBobina) != null)
|
|
return (null, $"El número de bobina '{createDto.NroBobina}' ya existe.");
|
|
|
|
var nuevaBobina = new StockBobina
|
|
{
|
|
IdTipoBobina = createDto.IdTipoBobina,
|
|
NroBobina = createDto.NroBobina,
|
|
Peso = createDto.Peso,
|
|
IdPlanta = createDto.IdPlanta,
|
|
IdEstadoBobina = 1, // 1 = Disponible (según contexto previo)
|
|
Remito = createDto.Remito,
|
|
FechaRemito = createDto.FechaRemito.Date,
|
|
FechaEstado = createDto.FechaRemito.Date, // Estado inicial en fecha de remito
|
|
IdPublicacion = null,
|
|
IdSeccion = null,
|
|
Obs = 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 bobinaCreada = await _stockBobinaRepository.CreateAsync(nuevaBobina, idUsuario, transaction);
|
|
if (bobinaCreada == null) throw new DataException("Error al ingresar la bobina.");
|
|
transaction.Commit();
|
|
_logger.LogInformation("Bobina ID {Id} ingresada por Usuario ID {UserId}.", bobinaCreada.IdBobina, idUsuario);
|
|
return (await MapToDto(bobinaCreada), null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
try { transaction.Rollback(); } catch { }
|
|
_logger.LogError(ex, "Error IngresarBobinaAsync: {NroBobina}", createDto.NroBobina);
|
|
return (null, $"Error interno: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<(bool Exito, string? Error)> ActualizarDatosBobinaDisponibleAsync(int idBobina, UpdateStockBobinaDto 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 bobinaExistente = await _stockBobinaRepository.GetByIdAsync(idBobina); // Obtener dentro de TX
|
|
if (bobinaExistente == null) return (false, "Bobina no encontrada.");
|
|
if (bobinaExistente.IdEstadoBobina != 1) // Solo se pueden editar datos si está "Disponible"
|
|
return (false, "Solo se pueden modificar los datos de una bobina en estado 'Disponible'.");
|
|
|
|
// Validar unicidad de NroBobina si cambió
|
|
if (bobinaExistente.NroBobina != updateDto.NroBobina &&
|
|
await _stockBobinaRepository.GetByNroBobinaAsync(updateDto.NroBobina) != null) // Validar fuera de TX o pasar TX al repo
|
|
{
|
|
try { transaction.Rollback(); } catch {} // Rollback antes de retornar por validación
|
|
return (false, $"El nuevo número de bobina '{updateDto.NroBobina}' ya existe.");
|
|
}
|
|
if (await _tipoBobinaRepository.GetByIdAsync(updateDto.IdTipoBobina) == null)
|
|
return (false, "Tipo de bobina inválido.");
|
|
if (await _plantaRepository.GetByIdAsync(updateDto.IdPlanta) == null)
|
|
return (false, "Planta inválida.");
|
|
|
|
|
|
bobinaExistente.IdTipoBobina = updateDto.IdTipoBobina;
|
|
bobinaExistente.NroBobina = updateDto.NroBobina;
|
|
bobinaExistente.Peso = updateDto.Peso;
|
|
bobinaExistente.IdPlanta = updateDto.IdPlanta;
|
|
bobinaExistente.Remito = updateDto.Remito;
|
|
bobinaExistente.FechaRemito = updateDto.FechaRemito.Date;
|
|
// FechaEstado se mantiene ya que el estado no cambia aquí
|
|
|
|
var actualizado = await _stockBobinaRepository.UpdateAsync(bobinaExistente, idUsuario, transaction, "Datos Actualizados");
|
|
if (!actualizado) throw new DataException("Error al actualizar la bobina.");
|
|
transaction.Commit();
|
|
return (true, null);
|
|
}
|
|
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); }
|
|
catch (Exception ex)
|
|
{
|
|
try { transaction.Rollback(); } catch { }
|
|
_logger.LogError(ex, "Error ActualizarDatosBobinaDisponibleAsync ID: {IdBobina}", idBobina);
|
|
return (false, $"Error interno: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<(bool Exito, string? Error)> CambiarEstadoBobinaAsync(int idBobina, CambiarEstadoBobinaDto cambiarEstadoDto, 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 bobina = await _stockBobinaRepository.GetByIdAsync(idBobina);
|
|
if (bobina == null) return (false, "Bobina no encontrada.");
|
|
|
|
var nuevoEstado = await _estadoBobinaRepository.GetByIdAsync(cambiarEstadoDto.NuevoEstadoId);
|
|
if (nuevoEstado == null) return (false, "El nuevo estado especificado no es válido.");
|
|
|
|
// Validaciones de flujo de estados
|
|
if (bobina.IdEstadoBobina == cambiarEstadoDto.NuevoEstadoId)
|
|
return (false, "La bobina ya se encuentra en ese estado.");
|
|
if (bobina.IdEstadoBobina == 3 && cambiarEstadoDto.NuevoEstadoId != 3) // 3 = Dañada
|
|
return (false, "Una bobina dañada no puede cambiar de estado.");
|
|
if (bobina.IdEstadoBobina == 2 && cambiarEstadoDto.NuevoEstadoId == 1) // 2 = En Uso, 1 = Disponible
|
|
return (false, "Una bobina 'En Uso' no puede volver a 'Disponible' directamente mediante esta acción.");
|
|
|
|
|
|
bobina.IdEstadoBobina = cambiarEstadoDto.NuevoEstadoId;
|
|
bobina.FechaEstado = cambiarEstadoDto.FechaCambioEstado.Date;
|
|
bobina.Obs = cambiarEstadoDto.Obs ?? bobina.Obs; // Mantener obs si no se provee uno nuevo
|
|
|
|
if (cambiarEstadoDto.NuevoEstadoId == 2) // "En Uso"
|
|
{
|
|
if (!cambiarEstadoDto.IdPublicacion.HasValue || !cambiarEstadoDto.IdSeccion.HasValue)
|
|
return (false, "Para el estado 'En Uso', se requiere Publicación y Sección.");
|
|
if (await _publicacionRepository.GetByIdSimpleAsync(cambiarEstadoDto.IdPublicacion.Value) == null)
|
|
return (false, "Publicación inválida.");
|
|
if (await _publiSeccionRepository.GetByIdAsync(cambiarEstadoDto.IdSeccion.Value) == null) // Asume GetByIdAsync en IPubliSeccionRepository
|
|
return (false, "Sección inválida.");
|
|
|
|
bobina.IdPublicacion = cambiarEstadoDto.IdPublicacion.Value;
|
|
bobina.IdSeccion = cambiarEstadoDto.IdSeccion.Value;
|
|
}
|
|
else
|
|
{ // Si no es "En Uso", limpiar estos campos
|
|
bobina.IdPublicacion = null;
|
|
bobina.IdSeccion = null;
|
|
}
|
|
|
|
var actualizado = await _stockBobinaRepository.UpdateAsync(bobina, idUsuario, transaction, $"Estado Cambiado a: {nuevoEstado.Denominacion}");
|
|
if (!actualizado) throw new DataException("Error al cambiar estado de la bobina.");
|
|
transaction.Commit();
|
|
return (true, null);
|
|
}
|
|
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); }
|
|
catch (Exception ex)
|
|
{
|
|
try { transaction.Rollback(); } catch { }
|
|
_logger.LogError(ex, "Error CambiarEstadoBobinaAsync ID: {IdBobina}", idBobina);
|
|
return (false, $"Error interno: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<(bool Exito, string? Error)> EliminarIngresoErroneoAsync(int idBobina, 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 bobina = await _stockBobinaRepository.GetByIdAsync(idBobina);
|
|
if (bobina == null) return (false, "Bobina no encontrada.");
|
|
if (bobina.IdEstadoBobina != 1) // Solo se pueden eliminar las "Disponibles" (ingresos erróneos)
|
|
return (false, "Solo se pueden eliminar ingresos de bobinas que estén en estado 'Disponible'.");
|
|
|
|
var eliminado = await _stockBobinaRepository.DeleteAsync(idBobina, idUsuario, transaction);
|
|
if (!eliminado) throw new DataException("Error al eliminar la bobina.");
|
|
transaction.Commit();
|
|
return (true, null);
|
|
}
|
|
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); }
|
|
catch (Exception ex)
|
|
{
|
|
try { transaction.Rollback(); } catch { }
|
|
_logger.LogError(ex, "Error EliminarIngresoErroneoAsync ID: {IdBobina}", idBobina);
|
|
return (false, $"Error interno: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
} |