feat: Implementación de Secciones, Recargos, Porc. Pago Dist. y backend E/S Dist.
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.
This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Contables;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
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;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public class EntradaSalidaDistService : IEntradaSalidaDistService
|
||||
{
|
||||
private readonly IEntradaSalidaDistRepository _esRepository;
|
||||
private readonly IPublicacionRepository _publicacionRepository;
|
||||
private readonly IDistribuidorRepository _distribuidorRepository;
|
||||
private readonly IPrecioRepository _precioRepository;
|
||||
private readonly IRecargoZonaRepository _recargoZonaRepository;
|
||||
private readonly IPorcPagoRepository _porcPagoRepository;
|
||||
private readonly ISaldoRepository _saldoRepository;
|
||||
private readonly IEmpresaRepository _empresaRepository; // Para obtener IdEmpresa de la publicación
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<EntradaSalidaDistService> _logger;
|
||||
|
||||
public EntradaSalidaDistService(
|
||||
IEntradaSalidaDistRepository esRepository,
|
||||
IPublicacionRepository publicacionRepository,
|
||||
IDistribuidorRepository distribuidorRepository,
|
||||
IPrecioRepository precioRepository,
|
||||
IRecargoZonaRepository recargoZonaRepository,
|
||||
IPorcPagoRepository porcPagoRepository,
|
||||
ISaldoRepository saldoRepository,
|
||||
IEmpresaRepository empresaRepository,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<EntradaSalidaDistService> logger)
|
||||
{
|
||||
_esRepository = esRepository;
|
||||
_publicacionRepository = publicacionRepository;
|
||||
_distribuidorRepository = distribuidorRepository;
|
||||
_precioRepository = precioRepository;
|
||||
_recargoZonaRepository = recargoZonaRepository;
|
||||
_porcPagoRepository = porcPagoRepository;
|
||||
_saldoRepository = saldoRepository;
|
||||
_empresaRepository = empresaRepository;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private async Task<EntradaSalidaDistDto> MapToDto(EntradaSalidaDist es)
|
||||
{
|
||||
if (es == null) return null!;
|
||||
|
||||
var publicacionData = await _publicacionRepository.GetByIdAsync(es.IdPublicacion);
|
||||
var distribuidorData = await _distribuidorRepository.GetByIdAsync(es.IdDistribuidor);
|
||||
|
||||
decimal montoCalculado = await CalcularMontoMovimiento(es.IdPublicacion, es.IdDistribuidor, es.Fecha, es.Cantidad, es.TipoMovimiento,
|
||||
es.IdPrecio, es.IdRecargo, es.IdPorcentaje, distribuidorData.Distribuidor?.IdZona);
|
||||
|
||||
|
||||
return new EntradaSalidaDistDto
|
||||
{
|
||||
IdParte = es.IdParte,
|
||||
IdPublicacion = es.IdPublicacion,
|
||||
NombrePublicacion = publicacionData.Publicacion?.Nombre ?? "N/A",
|
||||
IdEmpresaPublicacion = publicacionData.Publicacion?.IdEmpresa ?? 0,
|
||||
NombreEmpresaPublicacion = publicacionData.NombreEmpresa ?? "N/A",
|
||||
IdDistribuidor = es.IdDistribuidor,
|
||||
NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "N/A",
|
||||
Fecha = es.Fecha.ToString("yyyy-MM-dd"),
|
||||
TipoMovimiento = es.TipoMovimiento,
|
||||
Cantidad = es.Cantidad,
|
||||
Remito = es.Remito,
|
||||
Observacion = es.Observacion,
|
||||
MontoCalculado = montoCalculado
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<decimal> CalcularMontoMovimiento(int idPublicacion, int idDistribuidor, DateTime fecha, int cantidad, string tipoMovimiento,
|
||||
int idPrecio, int idRecargo, int idPorcentaje, int? idZonaDistribuidor)
|
||||
{
|
||||
if (tipoMovimiento == "Entrada") return 0; // Las entradas (devoluciones) no generan "debe" inmediato, ajustan el saldo.
|
||||
|
||||
var precioConfig = await _precioRepository.GetByIdAsync(idPrecio);
|
||||
if (precioConfig == null) throw new InvalidOperationException("Configuración de precio no encontrada para el movimiento.");
|
||||
|
||||
decimal precioDia = 0;
|
||||
DayOfWeek diaSemana = 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 (idRecargo > 0 && idZonaDistribuidor.HasValue) // El recargo se aplica por la zona del distribuidor
|
||||
{
|
||||
// Necesitamos encontrar el recargo activo para la publicación, la zona del distribuidor y la fecha
|
||||
var recargoConfig = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(idPublicacion, idZonaDistribuidor.Value, fecha);
|
||||
if (recargoConfig != null)
|
||||
{
|
||||
valorRecargo = recargoConfig.Valor;
|
||||
}
|
||||
}
|
||||
|
||||
decimal precioConRecargo = precioDia + valorRecargo;
|
||||
decimal montoBase = precioConRecargo * cantidad;
|
||||
|
||||
if (idPorcentaje > 0)
|
||||
{
|
||||
var porcConfig = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
|
||||
if (porcConfig != null)
|
||||
{
|
||||
return (montoBase / 100) * porcConfig.Porcentaje;
|
||||
}
|
||||
}
|
||||
return montoBase; // Si no hay porcentaje, se factura el 100% del precio con recargo
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<EntradaSalidaDistDto>> ObtenerTodosAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idPublicacion, int? idDistribuidor, string? tipoMovimiento)
|
||||
{
|
||||
var movimientos = await _esRepository.GetAllAsync(fechaDesde, fechaHasta, idPublicacion, idDistribuidor, tipoMovimiento);
|
||||
var dtos = new List<EntradaSalidaDistDto>();
|
||||
foreach (var mov in movimientos)
|
||||
{
|
||||
dtos.Add(await MapToDto(mov));
|
||||
}
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public async Task<EntradaSalidaDistDto?> ObtenerPorIdAsync(int idParte)
|
||||
{
|
||||
var movimiento = await _esRepository.GetByIdAsync(idParte);
|
||||
return movimiento == null ? null : await MapToDto(movimiento);
|
||||
}
|
||||
|
||||
public async Task<(EntradaSalidaDistDto? Movimiento, string? Error)> CrearMovimientoAsync(CreateEntradaSalidaDistDto createDto, int idUsuario)
|
||||
{
|
||||
var publicacion = await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion);
|
||||
if (publicacion == null || publicacion.Habilitada != true) return (null, "Publicación no válida o no habilitada.");
|
||||
|
||||
var distribuidor = await _distribuidorRepository.GetByIdSimpleAsync(createDto.IdDistribuidor);
|
||||
if (distribuidor == null) return (null, "Distribuidor no válido.");
|
||||
|
||||
if (await _esRepository.ExistsByRemitoAndTipoForPublicacionAsync(createDto.Remito, createDto.TipoMovimiento, createDto.IdPublicacion))
|
||||
{
|
||||
return (null, $"Ya existe un movimiento de '{createDto.TipoMovimiento}' con el remito N°{createDto.Remito} para esta publicación.");
|
||||
}
|
||||
|
||||
// Determinar IDs de Precio, Recargo y Porcentaje activos en la fecha del movimiento
|
||||
var precioActivo = await _precioRepository.GetActiveByPublicacionAndDateAsync(createDto.IdPublicacion, createDto.Fecha.Date);
|
||||
if (precioActivo == null) return (null, $"No hay un precio definido para la publicación '{publicacion.Nombre}' en la fecha {createDto.Fecha:dd/MM/yyyy}.");
|
||||
|
||||
RecargoZona? recargoActivo = null;
|
||||
if (distribuidor.IdZona.HasValue)
|
||||
{
|
||||
recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(createDto.IdPublicacion, distribuidor.IdZona.Value, createDto.Fecha.Date);
|
||||
}
|
||||
|
||||
var porcPagoActivo = await _porcPagoRepository.GetActiveByPublicacionDistribuidorAndDateAsync(createDto.IdPublicacion, createDto.IdDistribuidor, createDto.Fecha.Date);
|
||||
|
||||
var nuevoES = new EntradaSalidaDist
|
||||
{
|
||||
IdPublicacion = createDto.IdPublicacion,
|
||||
IdDistribuidor = createDto.IdDistribuidor,
|
||||
Fecha = createDto.Fecha.Date,
|
||||
TipoMovimiento = createDto.TipoMovimiento,
|
||||
Cantidad = createDto.Cantidad,
|
||||
Remito = createDto.Remito,
|
||||
Observacion = createDto.Observacion,
|
||||
IdPrecio = precioActivo.IdPrecio,
|
||||
IdRecargo = recargoActivo?.IdRecargo ?? 0, // 0 si no aplica
|
||||
IdPorcentaje = porcPagoActivo?.IdPorcentaje ?? 0 // 0 si no aplica
|
||||
};
|
||||
|
||||
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 esCreada = await _esRepository.CreateAsync(nuevoES, idUsuario, transaction);
|
||||
if (esCreada == null) throw new DataException("Error al registrar el movimiento.");
|
||||
|
||||
// Afectar Saldo
|
||||
decimal montoAfectacion = await CalcularMontoMovimiento(
|
||||
esCreada.IdPublicacion, esCreada.IdDistribuidor, esCreada.Fecha, esCreada.Cantidad, esCreada.TipoMovimiento,
|
||||
esCreada.IdPrecio, esCreada.IdRecargo, esCreada.IdPorcentaje, distribuidor.IdZona);
|
||||
|
||||
// Si es Salida, montoAfectacion es positivo (aumenta deuda). Si es Entrada, es 0 por CalcularMontoMovimiento,
|
||||
// pero para el saldo, una Entrada (devolución) debería restar del saldo deudor.
|
||||
// Por lo tanto, el monto real a restar del saldo si es Entrada sería el valor de esos ejemplares devueltos.
|
||||
if(esCreada.TipoMovimiento == "Entrada")
|
||||
{
|
||||
// Recalcular como si fuera salida para obtener el valor a acreditar/restar del saldo
|
||||
montoAfectacion = await CalcularMontoMovimiento(
|
||||
esCreada.IdPublicacion, esCreada.IdDistribuidor, esCreada.Fecha, esCreada.Cantidad, "Salida", // Forzar tipo Salida para cálculo de valor
|
||||
esCreada.IdPrecio, esCreada.IdRecargo, esCreada.IdPorcentaje, distribuidor.IdZona);
|
||||
montoAfectacion *= -1; // Hacerlo negativo para restar del saldo
|
||||
}
|
||||
|
||||
|
||||
bool saldoActualizado = await _saldoRepository.ModificarSaldoAsync("Distribuidores", esCreada.IdDistribuidor, publicacion.IdEmpresa, montoAfectacion, transaction);
|
||||
if (!saldoActualizado) throw new DataException("Error al actualizar el saldo del distribuidor.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Movimiento ID {Id} creado y saldo afectado por Usuario ID {UserId}.", esCreada.IdParte, idUsuario);
|
||||
return (await MapToDto(esCreada), null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error CrearMovimientoAsync para Pub ID {IdPub}", createDto.IdPublicacion);
|
||||
return (null, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaDistDto updateDto, int idUsuario)
|
||||
{
|
||||
// La actualización de un movimiento que afecta saldos es compleja.
|
||||
// Si cambia la cantidad, el monto original y el nuevo deben calcularse,
|
||||
// y la diferencia debe aplicarse al saldo.
|
||||
// Por ahora, este DTO solo permite cambiar Cantidad y Observacion.
|
||||
// Cambiar otros campos como Fecha, Publicacion, Distribuidor implicaría recalcular todo
|
||||
// y posiblemente anular el movimiento original y crear uno nuevo.
|
||||
|
||||
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 esExistente = await _esRepository.GetByIdAsync(idParte);
|
||||
if (esExistente == null) return (false, "Movimiento no encontrado.");
|
||||
|
||||
var publicacion = await _publicacionRepository.GetByIdSimpleAsync(esExistente.IdPublicacion);
|
||||
if (publicacion == null) return (false, "Publicación asociada no encontrada."); // Muy raro
|
||||
var distribuidor = await _distribuidorRepository.GetByIdSimpleAsync(esExistente.IdDistribuidor);
|
||||
if (distribuidor == null) return (false, "Distribuidor asociado no encontrado.");
|
||||
|
||||
|
||||
// 1. Calcular monto del movimiento original (antes de la actualización)
|
||||
decimal montoOriginal = await CalcularMontoMovimiento(
|
||||
esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, esExistente.TipoMovimiento,
|
||||
esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona);
|
||||
if(esExistente.TipoMovimiento == "Entrada") montoOriginal *= -1; // Para revertir
|
||||
|
||||
// 2. Actualizar la entidad en la BD (esto también guarda en historial)
|
||||
var esParaActualizar = new EntradaSalidaDist
|
||||
{
|
||||
IdParte = esExistente.IdParte,
|
||||
IdPublicacion = esExistente.IdPublicacion, // No cambia
|
||||
IdDistribuidor = esExistente.IdDistribuidor, // No cambia
|
||||
Fecha = esExistente.Fecha, // No cambia
|
||||
TipoMovimiento = esExistente.TipoMovimiento, // No cambia
|
||||
Cantidad = updateDto.Cantidad, // Nuevo valor
|
||||
Remito = esExistente.Remito, // No cambia
|
||||
Observacion = updateDto.Observacion, // Nuevo valor
|
||||
IdPrecio = esExistente.IdPrecio, // No cambia
|
||||
IdRecargo = esExistente.IdRecargo, // No cambia
|
||||
IdPorcentaje = esExistente.IdPorcentaje // No cambia
|
||||
};
|
||||
var actualizado = await _esRepository.UpdateAsync(esParaActualizar, idUsuario, transaction);
|
||||
if (!actualizado) throw new DataException("Error al actualizar el movimiento.");
|
||||
|
||||
// 3. Calcular monto del movimiento nuevo (después de la actualización)
|
||||
decimal montoNuevo = await CalcularMontoMovimiento(
|
||||
esParaActualizar.IdPublicacion, esParaActualizar.IdDistribuidor, esParaActualizar.Fecha, esParaActualizar.Cantidad, esParaActualizar.TipoMovimiento,
|
||||
esParaActualizar.IdPrecio, esParaActualizar.IdRecargo, esParaActualizar.IdPorcentaje, distribuidor.IdZona);
|
||||
if(esParaActualizar.TipoMovimiento == "Entrada") montoNuevo *= -1;
|
||||
|
||||
|
||||
// 4. Ajustar saldo con la diferencia
|
||||
decimal diferenciaAjusteSaldo = montoNuevo - montoOriginal;
|
||||
if (diferenciaAjusteSaldo != 0)
|
||||
{
|
||||
bool saldoActualizado = await _saldoRepository.ModificarSaldoAsync("Distribuidores", esExistente.IdDistribuidor, publicacion.IdEmpresa, diferenciaAjusteSaldo, transaction);
|
||||
if (!saldoActualizado) throw new DataException("Error al ajustar el saldo del distribuidor tras la actualización.");
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Movimiento ID {Id} actualizado y saldo ajustado por Usuario ID {UserId}.", idParte, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Movimiento no encontrado."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error ActualizarMovimientoAsync ID: {Id}", idParte);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, 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 esExistente = await _esRepository.GetByIdAsync(idParte);
|
||||
if (esExistente == null) return (false, "Movimiento no encontrado.");
|
||||
|
||||
var publicacion = await _publicacionRepository.GetByIdSimpleAsync(esExistente.IdPublicacion);
|
||||
if (publicacion == null) return (false, "Publicación asociada no encontrada.");
|
||||
var distribuidor = await _distribuidorRepository.GetByIdSimpleAsync(esExistente.IdDistribuidor);
|
||||
if (distribuidor == null) return (false, "Distribuidor asociado no encontrado.");
|
||||
|
||||
// 1. Calcular el monto del movimiento a eliminar para revertir el saldo
|
||||
decimal montoReversion = await CalcularMontoMovimiento(
|
||||
esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, esExistente.TipoMovimiento,
|
||||
esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona);
|
||||
|
||||
// Si es Salida, el monto es positivo, al revertir restamos.
|
||||
// Si es Entrada, el monto es 0 (por Calcular...), pero su efecto en saldo fue negativo, al revertir sumamos el valor.
|
||||
if(esExistente.TipoMovimiento == "Salida") {
|
||||
montoReversion *= -1; // Para restar del saldo lo que se sumó
|
||||
} else if (esExistente.TipoMovimiento == "Entrada") {
|
||||
// Recalcular el valor como si fuera salida para saber cuánto se restó del saldo
|
||||
montoReversion = await CalcularMontoMovimiento(
|
||||
esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, "Salida",
|
||||
esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona);
|
||||
// No se multiplica por -1 aquí, porque el saldo ya lo tiene restado, al eliminar revertimos sumando.
|
||||
}
|
||||
|
||||
|
||||
// 2. Eliminar el registro (esto también guarda en historial)
|
||||
var eliminado = await _esRepository.DeleteAsync(idParte, idUsuario, transaction);
|
||||
if (!eliminado) throw new DataException("Error al eliminar el movimiento.");
|
||||
|
||||
// 3. Ajustar Saldo
|
||||
bool saldoActualizado = await _saldoRepository.ModificarSaldoAsync("Distribuidores", esExistente.IdDistribuidor, publicacion.IdEmpresa, montoReversion, transaction);
|
||||
if (!saldoActualizado) throw new DataException("Error al revertir el saldo del distribuidor tras la eliminación.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Movimiento ID {Id} eliminado y saldo revertido por Usuario ID {UserId}.", idParte, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Movimiento no encontrado."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error EliminarMovimientoAsync ID: {Id}", idParte);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public interface IEntradaSalidaDistService
|
||||
{
|
||||
Task<IEnumerable<EntradaSalidaDistDto>> ObtenerTodosAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idPublicacion, int? idDistribuidor, string? tipoMovimiento);
|
||||
Task<EntradaSalidaDistDto?> ObtenerPorIdAsync(int idParte);
|
||||
Task<(EntradaSalidaDistDto? Movimiento, string? Error)> CrearMovimientoAsync(CreateEntradaSalidaDistDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaDistDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public interface IPorcMonCanillaService
|
||||
{
|
||||
Task<IEnumerable<PorcMonCanillaDto>> ObtenerPorPublicacionIdAsync(int idPublicacion);
|
||||
Task<PorcMonCanillaDto?> ObtenerPorIdAsync(int idPorcMon);
|
||||
Task<(PorcMonCanillaDto? Item, string? Error)> CrearAsync(CreatePorcMonCanillaDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int idPorcMon, UpdatePorcMonCanillaDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int idPorcMon, int idUsuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public interface IPorcPagoService
|
||||
{
|
||||
Task<IEnumerable<PorcPagoDto>> ObtenerPorPublicacionIdAsync(int idPublicacion);
|
||||
Task<PorcPagoDto?> ObtenerPorIdAsync(int idPorcentaje);
|
||||
Task<(PorcPagoDto? PorcPago, string? Error)> CrearAsync(CreatePorcPagoDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int idPorcentaje, UpdatePorcPagoDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int idPorcentaje, int idUsuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public interface IPubliSeccionService
|
||||
{
|
||||
Task<IEnumerable<PubliSeccionDto>> ObtenerPorPublicacionIdAsync(int idPublicacion, bool? soloActivas = null);
|
||||
Task<PubliSeccionDto?> ObtenerPorIdAsync(int idSeccion);
|
||||
Task<(PubliSeccionDto? Seccion, string? Error)> CrearAsync(CreatePubliSeccionDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int idSeccion, UpdatePubliSeccionDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int idSeccion, int idUsuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public interface IRecargoZonaService
|
||||
{
|
||||
Task<IEnumerable<RecargoZonaDto>> ObtenerPorPublicacionIdAsync(int idPublicacion);
|
||||
Task<RecargoZonaDto?> ObtenerPorIdAsync(int idRecargo);
|
||||
Task<(RecargoZonaDto? Recargo, string? Error)> CrearAsync(CreateRecargoZonaDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int idRecargo, UpdateRecargoZonaDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int idRecargo, int idUsuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public interface ISalidaOtroDestinoService
|
||||
{
|
||||
Task<IEnumerable<SalidaOtroDestinoDto>> ObtenerTodosAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idPublicacion, int? idDestino);
|
||||
Task<SalidaOtroDestinoDto?> ObtenerPorIdAsync(int idParte);
|
||||
Task<(SalidaOtroDestinoDto? Salida, string? Error)> CrearAsync(CreateSalidaOtroDestinoDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int idParte, UpdateSalidaOtroDestinoDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int idParte, int idUsuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
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 PorcPagoService : IPorcPagoService
|
||||
{
|
||||
private readonly IPorcPagoRepository _porcPagoRepository;
|
||||
private readonly IPublicacionRepository _publicacionRepository;
|
||||
private readonly IDistribuidorRepository _distribuidorRepository;
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<PorcPagoService> _logger;
|
||||
|
||||
public PorcPagoService(
|
||||
IPorcPagoRepository porcPagoRepository,
|
||||
IPublicacionRepository publicacionRepository,
|
||||
IDistribuidorRepository distribuidorRepository,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<PorcPagoService> logger)
|
||||
{
|
||||
_porcPagoRepository = porcPagoRepository;
|
||||
_publicacionRepository = publicacionRepository;
|
||||
_distribuidorRepository = distribuidorRepository;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private PorcPagoDto MapToDto((PorcPago PorcPago, string NombreDistribuidor) data) => new PorcPagoDto
|
||||
{
|
||||
IdPorcentaje = data.PorcPago.IdPorcentaje,
|
||||
IdPublicacion = data.PorcPago.IdPublicacion,
|
||||
IdDistribuidor = data.PorcPago.IdDistribuidor,
|
||||
NombreDistribuidor = data.NombreDistribuidor,
|
||||
VigenciaD = data.PorcPago.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = data.PorcPago.VigenciaH?.ToString("yyyy-MM-dd"),
|
||||
Porcentaje = data.PorcPago.Porcentaje
|
||||
};
|
||||
|
||||
private async Task<PorcPagoDto> MapToDtoWithLookup(PorcPago porcPago)
|
||||
{
|
||||
var distribuidorData = await _distribuidorRepository.GetByIdAsync(porcPago.IdDistribuidor);
|
||||
return new PorcPagoDto
|
||||
{
|
||||
IdPorcentaje = porcPago.IdPorcentaje,
|
||||
IdPublicacion = porcPago.IdPublicacion,
|
||||
IdDistribuidor = porcPago.IdDistribuidor,
|
||||
NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "Distribuidor Desconocido",
|
||||
VigenciaD = porcPago.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = porcPago.VigenciaH?.ToString("yyyy-MM-dd"),
|
||||
Porcentaje = porcPago.Porcentaje
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PorcPagoDto>> ObtenerPorPublicacionIdAsync(int idPublicacion)
|
||||
{
|
||||
var data = await _porcPagoRepository.GetByPublicacionIdAsync(idPublicacion);
|
||||
return data.Select(MapToDto);
|
||||
}
|
||||
|
||||
public async Task<PorcPagoDto?> ObtenerPorIdAsync(int idPorcentaje)
|
||||
{
|
||||
var porcPago = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
|
||||
if (porcPago == null) return null;
|
||||
return await MapToDtoWithLookup(porcPago);
|
||||
}
|
||||
|
||||
public async Task<(PorcPagoDto? PorcPago, string? Error)> CrearAsync(CreatePorcPagoDto createDto, int idUsuario)
|
||||
{
|
||||
if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null)
|
||||
return (null, "La publicación especificada no existe.");
|
||||
var distribuidorData = await _distribuidorRepository.GetByIdAsync(createDto.IdDistribuidor);
|
||||
if (distribuidorData.Distribuidor == null)
|
||||
return (null, "El distribuidor especificado no existe.");
|
||||
|
||||
var porcPagoActivo = await _porcPagoRepository.GetActiveByPublicacionDistribuidorAndDateAsync(createDto.IdPublicacion, createDto.IdDistribuidor, createDto.VigenciaD.Date);
|
||||
if (porcPagoActivo != null)
|
||||
{
|
||||
return (null, $"Ya existe un porcentaje de pago activo para esta publicación y distribuidor en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Cierre el período anterior primero.");
|
||||
}
|
||||
|
||||
var nuevoPorcPago = new PorcPago
|
||||
{
|
||||
IdPublicacion = createDto.IdPublicacion,
|
||||
IdDistribuidor = createDto.IdDistribuidor,
|
||||
VigenciaD = createDto.VigenciaD.Date,
|
||||
VigenciaH = null,
|
||||
Porcentaje = createDto.Porcentaje
|
||||
};
|
||||
|
||||
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 porcPagoAnterior = await _porcPagoRepository.GetPreviousActivePorcPagoAsync(createDto.IdPublicacion, createDto.IdDistribuidor, nuevoPorcPago.VigenciaD, transaction);
|
||||
if (porcPagoAnterior != null)
|
||||
{
|
||||
if (porcPagoAnterior.VigenciaD.Date >= nuevoPorcPago.VigenciaD.Date)
|
||||
{
|
||||
transaction.Rollback();
|
||||
return (null, $"La fecha de inicio del nuevo porcentaje ({nuevoPorcPago.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último porcentaje vigente ({porcPagoAnterior.VigenciaD:dd/MM/yyyy}) para este distribuidor.");
|
||||
}
|
||||
porcPagoAnterior.VigenciaH = nuevoPorcPago.VigenciaD.AddDays(-1);
|
||||
await _porcPagoRepository.UpdateAsync(porcPagoAnterior, idUsuario, transaction);
|
||||
}
|
||||
|
||||
var porcPagoCreado = await _porcPagoRepository.CreateAsync(nuevoPorcPago, idUsuario, transaction);
|
||||
if (porcPagoCreado == null) throw new DataException("Error al crear el porcentaje de pago.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("PorcPago ID {Id} creado por Usuario ID {UserId}.", porcPagoCreado.IdPorcentaje, idUsuario);
|
||||
return (new PorcPagoDto { // Construir DTO manualmente ya que tenemos NombreDistribuidor
|
||||
IdPorcentaje = porcPagoCreado.IdPorcentaje,
|
||||
IdPublicacion = porcPagoCreado.IdPublicacion,
|
||||
IdDistribuidor = porcPagoCreado.IdDistribuidor,
|
||||
NombreDistribuidor = distribuidorData.Distribuidor.Nombre,
|
||||
VigenciaD = porcPagoCreado.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = porcPagoCreado.VigenciaH?.ToString("yyyy-MM-dd"),
|
||||
Porcentaje = porcPagoCreado.Porcentaje
|
||||
}, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error CrearAsync PorcPago para Pub ID {IdPub}, Dist ID {IdDist}", createDto.IdPublicacion, createDto.IdDistribuidor);
|
||||
return (null, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPorcentaje, UpdatePorcPagoDto 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 porcPagoExistente = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
|
||||
if (porcPagoExistente == null) return (false, "Porcentaje de pago no encontrado.");
|
||||
|
||||
if (updateDto.VigenciaH.HasValue && updateDto.VigenciaH.Value.Date < porcPagoExistente.VigenciaD.Date)
|
||||
return (false, "Vigencia Hasta no puede ser anterior a Vigencia Desde.");
|
||||
|
||||
if (updateDto.VigenciaH.HasValue)
|
||||
{
|
||||
var porcentajesPubDist = await _porcPagoRepository.GetByPublicacionIdAsync(porcPagoExistente.IdPublicacion);
|
||||
var porcentajesPosteriores = porcentajesPubDist
|
||||
.Where(p => p.PorcPago.IdDistribuidor == porcPagoExistente.IdDistribuidor &&
|
||||
p.PorcPago.IdPorcentaje != idPorcentaje &&
|
||||
p.PorcPago.VigenciaD.Date <= updateDto.VigenciaH.Value.Date &&
|
||||
p.PorcPago.VigenciaD.Date > porcPagoExistente.VigenciaD.Date);
|
||||
if(porcentajesPosteriores.Any())
|
||||
{
|
||||
return (false, "No se puede cerrar este período porque existen porcentajes posteriores para este distribuidor que se solaparían.");
|
||||
}
|
||||
}
|
||||
|
||||
porcPagoExistente.Porcentaje = updateDto.Porcentaje;
|
||||
if (updateDto.VigenciaH.HasValue)
|
||||
{
|
||||
porcPagoExistente.VigenciaH = updateDto.VigenciaH.Value.Date;
|
||||
}
|
||||
|
||||
var actualizado = await _porcPagoRepository.UpdateAsync(porcPagoExistente, idUsuario, transaction);
|
||||
if (!actualizado) throw new DataException("Error al actualizar porcentaje de pago.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("PorcPago ID {Id} actualizado por Usuario ID {UserId}.", idPorcentaje, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Porcentaje no encontrado."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error ActualizarAsync PorcPago ID: {Id}", idPorcentaje);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarAsync(int idPorcentaje, 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 porcPagoAEliminar = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
|
||||
if (porcPagoAEliminar == null) return (false, "Porcentaje de pago no encontrado.");
|
||||
|
||||
if (porcPagoAEliminar.VigenciaH == null)
|
||||
{
|
||||
var todosPorcPubDistData = await _porcPagoRepository.GetByPublicacionIdAsync(porcPagoAEliminar.IdPublicacion);
|
||||
var todosPorcPubDist = todosPorcPubDistData
|
||||
.Where(p => p.PorcPago.IdDistribuidor == porcPagoAEliminar.IdDistribuidor)
|
||||
.Select(p => p.PorcPago)
|
||||
.OrderByDescending(p => p.VigenciaD).ToList();
|
||||
|
||||
var indiceActual = todosPorcPubDist.FindIndex(p => p.IdPorcentaje == idPorcentaje);
|
||||
if(indiceActual != -1 && (indiceActual + 1) < todosPorcPubDist.Count)
|
||||
{
|
||||
var porcPagoAnteriorDirecto = todosPorcPubDist[indiceActual + 1];
|
||||
if(porcPagoAnteriorDirecto.VigenciaH.HasValue &&
|
||||
porcPagoAnteriorDirecto.VigenciaH.Value.Date == porcPagoAEliminar.VigenciaD.AddDays(-1).Date)
|
||||
{
|
||||
porcPagoAnteriorDirecto.VigenciaH = null;
|
||||
await _porcPagoRepository.UpdateAsync(porcPagoAnteriorDirecto, idUsuario, transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var eliminado = await _porcPagoRepository.DeleteAsync(idPorcentaje, idUsuario, transaction);
|
||||
if (!eliminado) throw new DataException("Error al eliminar porcentaje de pago.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("PorcPago ID {Id} eliminado por Usuario ID {UserId}.", idPorcentaje, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Porcentaje no encontrado."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error EliminarAsync PorcPago ID: {Id}", idPorcentaje);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
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 PubliSeccionService : IPubliSeccionService
|
||||
{
|
||||
private readonly IPubliSeccionRepository _publiSeccionRepository;
|
||||
private readonly IPublicacionRepository _publicacionRepository; // Para validar IdPublicacion
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<PubliSeccionService> _logger;
|
||||
|
||||
public PubliSeccionService(
|
||||
IPubliSeccionRepository publiSeccionRepository,
|
||||
IPublicacionRepository publicacionRepository,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<PubliSeccionService> logger)
|
||||
{
|
||||
_publiSeccionRepository = publiSeccionRepository;
|
||||
_publicacionRepository = publicacionRepository;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private PubliSeccionDto MapToDto(PubliSeccion seccion) => new PubliSeccionDto
|
||||
{
|
||||
IdSeccion = seccion.IdSeccion,
|
||||
IdPublicacion = seccion.IdPublicacion,
|
||||
Nombre = seccion.Nombre,
|
||||
Estado = seccion.Estado
|
||||
};
|
||||
|
||||
public async Task<IEnumerable<PubliSeccionDto>> ObtenerPorPublicacionIdAsync(int idPublicacion, bool? soloActivas = null)
|
||||
{
|
||||
var secciones = await _publiSeccionRepository.GetByPublicacionIdAsync(idPublicacion, soloActivas);
|
||||
return secciones.Select(MapToDto);
|
||||
}
|
||||
|
||||
public async Task<PubliSeccionDto?> ObtenerPorIdAsync(int idSeccion)
|
||||
{
|
||||
var seccion = await _publiSeccionRepository.GetByIdAsync(idSeccion);
|
||||
return seccion == null ? null : MapToDto(seccion);
|
||||
}
|
||||
|
||||
public async Task<(PubliSeccionDto? Seccion, string? Error)> CrearAsync(CreatePubliSeccionDto createDto, int idUsuario)
|
||||
{
|
||||
if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null)
|
||||
return (null, "La publicación especificada no existe.");
|
||||
if (await _publiSeccionRepository.ExistsByNameInPublicacionAsync(createDto.Nombre, createDto.IdPublicacion))
|
||||
return (null, "Ya existe una sección con ese nombre para esta publicación.");
|
||||
|
||||
var nuevaSeccion = new PubliSeccion
|
||||
{
|
||||
IdPublicacion = createDto.IdPublicacion,
|
||||
Nombre = createDto.Nombre,
|
||||
Estado = createDto.Estado
|
||||
};
|
||||
|
||||
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 seccionCreada = await _publiSeccionRepository.CreateAsync(nuevaSeccion, idUsuario, transaction);
|
||||
if (seccionCreada == null) throw new DataException("Error al crear la sección.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Sección ID {Id} creada por Usuario ID {UserId}.", seccionCreada.IdSeccion, idUsuario);
|
||||
return (MapToDto(seccionCreada), null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error CrearAsync PubliSeccion para Pub ID {IdPub}, Nombre: {Nombre}", createDto.IdPublicacion, createDto.Nombre);
|
||||
return (null, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idSeccion, UpdatePubliSeccionDto 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 seccionExistente = await _publiSeccionRepository.GetByIdAsync(idSeccion); // Obtener dentro de TX
|
||||
if (seccionExistente == null) return (false, "Sección no encontrada.");
|
||||
|
||||
// Validar unicidad de nombre solo si el nombre ha cambiado
|
||||
if (seccionExistente.Nombre != updateDto.Nombre &&
|
||||
await _publiSeccionRepository.ExistsByNameInPublicacionAsync(updateDto.Nombre, seccionExistente.IdPublicacion, idSeccion))
|
||||
{
|
||||
return (false, "Ya existe otra sección con ese nombre para esta publicación.");
|
||||
}
|
||||
|
||||
seccionExistente.Nombre = updateDto.Nombre;
|
||||
seccionExistente.Estado = updateDto.Estado;
|
||||
|
||||
var actualizado = await _publiSeccionRepository.UpdateAsync(seccionExistente, idUsuario, transaction);
|
||||
if (!actualizado) throw new DataException("Error al actualizar la sección.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Sección ID {Id} actualizada por Usuario ID {UserId}.", idSeccion, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Sección no encontrada."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error ActualizarAsync PubliSeccion ID: {Id}", idSeccion);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarAsync(int idSeccion, 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 seccionExistente = await _publiSeccionRepository.GetByIdAsync(idSeccion); // Obtener dentro de TX
|
||||
if (seccionExistente == null) return (false, "Sección no encontrada.");
|
||||
|
||||
if (await _publiSeccionRepository.IsInUseAsync(idSeccion))
|
||||
{
|
||||
return (false, "No se puede eliminar. La sección está siendo utilizada en registros de tiradas o stock de bobinas.");
|
||||
}
|
||||
|
||||
var eliminado = await _publiSeccionRepository.DeleteAsync(idSeccion, idUsuario, transaction);
|
||||
if (!eliminado) throw new DataException("Error al eliminar la sección.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Sección ID {Id} eliminada por Usuario ID {UserId}.", idSeccion, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Sección no encontrada."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error EliminarAsync PubliSeccion ID: {Id}", idSeccion);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
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 RecargoZonaService : IRecargoZonaService
|
||||
{
|
||||
private readonly IRecargoZonaRepository _recargoZonaRepository;
|
||||
private readonly IPublicacionRepository _publicacionRepository;
|
||||
private readonly IZonaRepository _zonaRepository;
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<RecargoZonaService> _logger;
|
||||
|
||||
public RecargoZonaService(
|
||||
IRecargoZonaRepository recargoZonaRepository,
|
||||
IPublicacionRepository publicacionRepository,
|
||||
IZonaRepository zonaRepository,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<RecargoZonaService> logger)
|
||||
{
|
||||
_recargoZonaRepository = recargoZonaRepository;
|
||||
_publicacionRepository = publicacionRepository;
|
||||
_zonaRepository = zonaRepository;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private RecargoZonaDto MapToDto((RecargoZona Recargo, string NombreZona) data) => new RecargoZonaDto
|
||||
{
|
||||
IdRecargo = data.Recargo.IdRecargo,
|
||||
IdPublicacion = data.Recargo.IdPublicacion,
|
||||
IdZona = data.Recargo.IdZona,
|
||||
NombreZona = data.NombreZona,
|
||||
VigenciaD = data.Recargo.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = data.Recargo.VigenciaH?.ToString("yyyy-MM-dd"),
|
||||
Valor = data.Recargo.Valor
|
||||
};
|
||||
|
||||
// Helper para mapear cuando solo tenemos el RecargoZona y necesitamos buscar NombreZona
|
||||
private async Task<RecargoZonaDto> MapToDtoWithZonaLookup(RecargoZona recargo)
|
||||
{
|
||||
var zona = await _zonaRepository.GetByIdAsync(recargo.IdZona); // zonaRepository.GetByIdAsync devuelve ZonaDto
|
||||
return new RecargoZonaDto
|
||||
{
|
||||
IdRecargo = recargo.IdRecargo,
|
||||
IdPublicacion = recargo.IdPublicacion,
|
||||
IdZona = recargo.IdZona,
|
||||
NombreZona = zona?.Nombre ?? "Zona Desconocida/Inactiva", // Usar la propiedad Nombre del ZonaDto
|
||||
VigenciaD = recargo.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = recargo.VigenciaH?.ToString("yyyy-MM-dd"),
|
||||
Valor = recargo.Valor
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<RecargoZonaDto>> ObtenerPorPublicacionIdAsync(int idPublicacion)
|
||||
{
|
||||
var recargosData = await _recargoZonaRepository.GetByPublicacionIdAsync(idPublicacion);
|
||||
return recargosData
|
||||
.Select(rd => MapToDto(rd)) // MapToDto ahora devuelve RecargoZonaDto?
|
||||
.Where(dto => dto != null)
|
||||
.Select(dto => dto!); // Cast no nulo
|
||||
}
|
||||
|
||||
public async Task<RecargoZonaDto?> ObtenerPorIdAsync(int idRecargo)
|
||||
{
|
||||
var recargo = await _recargoZonaRepository.GetByIdAsync(idRecargo);
|
||||
if (recargo == null) return null;
|
||||
|
||||
var zona = await _zonaRepository.GetByIdAsync(recargo.IdZona); // Obtiene ZonaDto
|
||||
return new RecargoZonaDto {
|
||||
IdRecargo = recargo.IdRecargo,
|
||||
IdPublicacion = recargo.IdPublicacion,
|
||||
IdZona = recargo.IdZona,
|
||||
NombreZona = zona?.Nombre ?? "Zona Desconocida/Inactiva",
|
||||
VigenciaD = recargo.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = recargo.VigenciaH?.ToString("yyyy-MM-dd"),
|
||||
Valor = recargo.Valor
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<(RecargoZonaDto? Recargo, string? Error)> CrearAsync(CreateRecargoZonaDto createDto, int idUsuario)
|
||||
{
|
||||
if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null)
|
||||
return (null, "La publicación especificada no existe.");
|
||||
var zona = await _zonaRepository.GetByIdAsync(createDto.IdZona); // Devuelve ZonaDto
|
||||
if (zona == null)
|
||||
return (null, "La zona especificada no existe o no está activa.");
|
||||
|
||||
// Usar createDto.VigenciaD directamente que ya es DateTime
|
||||
var recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(createDto.IdPublicacion, createDto.IdZona, createDto.VigenciaD.Date);
|
||||
if (recargoActivo != null)
|
||||
{
|
||||
return (null, $"Ya existe un recargo activo para esta publicación y zona en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Primero debe cerrar el período anterior.");
|
||||
}
|
||||
|
||||
var nuevoRecargo = new RecargoZona
|
||||
{
|
||||
IdPublicacion = createDto.IdPublicacion,
|
||||
IdZona = createDto.IdZona,
|
||||
VigenciaD = createDto.VigenciaD.Date,
|
||||
VigenciaH = null,
|
||||
Valor = createDto.Valor
|
||||
};
|
||||
|
||||
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 recargoAnterior = await _recargoZonaRepository.GetPreviousActiveRecargoAsync(createDto.IdPublicacion, createDto.IdZona, nuevoRecargo.VigenciaD, transaction);
|
||||
if (recargoAnterior != null)
|
||||
{
|
||||
if (recargoAnterior.VigenciaD.Date >= nuevoRecargo.VigenciaD.Date) // Comparar solo fechas
|
||||
{
|
||||
transaction.Rollback();
|
||||
return (null, $"La fecha de inicio del nuevo recargo ({nuevoRecargo.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último recargo vigente ({recargoAnterior.VigenciaD:dd/MM/yyyy}) para esta zona.");
|
||||
}
|
||||
recargoAnterior.VigenciaH = nuevoRecargo.VigenciaD.AddDays(-1);
|
||||
await _recargoZonaRepository.UpdateAsync(recargoAnterior, idUsuario, transaction);
|
||||
}
|
||||
|
||||
var recargoCreado = await _recargoZonaRepository.CreateAsync(nuevoRecargo, idUsuario, transaction);
|
||||
if (recargoCreado == null) throw new DataException("Error al crear el recargo.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Recargo ID {Id} creado por Usuario ID {UserId}.", recargoCreado.IdRecargo, idUsuario);
|
||||
// Pasar el nombre de la zona ya obtenido
|
||||
return (new RecargoZonaDto {
|
||||
IdRecargo = recargoCreado.IdRecargo, IdPublicacion = recargoCreado.IdPublicacion, IdZona = recargoCreado.IdZona,
|
||||
NombreZona = zona.Nombre, VigenciaD = recargoCreado.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = recargoCreado.VigenciaH?.ToString("yyyy-MM-dd"), Valor = recargoCreado.Valor
|
||||
}, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error CrearAsync RecargoZona para Pub ID {IdPub}, Zona ID {IdZona}", createDto.IdPublicacion, createDto.IdZona);
|
||||
return (null, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idRecargo, UpdateRecargoZonaDto 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 recargoExistente = await _recargoZonaRepository.GetByIdAsync(idRecargo);
|
||||
if (recargoExistente == null) return (false, "Recargo por zona no encontrado.");
|
||||
|
||||
if (updateDto.VigenciaH.HasValue && updateDto.VigenciaH.Value.Date < recargoExistente.VigenciaD.Date)
|
||||
return (false, "Vigencia Hasta no puede ser anterior a Vigencia Desde.");
|
||||
|
||||
if (updateDto.VigenciaH.HasValue)
|
||||
{
|
||||
var recargosDeLaPublicacion = await _recargoZonaRepository.GetByPublicacionIdAsync(recargoExistente.IdPublicacion);
|
||||
var recargosPosteriores = recargosDeLaPublicacion
|
||||
.Where(r => r.Recargo.IdZona == recargoExistente.IdZona &&
|
||||
r.Recargo.IdRecargo != idRecargo &&
|
||||
r.Recargo.VigenciaD.Date <= updateDto.VigenciaH.Value.Date &&
|
||||
r.Recargo.VigenciaD.Date > recargoExistente.VigenciaD.Date);
|
||||
if(recargosPosteriores.Any())
|
||||
{
|
||||
return (false, "No se puede cerrar este período porque existen recargos posteriores para esta zona que se solaparían.");
|
||||
}
|
||||
}
|
||||
|
||||
recargoExistente.Valor = updateDto.Valor;
|
||||
if (updateDto.VigenciaH.HasValue)
|
||||
{
|
||||
recargoExistente.VigenciaH = updateDto.VigenciaH.Value.Date;
|
||||
}
|
||||
|
||||
var actualizado = await _recargoZonaRepository.UpdateAsync(recargoExistente, idUsuario, transaction);
|
||||
if (!actualizado) throw new DataException("Error al actualizar recargo.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Recargo ID {Id} actualizado por Usuario ID {UserId}.", idRecargo, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Recargo no encontrado."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error ActualizarAsync RecargoZona ID: {Id}", idRecargo);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarAsync(int idRecargo, 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 recargoAEliminar = await _recargoZonaRepository.GetByIdAsync(idRecargo);
|
||||
if (recargoAEliminar == null) return (false, "Recargo no encontrado.");
|
||||
|
||||
if (recargoAEliminar.VigenciaH == null)
|
||||
{
|
||||
var todosRecargosPubZonaData = await _recargoZonaRepository.GetByPublicacionIdAsync(recargoAEliminar.IdPublicacion);
|
||||
var todosRecargosPubZona = todosRecargosPubZonaData
|
||||
.Where(r => r.Recargo.IdZona == recargoAEliminar.IdZona)
|
||||
.Select(r => r.Recargo)
|
||||
.OrderByDescending(r => r.VigenciaD).ToList();
|
||||
|
||||
var indiceActual = todosRecargosPubZona.FindIndex(r => r.IdRecargo == idRecargo);
|
||||
if(indiceActual != -1 && (indiceActual + 1) < todosRecargosPubZona.Count)
|
||||
{
|
||||
var recargoAnteriorDirecto = todosRecargosPubZona[indiceActual + 1];
|
||||
if(recargoAnteriorDirecto.VigenciaH.HasValue && recargoAnteriorDirecto.VigenciaH.Value.Date == recargoAEliminar.VigenciaD.AddDays(-1).Date)
|
||||
{
|
||||
recargoAnteriorDirecto.VigenciaH = null;
|
||||
await _recargoZonaRepository.UpdateAsync(recargoAnteriorDirecto, idUsuario, transaction);
|
||||
_logger.LogInformation("Recargo anterior ID {IdRecargoAnterior} reabierto tras eliminación de Recargo ID {IdRecargoEliminado}.", recargoAnteriorDirecto.IdRecargo, idRecargo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var eliminado = await _recargoZonaRepository.DeleteAsync(idRecargo, idUsuario, transaction);
|
||||
if (!eliminado) throw new DataException("Error al eliminar recargo.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Recargo ID {Id} eliminado por Usuario ID {UserId}.", idRecargo, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Recargo no encontrado."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error EliminarAsync RecargoZona ID: {Id}", idRecargo);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
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 SalidaOtroDestinoService : ISalidaOtroDestinoService
|
||||
{
|
||||
private readonly ISalidaOtroDestinoRepository _salidaRepository;
|
||||
private readonly IPublicacionRepository _publicacionRepository;
|
||||
private readonly IOtroDestinoRepository _otroDestinoRepository;
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<SalidaOtroDestinoService> _logger;
|
||||
|
||||
public SalidaOtroDestinoService(
|
||||
ISalidaOtroDestinoRepository salidaRepository,
|
||||
IPublicacionRepository publicacionRepository,
|
||||
IOtroDestinoRepository otroDestinoRepository,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<SalidaOtroDestinoService> logger)
|
||||
{
|
||||
_salidaRepository = salidaRepository;
|
||||
_publicacionRepository = publicacionRepository;
|
||||
_otroDestinoRepository = otroDestinoRepository;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private async Task<SalidaOtroDestinoDto> MapToDto(SalidaOtroDestino salida)
|
||||
{
|
||||
if (salida == null) return null!; // Debería ser manejado por el llamador
|
||||
|
||||
var publicacion = await _publicacionRepository.GetByIdSimpleAsync(salida.IdPublicacion);
|
||||
var destino = await _otroDestinoRepository.GetByIdAsync(salida.IdDestino);
|
||||
|
||||
return new SalidaOtroDestinoDto
|
||||
{
|
||||
IdParte = salida.IdParte,
|
||||
IdPublicacion = salida.IdPublicacion,
|
||||
NombrePublicacion = publicacion?.Nombre ?? "Publicación Desconocida",
|
||||
IdDestino = salida.IdDestino,
|
||||
NombreDestino = destino?.Nombre ?? "Destino Desconocido",
|
||||
Fecha = salida.Fecha.ToString("yyyy-MM-dd"),
|
||||
Cantidad = salida.Cantidad,
|
||||
Observacion = salida.Observacion
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SalidaOtroDestinoDto>> ObtenerTodosAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idPublicacion, int? idDestino)
|
||||
{
|
||||
var salidas = await _salidaRepository.GetAllAsync(fechaDesde, fechaHasta, idPublicacion, idDestino);
|
||||
var dtos = new List<SalidaOtroDestinoDto>();
|
||||
foreach (var salida in salidas)
|
||||
{
|
||||
dtos.Add(await MapToDto(salida));
|
||||
}
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public async Task<SalidaOtroDestinoDto?> ObtenerPorIdAsync(int idParte)
|
||||
{
|
||||
var salida = await _salidaRepository.GetByIdAsync(idParte);
|
||||
return salida == null ? null : await MapToDto(salida);
|
||||
}
|
||||
|
||||
public async Task<(SalidaOtroDestinoDto? Salida, string? Error)> CrearAsync(CreateSalidaOtroDestinoDto createDto, int idUsuario)
|
||||
{
|
||||
if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null)
|
||||
return (null, "La publicación especificada no existe o no está habilitada.");
|
||||
if (await _otroDestinoRepository.GetByIdAsync(createDto.IdDestino) == null)
|
||||
return (null, "El destino especificado no existe.");
|
||||
|
||||
var nuevaSalida = new SalidaOtroDestino
|
||||
{
|
||||
IdPublicacion = createDto.IdPublicacion,
|
||||
IdDestino = createDto.IdDestino,
|
||||
Fecha = createDto.Fecha.Date,
|
||||
Cantidad = createDto.Cantidad,
|
||||
Observacion = createDto.Observacion
|
||||
};
|
||||
|
||||
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 salidaCreada = await _salidaRepository.CreateAsync(nuevaSalida, idUsuario, transaction);
|
||||
if (salidaCreada == null) throw new DataException("Error al registrar la salida.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("SalidaOtroDestino ID {Id} creada por Usuario ID {UserId}.", salidaCreada.IdParte, idUsuario);
|
||||
return (await MapToDto(salidaCreada), null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error CrearAsync SalidaOtroDestino para Pub ID {IdPub}", createDto.IdPublicacion);
|
||||
return (null, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idParte, UpdateSalidaOtroDestinoDto 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 salidaExistente = await _salidaRepository.GetByIdAsync(idParte); // Obtener dentro de TX
|
||||
if (salidaExistente == null) return (false, "Registro de salida no encontrado.");
|
||||
|
||||
// Actualizar solo los campos permitidos
|
||||
salidaExistente.Cantidad = updateDto.Cantidad;
|
||||
salidaExistente.Observacion = updateDto.Observacion;
|
||||
|
||||
var actualizado = await _salidaRepository.UpdateAsync(salidaExistente, idUsuario, transaction);
|
||||
if (!actualizado) throw new DataException("Error al actualizar la salida.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("SalidaOtroDestino ID {Id} actualizada por Usuario ID {UserId}.", idParte, 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 SalidaOtroDestino ID: {Id}", idParte);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarAsync(int idParte, 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 salidaExistente = await _salidaRepository.GetByIdAsync(idParte);
|
||||
if (salidaExistente == null) return (false, "Registro de salida no encontrado.");
|
||||
|
||||
var eliminado = await _salidaRepository.DeleteAsync(idParte, idUsuario, transaction);
|
||||
if (!eliminado) throw new DataException("Error al eliminar la salida.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("SalidaOtroDestino ID {Id} eliminada por Usuario ID {UserId}.", idParte, 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 SalidaOtroDestino ID: {Id}", idParte);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using GestionIntegral.Api.Dtos.Impresion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Impresion
|
||||
{
|
||||
public interface IStockBobinaService
|
||||
{
|
||||
Task<IEnumerable<StockBobinaDto>> ObtenerTodosAsync(
|
||||
int? idTipoBobina, string? nroBobinaFilter, int? idPlanta,
|
||||
int? idEstadoBobina, string? remitoFilter, DateTime? fechaDesde, DateTime? fechaHasta);
|
||||
|
||||
Task<StockBobinaDto?> ObtenerPorIdAsync(int idBobina);
|
||||
Task<(StockBobinaDto? Bobina, string? Error)> IngresarBobinaAsync(CreateStockBobinaDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarDatosBobinaDisponibleAsync(int idBobina, UpdateStockBobinaDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> CambiarEstadoBobinaAsync(int idBobina, CambiarEstadoBobinaDto cambiarEstadoDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarIngresoErroneoAsync(int idBobina, int idUsuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using GestionIntegral.Api.Dtos.Impresion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Impresion
|
||||
{
|
||||
public interface ITiradaService
|
||||
{
|
||||
Task<IEnumerable<TiradaDto>> ObtenerTiradasAsync(DateTime? fecha, int? idPublicacion, int? idPlanta);
|
||||
// GetById podría ser útil si se editan tiradas individuales, pero la creación es el foco principal.
|
||||
// Task<TiradaDto?> ObtenerTiradaPorIdRegistroAsync(int idRegistroTirada); // Para bob_RegTiradas
|
||||
Task<(TiradaDto? TiradaCreada, string? Error)> RegistrarTiradaCompletaAsync(CreateTiradaRequestDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarTiradaCompletaAsync(DateTime fecha, int idPublicacion, int idPlanta, int idUsuario);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
224
Backend/GestionIntegral.Api/Services/Impresion/TiradaService.cs
Normal file
224
Backend/GestionIntegral.Api/Services/Impresion/TiradaService.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
using GestionIntegral.Api.Data.Repositories.Impresion;
|
||||
using GestionIntegral.Api.Dtos.Impresion;
|
||||
using GestionIntegral.Api.Models.Distribucion; // Para Publicacion, PubliSeccion
|
||||
using GestionIntegral.Api.Models.Impresion; // Para RegTirada, RegPublicacionSeccion
|
||||
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 TiradaService : ITiradaService
|
||||
{
|
||||
private readonly IRegTiradaRepository _regTiradaRepository;
|
||||
private readonly IRegPublicacionSeccionRepository _regPublicacionSeccionRepository;
|
||||
private readonly IPublicacionRepository _publicacionRepository;
|
||||
private readonly IPlantaRepository _plantaRepository;
|
||||
private readonly IPubliSeccionRepository _publiSeccionRepository; // Para validar IDs de sección
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<TiradaService> _logger;
|
||||
|
||||
public TiradaService(
|
||||
IRegTiradaRepository regTiradaRepository,
|
||||
IRegPublicacionSeccionRepository regPublicacionSeccionRepository,
|
||||
IPublicacionRepository publicacionRepository,
|
||||
IPlantaRepository plantaRepository,
|
||||
IPubliSeccionRepository publiSeccionRepository,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<TiradaService> logger)
|
||||
{
|
||||
_regTiradaRepository = regTiradaRepository;
|
||||
_regPublicacionSeccionRepository = regPublicacionSeccionRepository;
|
||||
_publicacionRepository = publicacionRepository;
|
||||
_plantaRepository = plantaRepository;
|
||||
_publiSeccionRepository = publiSeccionRepository;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TiradaDto>> ObtenerTiradasAsync(DateTime? fecha, int? idPublicacion, int? idPlanta)
|
||||
{
|
||||
var tiradasPrincipales = await _regTiradaRepository.GetByCriteriaAsync(fecha, idPublicacion, idPlanta);
|
||||
var resultado = new List<TiradaDto>();
|
||||
|
||||
foreach (var tiradaP in tiradasPrincipales)
|
||||
{
|
||||
var publicacion = await _publicacionRepository.GetByIdSimpleAsync(tiradaP.IdPublicacion);
|
||||
var planta = await _plantaRepository.GetByIdAsync(tiradaP.IdPlanta);
|
||||
var seccionesImpresas = await _regPublicacionSeccionRepository.GetByFechaPublicacionPlantaAsync(tiradaP.Fecha, tiradaP.IdPublicacion, tiradaP.IdPlanta);
|
||||
|
||||
var detallesSeccionDto = new List<DetalleSeccionEnListadoDto>();
|
||||
int totalPaginas = 0;
|
||||
foreach (var seccionImp in seccionesImpresas)
|
||||
{
|
||||
var seccionInfo = await _publiSeccionRepository.GetByIdAsync(seccionImp.IdSeccion);
|
||||
detallesSeccionDto.Add(new DetalleSeccionEnListadoDto
|
||||
{
|
||||
IdSeccion = seccionImp.IdSeccion,
|
||||
NombreSeccion = seccionInfo?.Nombre ?? "Sección Desconocida",
|
||||
CantPag = seccionImp.CantPag,
|
||||
IdRegPublicacionSeccion = seccionImp.IdTirada // Este es el PK de bob_RegPublicaciones
|
||||
});
|
||||
totalPaginas += seccionImp.CantPag;
|
||||
}
|
||||
|
||||
resultado.Add(new TiradaDto
|
||||
{
|
||||
IdRegistroTirada = tiradaP.IdRegistro,
|
||||
IdPublicacion = tiradaP.IdPublicacion,
|
||||
NombrePublicacion = publicacion?.Nombre ?? "Publicación Desconocida",
|
||||
Fecha = tiradaP.Fecha.ToString("yyyy-MM-dd"),
|
||||
IdPlanta = tiradaP.IdPlanta,
|
||||
NombrePlanta = planta?.Nombre ?? "Planta Desconocida",
|
||||
Ejemplares = tiradaP.Ejemplares,
|
||||
SeccionesImpresas = detallesSeccionDto,
|
||||
TotalPaginasSumadas = totalPaginas
|
||||
});
|
||||
}
|
||||
return resultado;
|
||||
}
|
||||
|
||||
|
||||
public async Task<(TiradaDto? TiradaCreada, string? Error)> RegistrarTiradaCompletaAsync(CreateTiradaRequestDto createDto, int idUsuario)
|
||||
{
|
||||
// Validaciones previas
|
||||
var publicacion = await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion);
|
||||
if (publicacion == null) return (null, "La publicación especificada no existe.");
|
||||
var planta = await _plantaRepository.GetByIdAsync(createDto.IdPlanta);
|
||||
if (planta == null) return (null, "La planta especificada no existe.");
|
||||
|
||||
// Validar que no exista ya una tirada para esa Publicación, Fecha y Planta
|
||||
// (bob_RegTiradas debería ser único por estos campos)
|
||||
if (await _regTiradaRepository.GetByFechaPublicacionPlantaAsync(createDto.Fecha.Date, createDto.IdPublicacion, createDto.IdPlanta) != null)
|
||||
{
|
||||
return (null, $"Ya existe una tirada registrada para la publicación '{publicacion.Nombre}' en la planta '{planta.Nombre}' para la fecha {createDto.Fecha:dd/MM/yyyy}.");
|
||||
}
|
||||
|
||||
|
||||
// Validar secciones
|
||||
foreach (var seccionDto in createDto.Secciones)
|
||||
{
|
||||
var seccionDb = await _publiSeccionRepository.GetByIdAsync(seccionDto.IdSeccion);
|
||||
if (seccionDb == null || seccionDb.IdPublicacion != createDto.IdPublicacion)
|
||||
return (null, $"La sección con ID {seccionDto.IdSeccion} no es válida o no pertenece a la publicación seleccionada.");
|
||||
if (!seccionDb.Estado) // Asumiendo que solo se pueden tirar secciones activas
|
||||
return (null, $"La sección '{seccionDb.Nombre}' no está activa y no puede incluirse en la tirada.");
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
{
|
||||
// 1. Crear registro en bob_RegTiradas (total de ejemplares)
|
||||
var nuevaRegTirada = new RegTirada
|
||||
{
|
||||
IdPublicacion = createDto.IdPublicacion,
|
||||
Fecha = createDto.Fecha.Date,
|
||||
IdPlanta = createDto.IdPlanta,
|
||||
Ejemplares = createDto.Ejemplares
|
||||
};
|
||||
var regTiradaCreada = await _regTiradaRepository.CreateAsync(nuevaRegTirada, idUsuario, transaction);
|
||||
if (regTiradaCreada == null) throw new DataException("Error al registrar el total de la tirada.");
|
||||
|
||||
// 2. Crear registros en bob_RegPublicaciones (detalle de secciones y páginas)
|
||||
var seccionesImpresasDto = new List<DetalleSeccionEnListadoDto>();
|
||||
int totalPaginasSumadas = 0;
|
||||
|
||||
foreach (var seccionDto in createDto.Secciones)
|
||||
{
|
||||
var nuevaRegPubSeccion = new RegPublicacionSeccion
|
||||
{
|
||||
IdPublicacion = createDto.IdPublicacion,
|
||||
IdSeccion = seccionDto.IdSeccion,
|
||||
CantPag = seccionDto.CantPag,
|
||||
Fecha = createDto.Fecha.Date,
|
||||
IdPlanta = createDto.IdPlanta
|
||||
};
|
||||
var seccionCreadaEnTirada = await _regPublicacionSeccionRepository.CreateAsync(nuevaRegPubSeccion, idUsuario, transaction);
|
||||
if (seccionCreadaEnTirada == null) throw new DataException($"Error al registrar la sección ID {seccionDto.IdSeccion} en la tirada.");
|
||||
|
||||
var seccionInfo = await _publiSeccionRepository.GetByIdAsync(seccionDto.IdSeccion); // Para obtener nombre
|
||||
seccionesImpresasDto.Add(new DetalleSeccionEnListadoDto{
|
||||
IdSeccion = seccionCreadaEnTirada.IdSeccion,
|
||||
NombreSeccion = seccionInfo?.Nombre ?? "N/A",
|
||||
CantPag = seccionCreadaEnTirada.CantPag,
|
||||
IdRegPublicacionSeccion = seccionCreadaEnTirada.IdTirada
|
||||
});
|
||||
totalPaginasSumadas += seccionCreadaEnTirada.CantPag;
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Tirada completa registrada para Pub ID {IdPub}, Fecha {Fecha}, Planta ID {IdPlanta} por Usuario ID {UserId}.",
|
||||
createDto.IdPublicacion, createDto.Fecha.Date, createDto.IdPlanta, idUsuario);
|
||||
|
||||
return (new TiradaDto {
|
||||
IdRegistroTirada = regTiradaCreada.IdRegistro,
|
||||
IdPublicacion = regTiradaCreada.IdPublicacion,
|
||||
NombrePublicacion = publicacion.Nombre,
|
||||
Fecha = regTiradaCreada.Fecha.ToString("yyyy-MM-dd"),
|
||||
IdPlanta = regTiradaCreada.IdPlanta,
|
||||
NombrePlanta = planta.Nombre,
|
||||
Ejemplares = regTiradaCreada.Ejemplares,
|
||||
SeccionesImpresas = seccionesImpresasDto,
|
||||
TotalPaginasSumadas = totalPaginasSumadas
|
||||
}, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error RegistrarTiradaCompletaAsync para Pub ID {IdPub}, Fecha {Fecha}", createDto.IdPublicacion, createDto.Fecha);
|
||||
return (null, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarTiradaCompletaAsync(DateTime fecha, int idPublicacion, int idPlanta, int idUsuario)
|
||||
{
|
||||
// Verificar que la tirada principal exista
|
||||
var tiradaPrincipal = await _regTiradaRepository.GetByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta);
|
||||
if (tiradaPrincipal == null)
|
||||
{
|
||||
return (false, "No se encontró una tirada principal para los criterios especificados.");
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// 1. Eliminar detalles de secciones de bob_RegPublicaciones
|
||||
// El método ya guarda en historial
|
||||
await _regPublicacionSeccionRepository.DeleteByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta, idUsuario, transaction);
|
||||
_logger.LogInformation("Secciones de tirada eliminadas para Fecha: {Fecha}, PubID: {IdPublicacion}, PlantaID: {IdPlanta}", fecha.Date, idPublicacion, idPlanta);
|
||||
|
||||
// 2. Eliminar el registro principal de bob_RegTiradas
|
||||
// El método ya guarda en historial
|
||||
bool principalEliminado = await _regTiradaRepository.DeleteByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta, idUsuario, transaction);
|
||||
// bool principalEliminado = await _regTiradaRepository.DeleteAsync(tiradaPrincipal.IdRegistro, idUsuario, transaction); // Alternativa si ya tienes el IdRegistro
|
||||
if (!principalEliminado && tiradaPrincipal != null) // Si DeleteByFechaPublicacionPlantaAsync devuelve false porque no encontró nada (raro si pasó la validación)
|
||||
{
|
||||
_logger.LogWarning("No se eliminó el registro principal de tirada (bob_RegTiradas) para Fecha: {Fecha}, PubID: {IdPublicacion}, PlantaID: {IdPlanta}. Pudo haber sido eliminado concurrentemente.", fecha.Date, idPublicacion, idPlanta);
|
||||
// Decidir si esto es un error que debe hacer rollback
|
||||
}
|
||||
_logger.LogInformation("Registro principal de tirada eliminado para Fecha: {Fecha}, PubID: {IdPublicacion}, PlantaID: {IdPlanta}", fecha.Date, idPublicacion, idPlanta);
|
||||
|
||||
|
||||
transaction.Commit();
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error EliminarTiradaCompletaAsync para Fecha {Fecha}, PubID {IdPub}, PlantaID {IdPlanta}", fecha.Date, idPublicacion, idPlanta);
|
||||
return (false, $"Error interno al eliminar la tirada: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user