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,138 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Distribucion;
|
||||||
|
using GestionIntegral.Api.Services.Distribucion;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding; // Para BindRequired
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Distribucion
|
||||||
|
{
|
||||||
|
[Route("api/entradassalidasdist")] // Ruta base
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class EntradasSalidasDistController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IEntradaSalidaDistService _esService;
|
||||||
|
private readonly ILogger<EntradasSalidasDistController> _logger;
|
||||||
|
|
||||||
|
// Permisos para Entradas/Salidas Distribuidores (MD001, MD002)
|
||||||
|
// Asumo MD001 para Ver, MD002 para Crear/Modificar/Eliminar
|
||||||
|
private const string PermisoVerMovimientos = "MD001";
|
||||||
|
private const string PermisoGestionarMovimientos = "MD002";
|
||||||
|
|
||||||
|
public EntradasSalidasDistController(IEntradaSalidaDistService esService, ILogger<EntradasSalidasDistController> logger)
|
||||||
|
{
|
||||||
|
_esService = esService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
private int? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
||||||
|
_logger.LogWarning("No se pudo obtener el UserId del token JWT en EntradasSalidasDistController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/entradassalidasdist
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<EntradaSalidaDistDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetAll(
|
||||||
|
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
|
||||||
|
[FromQuery] int? idPublicacion, [FromQuery] int? idDistribuidor,
|
||||||
|
[FromQuery] string? tipoMovimiento)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerMovimientos)) return Forbid();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var movimientos = await _esService.ObtenerTodosAsync(fechaDesde, fechaHasta, idPublicacion, idDistribuidor, tipoMovimiento);
|
||||||
|
return Ok(movimientos);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener listado de Entradas/Salidas Dist.");
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/entradassalidasdist/{idParte}
|
||||||
|
[HttpGet("{idParte:int}", Name = "GetEntradaSalidaDistById")]
|
||||||
|
[ProducesResponseType(typeof(EntradaSalidaDistDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetById(int idParte)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerMovimientos)) return Forbid();
|
||||||
|
var movimiento = await _esService.ObtenerPorIdAsync(idParte);
|
||||||
|
if (movimiento == null) return NotFound(new { message = $"Movimiento con ID {idParte} no encontrado." });
|
||||||
|
return Ok(movimiento);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/entradassalidasdist
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(EntradaSalidaDistDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> CreateMovimiento([FromBody] CreateEntradaSalidaDistDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarMovimientos)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _esService.CrearMovimientoAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al registrar el movimiento.");
|
||||||
|
|
||||||
|
return CreatedAtRoute("GetEntradaSalidaDistById", new { idParte = dto.IdParte }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/entradassalidasdist/{idParte}
|
||||||
|
[HttpPut("{idParte:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> UpdateMovimiento(int idParte, [FromBody] UpdateEntradaSalidaDistDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarMovimientos)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _esService.ActualizarMovimientoAsync(idParte, updateDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Movimiento no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/entradassalidasdist/{idParte}
|
||||||
|
[HttpDelete("{idParte:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> DeleteMovimiento(int idParte)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarMovimientos)) return Forbid(); // Asumo que el mismo permiso sirve para eliminar
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _esService.EliminarMovimientoAsync(idParte, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Movimiento no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Distribucion;
|
||||||
|
using GestionIntegral.Api.Services.Distribucion;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Distribucion
|
||||||
|
{
|
||||||
|
[Route("api/publicaciones/{idPublicacion}/porcentajesmoncanilla")] // Anidado
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class PorcentajesMonCanillaController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IPorcMonCanillaService _porcMonCanillaService;
|
||||||
|
private readonly ILogger<PorcentajesMonCanillaController> _logger;
|
||||||
|
|
||||||
|
// Permiso CG004 para porcentajes de pago de canillitas
|
||||||
|
private const string PermisoGestionar = "CG004";
|
||||||
|
|
||||||
|
public PorcentajesMonCanillaController(IPorcMonCanillaService porcMonCanillaService, ILogger<PorcentajesMonCanillaController> logger)
|
||||||
|
{
|
||||||
|
_porcMonCanillaService = porcMonCanillaService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
private int? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<PorcMonCanillaDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetPorcMonCanillaPorPublicacion(int idPublicacion)
|
||||||
|
{
|
||||||
|
// DP001 para ver publicación, CG004 para gestionar específicamente esto
|
||||||
|
if (!TienePermiso("DP001") && !TienePermiso(PermisoGestionar)) return Forbid();
|
||||||
|
var items = await _porcMonCanillaService.ObtenerPorPublicacionIdAsync(idPublicacion);
|
||||||
|
return Ok(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{idPorcMon:int}", Name = "GetPorcMonCanillaById")]
|
||||||
|
[ProducesResponseType(typeof(PorcMonCanillaDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetPorcMonCanillaById(int idPublicacion, int idPorcMon)
|
||||||
|
{
|
||||||
|
if (!TienePermiso("DP001") && !TienePermiso(PermisoGestionar)) return Forbid();
|
||||||
|
var item = await _porcMonCanillaService.ObtenerPorIdAsync(idPorcMon);
|
||||||
|
if (item == null || item.IdPublicacion != idPublicacion) return NotFound();
|
||||||
|
return Ok(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(PorcMonCanillaDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> CreatePorcMonCanilla(int idPublicacion, [FromBody] CreatePorcMonCanillaDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionar)) return Forbid();
|
||||||
|
if (idPublicacion != createDto.IdPublicacion)
|
||||||
|
return BadRequest(new { message = "ID de publicación en ruta no coincide con el del cuerpo." });
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _porcMonCanillaService.CrearAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear.");
|
||||||
|
return CreatedAtRoute("GetPorcMonCanillaById", new { idPublicacion = dto.IdPublicacion, idPorcMon = dto.IdPorcMon }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{idPorcMon:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> UpdatePorcMonCanilla(int idPublicacion, int idPorcMon, [FromBody] UpdatePorcMonCanillaDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionar)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var existente = await _porcMonCanillaService.ObtenerPorIdAsync(idPorcMon);
|
||||||
|
if (existente == null || existente.IdPublicacion != idPublicacion)
|
||||||
|
return NotFound(new { message = "Registro no encontrado para esta publicación."});
|
||||||
|
|
||||||
|
var (exito, error) = await _porcMonCanillaService.ActualizarAsync(idPorcMon, updateDto, userId.Value);
|
||||||
|
if (!exito) return BadRequest(new { message = error });
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{idPorcMon:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> DeletePorcMonCanilla(int idPublicacion, int idPorcMon)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionar)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var existente = await _porcMonCanillaService.ObtenerPorIdAsync(idPorcMon);
|
||||||
|
if (existente == null || existente.IdPublicacion != idPublicacion)
|
||||||
|
return NotFound(new { message = "Registro no encontrado para esta publicación."});
|
||||||
|
|
||||||
|
var (exito, error) = await _porcMonCanillaService.EliminarAsync(idPorcMon, userId.Value);
|
||||||
|
if (!exito) return BadRequest(new { message = error });
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Distribucion;
|
||||||
|
using GestionIntegral.Api.Services.Distribucion;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Distribucion
|
||||||
|
{
|
||||||
|
[Route("api/publicaciones/{idPublicacion}/porcentajespago")] // Anidado
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class PorcentajesPagoController : ControllerBase // Para Porcentajes de Pago Distribuidores
|
||||||
|
{
|
||||||
|
private readonly IPorcPagoService _porcPagoService;
|
||||||
|
private readonly ILogger<PorcentajesPagoController> _logger;
|
||||||
|
|
||||||
|
// Permiso DG004 para gestionar porcentajes de pago de distribuidores
|
||||||
|
private const string PermisoGestionarPorcentajes = "DG004";
|
||||||
|
|
||||||
|
public PorcentajesPagoController(IPorcPagoService porcPagoService, ILogger<PorcentajesPagoController> logger)
|
||||||
|
{
|
||||||
|
_porcPagoService = porcPagoService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
private int? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<PorcPagoDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetPorcentajesPorPublicacion(int idPublicacion)
|
||||||
|
{
|
||||||
|
if (!TienePermiso("DP001") && !TienePermiso(PermisoGestionarPorcentajes)) return Forbid(); // DP001 para ver Publicación
|
||||||
|
var porcentajes = await _porcPagoService.ObtenerPorPublicacionIdAsync(idPublicacion);
|
||||||
|
return Ok(porcentajes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{idPorcentaje:int}", Name = "GetPorcPagoById")]
|
||||||
|
[ProducesResponseType(typeof(PorcPagoDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetPorcPagoById(int idPublicacion, int idPorcentaje)
|
||||||
|
{
|
||||||
|
if (!TienePermiso("DP001") && !TienePermiso(PermisoGestionarPorcentajes)) return Forbid();
|
||||||
|
var porcPago = await _porcPagoService.ObtenerPorIdAsync(idPorcentaje);
|
||||||
|
if (porcPago == null || porcPago.IdPublicacion != idPublicacion) return NotFound();
|
||||||
|
return Ok(porcPago);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(PorcPagoDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> CreatePorcPago(int idPublicacion, [FromBody] CreatePorcPagoDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarPorcentajes)) return Forbid();
|
||||||
|
if (idPublicacion != createDto.IdPublicacion)
|
||||||
|
return BadRequest(new { message = "ID de publicación en ruta no coincide con el del cuerpo." });
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _porcPagoService.CrearAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear porcentaje.");
|
||||||
|
return CreatedAtRoute("GetPorcPagoById", new { idPublicacion = dto.IdPublicacion, idPorcentaje = dto.IdPorcentaje }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{idPorcentaje:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> UpdatePorcPago(int idPublicacion, int idPorcentaje, [FromBody] UpdatePorcPagoDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarPorcentajes)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var existente = await _porcPagoService.ObtenerPorIdAsync(idPorcentaje);
|
||||||
|
if (existente == null || existente.IdPublicacion != idPublicacion)
|
||||||
|
return NotFound(new { message = "Porcentaje no encontrado para esta publicación."});
|
||||||
|
|
||||||
|
var (exito, error) = await _porcPagoService.ActualizarAsync(idPorcentaje, updateDto, userId.Value);
|
||||||
|
if (!exito) return BadRequest(new { message = error });
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{idPorcentaje:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> DeletePorcPago(int idPublicacion, int idPorcentaje)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarPorcentajes)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var existente = await _porcPagoService.ObtenerPorIdAsync(idPorcentaje);
|
||||||
|
if (existente == null || existente.IdPublicacion != idPublicacion)
|
||||||
|
return NotFound(new { message = "Porcentaje no encontrado para esta publicación."});
|
||||||
|
|
||||||
|
var (exito, error) = await _porcPagoService.EliminarAsync(idPorcentaje, userId.Value);
|
||||||
|
if (!exito) return BadRequest(new { message = error });
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Distribucion;
|
||||||
|
using GestionIntegral.Api.Services.Distribucion;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Distribucion
|
||||||
|
{
|
||||||
|
[Route("api/publicaciones/{idPublicacion}/secciones")] // Anidado
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class PubliSeccionesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IPubliSeccionService _publiSeccionService;
|
||||||
|
private readonly ILogger<PubliSeccionesController> _logger;
|
||||||
|
|
||||||
|
// Permiso DP007 para gestionar secciones de publicaciones
|
||||||
|
private const string PermisoGestionarSecciones = "DP007";
|
||||||
|
|
||||||
|
public PubliSeccionesController(IPubliSeccionService publiSeccionService, ILogger<PubliSeccionesController> logger)
|
||||||
|
{
|
||||||
|
_publiSeccionService = publiSeccionService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
private int? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/publicaciones/{idPublicacion}/secciones
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<PubliSeccionDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetSeccionesPorPublicacion(int idPublicacion, [FromQuery] bool? soloActivas)
|
||||||
|
{
|
||||||
|
// DP001 para ver publicación, DP007 para gestionar específicamente secciones
|
||||||
|
if (!TienePermiso("DP001") && !TienePermiso(PermisoGestionarSecciones)) return Forbid();
|
||||||
|
var secciones = await _publiSeccionService.ObtenerPorPublicacionIdAsync(idPublicacion, soloActivas);
|
||||||
|
return Ok(secciones);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/publicaciones/{idPublicacion}/secciones/{idSeccion}
|
||||||
|
[HttpGet("{idSeccion:int}", Name = "GetPubliSeccionById")]
|
||||||
|
[ProducesResponseType(typeof(PubliSeccionDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetPubliSeccionById(int idPublicacion, int idSeccion)
|
||||||
|
{
|
||||||
|
if (!TienePermiso("DP001") && !TienePermiso(PermisoGestionarSecciones)) return Forbid();
|
||||||
|
var seccion = await _publiSeccionService.ObtenerPorIdAsync(idSeccion);
|
||||||
|
if (seccion == null || seccion.IdPublicacion != idPublicacion) return NotFound();
|
||||||
|
return Ok(seccion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/publicaciones/{idPublicacion}/secciones
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(PubliSeccionDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> CreatePubliSeccion(int idPublicacion, [FromBody] CreatePubliSeccionDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarSecciones)) return Forbid();
|
||||||
|
if (idPublicacion != createDto.IdPublicacion)
|
||||||
|
return BadRequest(new { message = "ID de publicación en ruta no coincide con el del cuerpo." });
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _publiSeccionService.CrearAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear sección.");
|
||||||
|
return CreatedAtRoute("GetPubliSeccionById", new { idPublicacion = dto.IdPublicacion, idSeccion = dto.IdSeccion }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/publicaciones/{idPublicacion}/secciones/{idSeccion}
|
||||||
|
[HttpPut("{idSeccion:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> UpdatePubliSeccion(int idPublicacion, int idSeccion, [FromBody] UpdatePubliSeccionDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarSecciones)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var existente = await _publiSeccionService.ObtenerPorIdAsync(idSeccion);
|
||||||
|
if (existente == null || existente.IdPublicacion != idPublicacion)
|
||||||
|
return NotFound(new { message = "Sección no encontrada para esta publicación."});
|
||||||
|
|
||||||
|
var (exito, error) = await _publiSeccionService.ActualizarAsync(idSeccion, updateDto, userId.Value);
|
||||||
|
if (!exito) return BadRequest(new { message = error });
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/publicaciones/{idPublicacion}/secciones/{idSeccion}
|
||||||
|
[HttpDelete("{idSeccion:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> DeletePubliSeccion(int idPublicacion, int idSeccion)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarSecciones)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var existente = await _publiSeccionService.ObtenerPorIdAsync(idSeccion);
|
||||||
|
if (existente == null || existente.IdPublicacion != idPublicacion)
|
||||||
|
return NotFound(new { message = "Sección no encontrada para esta publicación."});
|
||||||
|
|
||||||
|
var (exito, error) = await _publiSeccionService.EliminarAsync(idSeccion, userId.Value);
|
||||||
|
if (!exito) return BadRequest(new { message = error });
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Distribucion;
|
||||||
|
using GestionIntegral.Api.Services.Distribucion;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Distribucion
|
||||||
|
{
|
||||||
|
[Route("api/publicaciones/{idPublicacion}/recargos")] // Anidado bajo publicaciones
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class RecargosZonaController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IRecargoZonaService _recargoZonaService;
|
||||||
|
private readonly ILogger<RecargosZonaController> _logger;
|
||||||
|
|
||||||
|
// Permiso DP005 para gestionar recargos
|
||||||
|
private const string PermisoGestionarRecargos = "DP005";
|
||||||
|
|
||||||
|
public RecargosZonaController(IRecargoZonaService recargoZonaService, ILogger<RecargosZonaController> logger)
|
||||||
|
{
|
||||||
|
_recargoZonaService = recargoZonaService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
private int? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/publicaciones/{idPublicacion}/recargos
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<RecargoZonaDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetRecargosPorPublicacion(int idPublicacion)
|
||||||
|
{
|
||||||
|
if (!TienePermiso("DP001") && !TienePermiso(PermisoGestionarRecargos)) return Forbid();
|
||||||
|
var recargos = await _recargoZonaService.ObtenerPorPublicacionIdAsync(idPublicacion);
|
||||||
|
return Ok(recargos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/publicaciones/{idPublicacion}/recargos/{idRecargo}
|
||||||
|
[HttpGet("{idRecargo:int}", Name = "GetRecargoZonaById")]
|
||||||
|
[ProducesResponseType(typeof(RecargoZonaDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetRecargoZonaById(int idPublicacion, int idRecargo)
|
||||||
|
{
|
||||||
|
if (!TienePermiso("DP001") && !TienePermiso(PermisoGestionarRecargos)) return Forbid();
|
||||||
|
var recargo = await _recargoZonaService.ObtenerPorIdAsync(idRecargo);
|
||||||
|
if (recargo == null || recargo.IdPublicacion != idPublicacion) return NotFound();
|
||||||
|
return Ok(recargo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/publicaciones/{idPublicacion}/recargos
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(RecargoZonaDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> CreateRecargoZona(int idPublicacion, [FromBody] CreateRecargoZonaDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarRecargos)) return Forbid();
|
||||||
|
if (idPublicacion != createDto.IdPublicacion)
|
||||||
|
return BadRequest(new { message = "ID de publicación en ruta no coincide con el del cuerpo." });
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _recargoZonaService.CrearAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear recargo.");
|
||||||
|
return CreatedAtRoute("GetRecargoZonaById", new { idPublicacion = dto.IdPublicacion, idRecargo = dto.IdRecargo }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/publicaciones/{idPublicacion}/recargos/{idRecargo}
|
||||||
|
[HttpPut("{idRecargo:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> UpdateRecargoZona(int idPublicacion, int idRecargo, [FromBody] UpdateRecargoZonaDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarRecargos)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var recargoExistente = await _recargoZonaService.ObtenerPorIdAsync(idRecargo);
|
||||||
|
if (recargoExistente == null || recargoExistente.IdPublicacion != idPublicacion)
|
||||||
|
return NotFound(new { message = "Recargo no encontrado para esta publicación."});
|
||||||
|
|
||||||
|
var (exito, error) = await _recargoZonaService.ActualizarAsync(idRecargo, updateDto, userId.Value);
|
||||||
|
if (!exito) return BadRequest(new { message = error });
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/publicaciones/{idPublicacion}/recargos/{idRecargo}
|
||||||
|
[HttpDelete("{idRecargo:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> DeleteRecargoZona(int idPublicacion, int idRecargo)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarRecargos)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var recargoExistente = await _recargoZonaService.ObtenerPorIdAsync(idRecargo);
|
||||||
|
if (recargoExistente == null || recargoExistente.IdPublicacion != idPublicacion)
|
||||||
|
return NotFound(new { message = "Recargo no encontrado para esta publicación."});
|
||||||
|
|
||||||
|
var (exito, error) = await _recargoZonaService.EliminarAsync(idRecargo, userId.Value);
|
||||||
|
if (!exito) return BadRequest(new { message = error });
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Distribucion;
|
||||||
|
using GestionIntegral.Api.Services.Distribucion;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Distribucion
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")] // Ruta base: /api/salidasotrosdestinos
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class SalidasOtrosDestinosController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ISalidaOtroDestinoService _salidaService;
|
||||||
|
private readonly ILogger<SalidasOtrosDestinosController> _logger;
|
||||||
|
|
||||||
|
// Permisos para Salidas Otros Destinos (SO001 a SO003, asumiendo OD00X para la entidad OtrosDestinos)
|
||||||
|
private const string PermisoVerSalidasOD = "SO001";
|
||||||
|
private const string PermisoCrearSalidaOD = "SO002"; // Asumo SO002 para crear/modificar basado en tu excel
|
||||||
|
private const string PermisoModificarSalidaOD = "SO002"; // "
|
||||||
|
private const string PermisoEliminarSalidaOD = "SO003";
|
||||||
|
|
||||||
|
public SalidasOtrosDestinosController(ISalidaOtroDestinoService salidaService, ILogger<SalidasOtrosDestinosController> logger)
|
||||||
|
{
|
||||||
|
_salidaService = salidaService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
private int? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
||||||
|
_logger.LogWarning("No se pudo obtener el UserId del token JWT en SalidasOtrosDestinosController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/salidasotrosdestinos
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<SalidaOtroDestinoDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetAllSalidas(
|
||||||
|
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
|
||||||
|
[FromQuery] int? idPublicacion, [FromQuery] int? idDestino)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerSalidasOD)) return Forbid();
|
||||||
|
var salidas = await _salidaService.ObtenerTodosAsync(fechaDesde, fechaHasta, idPublicacion, idDestino);
|
||||||
|
return Ok(salidas);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/salidasotrosdestinos/{idParte}
|
||||||
|
[HttpGet("{idParte:int}", Name = "GetSalidaOtroDestinoById")]
|
||||||
|
[ProducesResponseType(typeof(SalidaOtroDestinoDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetSalidaOtroDestinoById(int idParte)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerSalidasOD)) return Forbid();
|
||||||
|
var salida = await _salidaService.ObtenerPorIdAsync(idParte);
|
||||||
|
if (salida == null) return NotFound();
|
||||||
|
return Ok(salida);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/salidasotrosdestinos
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(SalidaOtroDestinoDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> CreateSalidaOtroDestino([FromBody] CreateSalidaOtroDestinoDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoCrearSalidaOD)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _salidaService.CrearAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al registrar la salida.");
|
||||||
|
return CreatedAtRoute("GetSalidaOtroDestinoById", new { idParte = dto.IdParte }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/salidasotrosdestinos/{idParte}
|
||||||
|
[HttpPut("{idParte:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> UpdateSalidaOtroDestino(int idParte, [FromBody] UpdateSalidaOtroDestinoDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoModificarSalidaOD)) return Forbid(); // O SO002 si se usa para modificar
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _salidaService.ActualizarAsync(idParte, updateDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Registro de salida no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/salidasotrosdestinos/{idParte}
|
||||||
|
[HttpDelete("{idParte:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> DeleteSalidaOtroDestino(int idParte)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoEliminarSalidaOD)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _salidaService.EliminarAsync(idParte, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Registro de salida no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Impresion;
|
||||||
|
using GestionIntegral.Api.Services.Impresion;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Impresion
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")] // Ruta base: /api/stockbobinas
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class StockBobinasController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IStockBobinaService _stockBobinaService;
|
||||||
|
private readonly ILogger<StockBobinasController> _logger;
|
||||||
|
|
||||||
|
// Permisos para Stock de Bobinas (IB001 a IB005)
|
||||||
|
private const string PermisoVerStock = "IB001";
|
||||||
|
private const string PermisoIngresarBobina = "IB002";
|
||||||
|
private const string PermisoCambiarEstado = "IB003";
|
||||||
|
private const string PermisoModificarDatos = "IB004"; // Para bobinas "Disponibles"
|
||||||
|
private const string PermisoEliminarBobina = "IB005"; // Para ingresos erróneos "Disponibles"
|
||||||
|
|
||||||
|
|
||||||
|
public StockBobinasController(IStockBobinaService stockBobinaService, ILogger<StockBobinasController> logger)
|
||||||
|
{
|
||||||
|
_stockBobinaService = stockBobinaService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
private int? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
||||||
|
_logger.LogWarning("No se pudo obtener el UserId del token JWT en StockBobinasController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/stockbobinas
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<StockBobinaDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetAllStockBobinas(
|
||||||
|
[FromQuery] int? idTipoBobina, [FromQuery] string? nroBobina, [FromQuery] int? idPlanta,
|
||||||
|
[FromQuery] int? idEstadoBobina, [FromQuery] string? remito,
|
||||||
|
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerStock)) return Forbid();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bobinas = await _stockBobinaService.ObtenerTodosAsync(idTipoBobina, nroBobina, idPlanta, idEstadoBobina, remito, fechaDesde, fechaHasta);
|
||||||
|
return Ok(bobinas);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener el stock de bobinas.");
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al procesar la solicitud.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/stockbobinas/{idBobina}
|
||||||
|
[HttpGet("{idBobina:int}", Name = "GetStockBobinaById")]
|
||||||
|
[ProducesResponseType(typeof(StockBobinaDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetStockBobinaById(int idBobina)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerStock)) return Forbid();
|
||||||
|
var bobina = await _stockBobinaService.ObtenerPorIdAsync(idBobina);
|
||||||
|
if (bobina == null) return NotFound(new { message = $"Bobina con ID {idBobina} no encontrada." });
|
||||||
|
return Ok(bobina);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/stockbobinas (Ingresar nueva bobina)
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(StockBobinaDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> IngresarBobina([FromBody] CreateStockBobinaDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoIngresarBobina)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _stockBobinaService.IngresarBobinaAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al ingresar la bobina.");
|
||||||
|
return CreatedAtRoute("GetStockBobinaById", new { idBobina = dto.IdBobina }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/stockbobinas/{idBobina}/datos (Actualizar datos de una bobina DISPONIBLE)
|
||||||
|
[HttpPut("{idBobina:int}/datos")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> UpdateDatosBobinaDisponible(int idBobina, [FromBody] UpdateStockBobinaDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoModificarDatos)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _stockBobinaService.ActualizarDatosBobinaDisponibleAsync(idBobina, updateDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Bobina no encontrada.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/stockbobinas/{idBobina}/cambiar-estado
|
||||||
|
[HttpPut("{idBobina:int}/cambiar-estado")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> CambiarEstadoBobina(int idBobina, [FromBody] CambiarEstadoBobinaDto cambiarEstadoDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoCambiarEstado)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
// Validación adicional en el controlador para el caso "En Uso"
|
||||||
|
if (cambiarEstadoDto.NuevoEstadoId == 2) // Asumiendo 2 = En Uso
|
||||||
|
{
|
||||||
|
if (!cambiarEstadoDto.IdPublicacion.HasValue || cambiarEstadoDto.IdPublicacion.Value <= 0)
|
||||||
|
return BadRequest(new { message = "Se requiere IdPublicacion para el estado 'En Uso'."});
|
||||||
|
if (!cambiarEstadoDto.IdSeccion.HasValue || cambiarEstadoDto.IdSeccion.Value <=0)
|
||||||
|
return BadRequest(new { message = "Se requiere IdSeccion para el estado 'En Uso'."});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var (exito, error) = await _stockBobinaService.CambiarEstadoBobinaAsync(idBobina, cambiarEstadoDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Bobina no encontrada.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/stockbobinas/{idBobina} (Eliminar un ingreso erróneo, solo si está "Disponible")
|
||||||
|
[HttpDelete("{idBobina:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)] // Si no está disponible o no se puede borrar
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> DeleteIngresoBobina(int idBobina)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoEliminarBobina)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _stockBobinaService.EliminarIngresoErroneoAsync(idBobina, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Bobina no encontrada.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error }); // Ej: "No está disponible"
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Impresion;
|
||||||
|
using GestionIntegral.Api.Services.Impresion;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Impresion
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")] // Ruta base: /api/tiradas
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class TiradasController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ITiradaService _tiradaService;
|
||||||
|
private readonly ILogger<TiradasController> _logger;
|
||||||
|
|
||||||
|
// Permisos para Tiradas (IT001 a IT003)
|
||||||
|
private const string PermisoVerTiradas = "IT001";
|
||||||
|
private const string PermisoRegistrarTirada = "IT002";
|
||||||
|
private const string PermisoEliminarTirada = "IT003"; // Asumo que se refiere a eliminar una tirada completa (cabecera y detalles)
|
||||||
|
|
||||||
|
public TiradasController(ITiradaService tiradaService, ILogger<TiradasController> logger)
|
||||||
|
{
|
||||||
|
_tiradaService = tiradaService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
private int? GetCurrentUserId()
|
||||||
|
{
|
||||||
|
if (int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"), out int userId)) return userId;
|
||||||
|
_logger.LogWarning("No se pudo obtener el UserId del token JWT en TiradasController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/tiradas
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<TiradaDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetTiradas(
|
||||||
|
[FromQuery] DateTime? fecha,
|
||||||
|
[FromQuery] int? idPublicacion,
|
||||||
|
[FromQuery] int? idPlanta)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerTiradas)) return Forbid();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tiradas = await _tiradaService.ObtenerTiradasAsync(fecha, idPublicacion, idPlanta);
|
||||||
|
return Ok(tiradas);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener listado de tiradas.");
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al procesar la solicitud.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/tiradas
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(TiradaDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> RegistrarTirada([FromBody] CreateTiradaRequestDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoRegistrarTirada)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (tiradaCreada, error) = await _tiradaService.RegistrarTiradaCompletaAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (tiradaCreada == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al registrar la tirada.");
|
||||||
|
|
||||||
|
// No hay un "GetById" simple para una tirada completa con un solo ID,
|
||||||
|
// ya que se identifica por Fecha, Publicacion, Planta.
|
||||||
|
// Podríamos devolver la tirada creada directamente.
|
||||||
|
return StatusCode(StatusCodes.Status201Created, tiradaCreada);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/tiradas
|
||||||
|
// Se identifica la tirada a eliminar por su combinación única de Fecha, IdPublicacion, IdPlanta
|
||||||
|
[HttpDelete]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)] // Si no se encuentra la tirada para borrar
|
||||||
|
public async Task<IActionResult> DeleteTirada(
|
||||||
|
[FromQuery, BindRequired] DateTime fecha, // Microsoft.AspNetCore.Mvc.ModelBinding.BindRequiredAttribute
|
||||||
|
[FromQuery, BindRequired] int idPublicacion,
|
||||||
|
[FromQuery, BindRequired] int idPlanta)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoEliminarTirada)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _tiradaService.EliminarTiradaCompletaAsync(fecha, idPublicacion, idPlanta, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "No se encontró una tirada principal para los criterios especificados.")
|
||||||
|
return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using GestionIntegral.Api.Data.Repositories;
|
|
||||||
using GestionIntegral.Api.Models.Distribucion;
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System; // Para Exception
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -24,10 +24,14 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
public async Task<IEnumerable<(Canilla Canilla, string NombreZona, string NombreEmpresa)>> GetAllAsync(string? nomApeFilter, int? legajoFilter, bool? soloActivos)
|
public async Task<IEnumerable<(Canilla Canilla, string NombreZona, string NombreEmpresa)>> GetAllAsync(string? nomApeFilter, int? legajoFilter, bool? soloActivos)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder(@"
|
var sqlBuilder = new StringBuilder(@"
|
||||||
SELECT c.*, z.Nombre AS NombreZona, ISNULL(e.Nombre, 'N/A (Accionista)') AS NombreEmpresa
|
SELECT
|
||||||
|
c.Id_Canilla AS IdCanilla, c.Legajo, c.NomApe, c.Parada, c.Id_Zona AS IdZona,
|
||||||
|
c.Accionista, c.Obs, c.Empresa, c.Baja, c.FechaBaja,
|
||||||
|
z.Nombre AS NombreZona,
|
||||||
|
ISNULL(e.Nombre, 'N/A (Accionista)') AS NombreEmpresa
|
||||||
FROM dbo.dist_dtCanillas c
|
FROM dbo.dist_dtCanillas c
|
||||||
INNER JOIN dbo.dist_dtZonas z ON c.Id_Zona = z.Id_Zona
|
INNER JOIN dbo.dist_dtZonas z ON c.Id_Zona = z.Id_Zona
|
||||||
LEFT JOIN dbo.dist_dtEmpresas e ON c.Empresa = e.Id_Empresa -- Empresa 0 no tendrá join
|
LEFT JOIN dbo.dist_dtEmpresas e ON c.Empresa = e.Id_Empresa
|
||||||
WHERE 1=1");
|
WHERE 1=1");
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new DynamicParameters();
|
||||||
|
|
||||||
@@ -37,13 +41,13 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
}
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(nomApeFilter))
|
if (!string.IsNullOrWhiteSpace(nomApeFilter))
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND c.NomApe LIKE @NomApe");
|
sqlBuilder.Append(" AND c.NomApe LIKE @NomApeParam");
|
||||||
parameters.Add("NomApe", $"%{nomApeFilter}%");
|
parameters.Add("NomApeParam", $"%{nomApeFilter}%");
|
||||||
}
|
}
|
||||||
if (legajoFilter.HasValue)
|
if (legajoFilter.HasValue)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND c.Legajo = @Legajo");
|
sqlBuilder.Append(" AND c.Legajo = @LegajoParam");
|
||||||
parameters.Add("Legajo", legajoFilter.Value);
|
parameters.Add("LegajoParam", legajoFilter.Value);
|
||||||
}
|
}
|
||||||
sqlBuilder.Append(" ORDER BY c.NomApe;");
|
sqlBuilder.Append(" ORDER BY c.NomApe;");
|
||||||
|
|
||||||
@@ -59,7 +63,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error al obtener todos los Canillas.");
|
_logger.LogError(ex, "Error al obtener todos los Canillas. Filtros: NomApe='{NomApeFilter}', Legajo='{LegajoFilter}', SoloActivos='{SoloActivos}'", nomApeFilter, legajoFilter, soloActivos);
|
||||||
return Enumerable.Empty<(Canilla, string, string)>();
|
return Enumerable.Empty<(Canilla, string, string)>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,18 +71,22 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
public async Task<(Canilla? Canilla, string? NombreZona, string? NombreEmpresa)> GetByIdAsync(int id)
|
public async Task<(Canilla? Canilla, string? NombreZona, string? NombreEmpresa)> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT c.*, z.Nombre AS NombreZona, ISNULL(e.Nombre, 'N/A (Accionista)') AS NombreEmpresa
|
SELECT
|
||||||
|
c.Id_Canilla AS IdCanilla, c.Legajo, c.NomApe, c.Parada, c.Id_Zona AS IdZona,
|
||||||
|
c.Accionista, c.Obs, c.Empresa, c.Baja, c.FechaBaja,
|
||||||
|
z.Nombre AS NombreZona,
|
||||||
|
ISNULL(e.Nombre, 'N/A (Accionista)') AS NombreEmpresa
|
||||||
FROM dbo.dist_dtCanillas c
|
FROM dbo.dist_dtCanillas c
|
||||||
INNER JOIN dbo.dist_dtZonas z ON c.Id_Zona = z.Id_Zona
|
INNER JOIN dbo.dist_dtZonas z ON c.Id_Zona = z.Id_Zona
|
||||||
LEFT JOIN dbo.dist_dtEmpresas e ON c.Empresa = e.Id_Empresa
|
LEFT JOIN dbo.dist_dtEmpresas e ON c.Empresa = e.Id_Empresa
|
||||||
WHERE c.Id_Canilla = @Id";
|
WHERE c.Id_Canilla = @IdParam";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
var result = await connection.QueryAsync<Canilla, string, string, (Canilla, string, string)>(
|
var result = await connection.QueryAsync<Canilla, string, string, (Canilla?, string?, string?)>(
|
||||||
sql,
|
sql,
|
||||||
(canilla, nombreZona, nombreEmpresa) => (canilla, nombreZona, nombreEmpresa),
|
(canilla, nombreZona, nombreEmpresa) => (canilla, nombreZona, nombreEmpresa),
|
||||||
new { Id = id },
|
new { IdParam = id },
|
||||||
splitOn: "NombreZona,NombreEmpresa"
|
splitOn: "NombreZona,NombreEmpresa"
|
||||||
);
|
);
|
||||||
return result.SingleOrDefault();
|
return result.SingleOrDefault();
|
||||||
@@ -89,25 +97,37 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
return (null, null, null);
|
return (null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<Canilla?> GetByIdSimpleAsync(int id) // Para uso interno del servicio
|
public async Task<Canilla?> GetByIdSimpleAsync(int id)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Canilla AS IdCanilla, Legajo, NomApe, Parada, Id_Zona AS IdZona,
|
||||||
|
Accionista, Obs, Empresa, Baja, FechaBaja
|
||||||
|
FROM dbo.dist_dtCanillas
|
||||||
|
WHERE Id_Canilla = @IdParam";
|
||||||
|
try
|
||||||
{
|
{
|
||||||
const string sql = "SELECT * FROM dbo.dist_dtCanillas WHERE Id_Canilla = @Id";
|
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.QuerySingleOrDefaultAsync<Canilla>(sql, new { Id = id });
|
return await connection.QuerySingleOrDefaultAsync<Canilla>(sql, new { IdParam = id });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener Canilla (simple) por ID: {IdCanilla}", id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> ExistsByLegajoAsync(int legajo, int? excludeIdCanilla = null)
|
public async Task<bool> ExistsByLegajoAsync(int legajo, int? excludeIdCanilla = null)
|
||||||
{
|
{
|
||||||
if (legajo == 0) return false; // Legajo 0 es como nulo, no debería validarse como único
|
if (legajo == 0) return false;
|
||||||
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtCanillas WHERE Legajo = @Legajo AND Legajo != 0"); // Excluir legajo 0 de la unicidad
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtCanillas WHERE Legajo = @LegajoParam AND Legajo != 0");
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new DynamicParameters();
|
||||||
parameters.Add("Legajo", legajo);
|
parameters.Add("LegajoParam", legajo);
|
||||||
|
|
||||||
if (excludeIdCanilla.HasValue)
|
if (excludeIdCanilla.HasValue)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND Id_Canilla != @ExcludeIdCanilla");
|
sqlBuilder.Append(" AND Id_Canilla != @ExcludeIdCanillaParam");
|
||||||
parameters.Add("ExcludeIdCanilla", excludeIdCanilla.Value);
|
parameters.Add("ExcludeIdCanillaParam", excludeIdCanilla.Value);
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -125,23 +145,24 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
const string sqlInsert = @"
|
const string sqlInsert = @"
|
||||||
INSERT INTO dbo.dist_dtCanillas (Legajo, NomApe, Parada, Id_Zona, Accionista, Obs, Empresa, Baja, FechaBaja)
|
INSERT INTO dbo.dist_dtCanillas (Legajo, NomApe, Parada, Id_Zona, Accionista, Obs, Empresa, Baja, FechaBaja)
|
||||||
OUTPUT INSERTED.*
|
OUTPUT INSERTED.Id_Canilla AS IdCanilla, INSERTED.Legajo, INSERTED.NomApe, INSERTED.Parada, INSERTED.Id_Zona AS IdZona,
|
||||||
|
INSERTED.Accionista, INSERTED.Obs, INSERTED.Empresa, INSERTED.Baja, INSERTED.FechaBaja
|
||||||
VALUES (@Legajo, @NomApe, @Parada, @IdZona, @Accionista, @Obs, @Empresa, @Baja, @FechaBaja);";
|
VALUES (@Legajo, @NomApe, @Parada, @IdZona, @Accionista, @Obs, @Empresa, @Baja, @FechaBaja);";
|
||||||
const string sqlInsertHistorico = @"
|
|
||||||
INSERT INTO dbo.dist_dtCanillas_H
|
|
||||||
(Id_Canilla, Legajo, NomApe, Parada, Id_Zona, Accionista, Obs, Empresa, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
|
||||||
VALUES (@IdCanilla, @Legajo, @NomApe, @Parada, @IdZona, @Accionista, @Obs, @Empresa, @Baja, @FechaBaja, @Id_Usuario, @FechaMod, @TipoMod);";
|
|
||||||
|
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var insertedCanilla = await connection.QuerySingleAsync<Canilla>(sqlInsert, nuevoCanilla, transaction);
|
var insertedCanilla = await connection.QuerySingleAsync<Canilla>(sqlInsert, nuevoCanilla, transaction);
|
||||||
|
|
||||||
if (insertedCanilla == null) throw new DataException("No se pudo crear el canilla.");
|
if (insertedCanilla == null || insertedCanilla.IdCanilla == 0) throw new DataException("No se pudo crear el canillita o obtener su ID.");
|
||||||
|
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_dtCanillas_H
|
||||||
|
(Id_Canilla, Legajo, NomApe, Parada, Id_Zona, Accionista, Obs, Empresa, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdCanillaParam, @LegajoParam, @NomApeParam, @ParadaParam, @IdZonaParam, @AccionistaParam, @ObsParam, @EmpresaParam, @BajaParam, @FechaBajaParam, @Id_UsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
insertedCanilla.IdCanilla, insertedCanilla.Legajo, insertedCanilla.NomApe, insertedCanilla.Parada, insertedCanilla.IdZona,
|
IdCanillaParam = insertedCanilla.IdCanilla, LegajoParam = insertedCanilla.Legajo, NomApeParam = insertedCanilla.NomApe, ParadaParam = insertedCanilla.Parada, IdZonaParam = insertedCanilla.IdZona,
|
||||||
insertedCanilla.Accionista, insertedCanilla.Obs, insertedCanilla.Empresa, insertedCanilla.Baja, insertedCanilla.FechaBaja,
|
AccionistaParam = insertedCanilla.Accionista, ObsParam = insertedCanilla.Obs, EmpresaParam = insertedCanilla.Empresa, BajaParam = insertedCanilla.Baja, FechaBajaParam = insertedCanilla.FechaBaja,
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Creado"
|
Id_UsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
return insertedCanilla;
|
return insertedCanilla;
|
||||||
}
|
}
|
||||||
@@ -150,27 +171,29 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var canillaActual = await connection.QuerySingleOrDefaultAsync<Canilla>(
|
var canillaActual = await connection.QuerySingleOrDefaultAsync<Canilla>(
|
||||||
"SELECT * FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanilla", new { canillaAActualizar.IdCanilla }, transaction);
|
@"SELECT Id_Canilla AS IdCanilla, Legajo, NomApe, Parada, Id_Zona AS IdZona,
|
||||||
|
Accionista, Obs, Empresa, Baja, FechaBaja
|
||||||
|
FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanillaParam",
|
||||||
|
new { IdCanillaParam = canillaAActualizar.IdCanilla }, transaction);
|
||||||
if (canillaActual == null) throw new KeyNotFoundException("Canilla no encontrado para actualizar.");
|
if (canillaActual == null) throw new KeyNotFoundException("Canilla no encontrado para actualizar.");
|
||||||
|
|
||||||
const string sqlUpdate = @"
|
const string sqlUpdate = @"
|
||||||
UPDATE dbo.dist_dtCanillas SET
|
UPDATE dbo.dist_dtCanillas SET
|
||||||
Legajo = @Legajo, NomApe = @NomApe, Parada = @Parada, Id_Zona = @IdZona,
|
Legajo = @Legajo, NomApe = @NomApe, Parada = @Parada, Id_Zona = @IdZona,
|
||||||
Accionista = @Accionista, Obs = @Obs, Empresa = @Empresa
|
Accionista = @Accionista, Obs = @Obs, Empresa = @Empresa
|
||||||
-- Baja y FechaBaja se manejan por ToggleBajaAsync
|
|
||||||
WHERE Id_Canilla = @IdCanilla;";
|
WHERE Id_Canilla = @IdCanilla;";
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_dtCanillas_H
|
INSERT INTO dbo.dist_dtCanillas_H
|
||||||
(Id_Canilla, Legajo, NomApe, Parada, Id_Zona, Accionista, Obs, Empresa, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
(Id_Canilla, Legajo, NomApe, Parada, Id_Zona, Accionista, Obs, Empresa, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdCanilla, @LegajoActual, @NomApeActual, @ParadaActual, @IdZonaActual, @AccionistaActual, @ObsActual, @EmpresaActual, @BajaActual, @FechaBajaActual, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdCanillaParam, @LegajoParam, @NomApeParam, @ParadaParam, @IdZonaParam, @AccionistaParam, @ObsParam, @EmpresaParam, @BajaParam, @FechaBajaParam, @Id_UsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
IdCanilla = canillaActual.IdCanilla,
|
IdCanillaParam = canillaActual.IdCanilla,
|
||||||
LegajoActual = canillaActual.Legajo, NomApeActual = canillaActual.NomApe, ParadaActual = canillaActual.Parada, IdZonaActual = canillaActual.IdZona,
|
LegajoParam = canillaActual.Legajo, NomApeParam = canillaActual.NomApe, ParadaParam = canillaActual.Parada, IdZonaParam = canillaActual.IdZona,
|
||||||
AccionistaActual = canillaActual.Accionista, ObsActual = canillaActual.Obs, EmpresaActual = canillaActual.Empresa,
|
AccionistaParam = canillaActual.Accionista, ObsParam = canillaActual.Obs, EmpresaParam = canillaActual.Empresa,
|
||||||
BajaActual = canillaActual.Baja, FechaBajaActual = canillaActual.FechaBaja, // Registrar estado actual de baja
|
BajaParam = canillaActual.Baja, FechaBajaParam = canillaActual.FechaBaja,
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Actualizado"
|
Id_UsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
|
|
||||||
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, canillaAActualizar, transaction);
|
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, canillaAActualizar, transaction);
|
||||||
@@ -181,24 +204,27 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var canillaActual = await connection.QuerySingleOrDefaultAsync<Canilla>(
|
var canillaActual = await connection.QuerySingleOrDefaultAsync<Canilla>(
|
||||||
"SELECT * FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanilla", new { IdCanilla = id }, transaction);
|
@"SELECT Id_Canilla AS IdCanilla, Legajo, NomApe, Parada, Id_Zona AS IdZona,
|
||||||
|
Accionista, Obs, Empresa, Baja, FechaBaja
|
||||||
|
FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanillaParam",
|
||||||
|
new { IdCanillaParam = id }, transaction);
|
||||||
if (canillaActual == null) throw new KeyNotFoundException("Canilla no encontrado para dar de baja/alta.");
|
if (canillaActual == null) throw new KeyNotFoundException("Canilla no encontrado para dar de baja/alta.");
|
||||||
|
|
||||||
const string sqlUpdate = "UPDATE dbo.dist_dtCanillas SET Baja = @Baja, FechaBaja = @FechaBaja WHERE Id_Canilla = @IdCanilla;";
|
const string sqlUpdate = "UPDATE dbo.dist_dtCanillas SET Baja = @BajaParam, FechaBaja = @FechaBajaParam WHERE Id_Canilla = @IdCanillaParam;";
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_dtCanillas_H
|
INSERT INTO dbo.dist_dtCanillas_H
|
||||||
(Id_Canilla, Legajo, NomApe, Parada, Id_Zona, Accionista, Obs, Empresa, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
(Id_Canilla, Legajo, NomApe, Parada, Id_Zona, Accionista, Obs, Empresa, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdCanilla, @Legajo, @NomApe, @Parada, @IdZona, @Accionista, @Obs, @Empresa, @BajaNueva, @FechaBajaNueva, @Id_Usuario, @FechaMod, @TipoModHist);";
|
VALUES (@IdCanillaParam, @LegajoParam, @NomApeParam, @ParadaParam, @IdZonaParam, @AccionistaParam, @ObsParam, @EmpresaParam, @BajaNuevaParam, @FechaBajaNuevaParam, @Id_UsuarioParam, @FechaModParam, @TipoModHistParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
canillaActual.IdCanilla, canillaActual.Legajo, canillaActual.NomApe, canillaActual.Parada, canillaActual.IdZona,
|
IdCanillaParam = canillaActual.IdCanilla, LegajoParam = canillaActual.Legajo, NomApeParam = canillaActual.NomApe, ParadaParam = canillaActual.Parada, IdZonaParam = canillaActual.IdZona,
|
||||||
canillaActual.Accionista, canillaActual.Obs, canillaActual.Empresa,
|
AccionistaParam = canillaActual.Accionista, ObsParam = canillaActual.Obs, EmpresaParam = canillaActual.Empresa,
|
||||||
BajaNueva = darDeBaja, FechaBajaNueva = (darDeBaja ? fechaBaja : null), // FechaBaja solo si se da de baja
|
BajaNuevaParam = darDeBaja, FechaBajaNuevaParam = (darDeBaja ? fechaBaja : null),
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoModHist = (darDeBaja ? "Baja" : "Alta")
|
Id_UsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModHistParam = (darDeBaja ? "Baja" : "Alta")
|
||||||
}, transaction);
|
}, transaction);
|
||||||
|
|
||||||
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new { Baja = darDeBaja, FechaBaja = (darDeBaja ? fechaBaja : null), IdCanilla = id }, transaction);
|
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new { BajaParam = darDeBaja, FechaBajaParam = (darDeBaja ? fechaBaja : null), IdCanillaParam = id }, transaction);
|
||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using GestionIntegral.Api.Models.Distribucion;
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System; // Añadido para Exception
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -23,7 +24,10 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
public async Task<IEnumerable<(Distribuidor Distribuidor, string? NombreZona)>> GetAllAsync(string? nombreFilter, string? nroDocFilter)
|
public async Task<IEnumerable<(Distribuidor Distribuidor, string? NombreZona)>> GetAllAsync(string? nombreFilter, string? nroDocFilter)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder(@"
|
var sqlBuilder = new StringBuilder(@"
|
||||||
SELECT d.*, z.Nombre AS NombreZona
|
SELECT
|
||||||
|
d.Id_Distribuidor AS IdDistribuidor, d.Nombre, d.Contacto, d.NroDoc, d.Id_Zona AS IdZona,
|
||||||
|
d.Calle, d.Numero, d.Piso, d.Depto, d.Telefono, d.Email, d.Localidad,
|
||||||
|
z.Nombre AS NombreZona
|
||||||
FROM dbo.dist_dtDistribuidores d
|
FROM dbo.dist_dtDistribuidores d
|
||||||
LEFT JOIN dbo.dist_dtZonas z ON d.Id_Zona = z.Id_Zona
|
LEFT JOIN dbo.dist_dtZonas z ON d.Id_Zona = z.Id_Zona
|
||||||
WHERE 1=1");
|
WHERE 1=1");
|
||||||
@@ -31,13 +35,13 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(nombreFilter))
|
if (!string.IsNullOrWhiteSpace(nombreFilter))
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND d.Nombre LIKE @Nombre");
|
sqlBuilder.Append(" AND d.Nombre LIKE @NombreParam");
|
||||||
parameters.Add("Nombre", $"%{nombreFilter}%");
|
parameters.Add("NombreParam", $"%{nombreFilter}%");
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(nroDocFilter))
|
if (!string.IsNullOrWhiteSpace(nroDocFilter))
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND d.NroDoc LIKE @NroDoc");
|
sqlBuilder.Append(" AND d.NroDoc LIKE @NroDocParam");
|
||||||
parameters.Add("NroDoc", $"%{nroDocFilter}%");
|
parameters.Add("NroDocParam", $"%{nroDocFilter}%");
|
||||||
}
|
}
|
||||||
sqlBuilder.Append(" ORDER BY d.Nombre;");
|
sqlBuilder.Append(" ORDER BY d.Nombre;");
|
||||||
|
|
||||||
@@ -53,7 +57,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error al obtener todos los Distribuidores.");
|
_logger.LogError(ex, "Error al obtener todos los Distribuidores. Filtros: Nombre='{NombreFilter}', NroDoc='{NroDocFilter}'", nombreFilter, nroDocFilter);
|
||||||
return Enumerable.Empty<(Distribuidor, string?)>();
|
return Enumerable.Empty<(Distribuidor, string?)>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,17 +65,20 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
public async Task<(Distribuidor? Distribuidor, string? NombreZona)> GetByIdAsync(int id)
|
public async Task<(Distribuidor? Distribuidor, string? NombreZona)> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT d.*, z.Nombre AS NombreZona
|
SELECT
|
||||||
|
d.Id_Distribuidor AS IdDistribuidor, d.Nombre, d.Contacto, d.NroDoc, d.Id_Zona AS IdZona,
|
||||||
|
d.Calle, d.Numero, d.Piso, d.Depto, d.Telefono, d.Email, d.Localidad,
|
||||||
|
z.Nombre AS NombreZona
|
||||||
FROM dbo.dist_dtDistribuidores d
|
FROM dbo.dist_dtDistribuidores d
|
||||||
LEFT JOIN dbo.dist_dtZonas z ON d.Id_Zona = z.Id_Zona
|
LEFT JOIN dbo.dist_dtZonas z ON d.Id_Zona = z.Id_Zona
|
||||||
WHERE d.Id_Distribuidor = @Id";
|
WHERE d.Id_Distribuidor = @IdParam";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
var result = await connection.QueryAsync<Distribuidor, string, (Distribuidor, string?)>(
|
var result = await connection.QueryAsync<Distribuidor, string, (Distribuidor?, string?)>(
|
||||||
sql,
|
sql,
|
||||||
(dist, zona) => (dist, zona),
|
(dist, zona) => (dist, zona),
|
||||||
new { Id = id },
|
new { IdParam = id },
|
||||||
splitOn: "NombreZona"
|
splitOn: "NombreZona"
|
||||||
);
|
);
|
||||||
return result.SingleOrDefault();
|
return result.SingleOrDefault();
|
||||||
@@ -85,73 +92,112 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
|
|
||||||
public async Task<Distribuidor?> GetByIdSimpleAsync(int id)
|
public async Task<Distribuidor?> GetByIdSimpleAsync(int id)
|
||||||
{
|
{
|
||||||
const string sql = "SELECT * FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @Id";
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona,
|
||||||
|
Calle, Numero, Piso, Depto, Telefono, Email, Localidad
|
||||||
|
FROM dbo.dist_dtDistribuidores
|
||||||
|
WHERE Id_Distribuidor = @IdParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.QuerySingleOrDefaultAsync<Distribuidor>(sql, new { Id = id });
|
return await connection.QuerySingleOrDefaultAsync<Distribuidor>(sql, new { IdParam = id });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener Distribuidor (simple) por ID: {IdDistribuidor}", id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> ExistsByNroDocAsync(string nroDoc, int? excludeIdDistribuidor = null)
|
public async Task<bool> ExistsByNroDocAsync(string nroDoc, int? excludeIdDistribuidor = null)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtDistribuidores WHERE NroDoc = @NroDoc");
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtDistribuidores WHERE NroDoc = @NroDocParam");
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new DynamicParameters();
|
||||||
parameters.Add("NroDoc", nroDoc);
|
parameters.Add("NroDocParam", nroDoc);
|
||||||
if (excludeIdDistribuidor.HasValue)
|
if (excludeIdDistribuidor.HasValue)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND Id_Distribuidor != @ExcludeId");
|
sqlBuilder.Append(" AND Id_Distribuidor != @ExcludeIdParam");
|
||||||
parameters.Add("ExcludeId", excludeIdDistribuidor.Value);
|
parameters.Add("ExcludeIdParam", excludeIdDistribuidor.Value);
|
||||||
}
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error en ExistsByNroDocAsync. NroDoc: {NroDoc}", nroDoc);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
public async Task<bool> ExistsByNameAsync(string nombre, int? excludeIdDistribuidor = null)
|
public async Task<bool> ExistsByNameAsync(string nombre, int? excludeIdDistribuidor = null)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtDistribuidores WHERE Nombre = @Nombre");
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtDistribuidores WHERE Nombre = @NombreParam");
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new DynamicParameters();
|
||||||
parameters.Add("Nombre", nombre);
|
parameters.Add("NombreParam", nombre);
|
||||||
if (excludeIdDistribuidor.HasValue)
|
if (excludeIdDistribuidor.HasValue)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND Id_Distribuidor != @ExcludeId");
|
sqlBuilder.Append(" AND Id_Distribuidor != @ExcludeIdParam");
|
||||||
parameters.Add("ExcludeId", excludeIdDistribuidor.Value);
|
parameters.Add("ExcludeIdParam", excludeIdDistribuidor.Value);
|
||||||
}
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error en ExistsByNameAsync. Nombre: {Nombre}", nombre);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> IsInUseAsync(int id)
|
public async Task<bool> IsInUseAsync(int id)
|
||||||
{
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
string[] checkQueries = {
|
string[] checkQueries = {
|
||||||
"SELECT TOP 1 1 FROM dbo.dist_EntradasSalidas WHERE Id_Distribuidor = @Id",
|
"SELECT TOP 1 1 FROM dbo.dist_EntradasSalidas WHERE Id_Distribuidor = @IdParam",
|
||||||
"SELECT TOP 1 1 FROM dbo.cue_PagosDistribuidor WHERE Id_Distribuidor = @Id",
|
"SELECT TOP 1 1 FROM dbo.cue_PagosDistribuidor WHERE Id_Distribuidor = @IdParam",
|
||||||
"SELECT TOP 1 1 FROM dbo.dist_PorcPago WHERE Id_Distribuidor = @Id"
|
"SELECT TOP 1 1 FROM dbo.dist_PorcPago WHERE Id_Distribuidor = @IdParam"
|
||||||
};
|
};
|
||||||
|
try
|
||||||
|
{
|
||||||
foreach (var query in checkQueries)
|
foreach (var query in checkQueries)
|
||||||
{
|
{
|
||||||
if (await connection.ExecuteScalarAsync<int?>(query, new { Id = id }) == 1) return true;
|
if (await connection.ExecuteScalarAsync<int?>(query, new { IdParam = id }) == 1) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error en IsInUseAsync para Distribuidor ID: {IdDistribuidor}", id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Distribuidor?> CreateAsync(Distribuidor nuevoDistribuidor, int idUsuario, IDbTransaction transaction)
|
public async Task<Distribuidor?> CreateAsync(Distribuidor nuevoDistribuidor, int idUsuario, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
const string sqlInsert = @"
|
const string sqlInsert = @"
|
||||||
INSERT INTO dbo.dist_dtDistribuidores (Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad)
|
INSERT INTO dbo.dist_dtDistribuidores (Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad)
|
||||||
OUTPUT INSERTED.*
|
OUTPUT INSERTED.Id_Distribuidor AS IdDistribuidor, INSERTED.Nombre, INSERTED.Contacto, INSERTED.NroDoc, INSERTED.Id_Zona AS IdZona,
|
||||||
|
INSERTED.Calle, INSERTED.Numero, INSERTED.Piso, INSERTED.Depto, INSERTED.Telefono, INSERTED.Email, INSERTED.Localidad
|
||||||
VALUES (@Nombre, @Contacto, @NroDoc, @IdZona, @Calle, @Numero, @Piso, @Depto, @Telefono, @Email, @Localidad);";
|
VALUES (@Nombre, @Contacto, @NroDoc, @IdZona, @Calle, @Numero, @Piso, @Depto, @Telefono, @Email, @Localidad);";
|
||||||
const string sqlInsertHistorico = @"
|
|
||||||
INSERT INTO dbo.dist_dtDistribuidores_H
|
|
||||||
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod)
|
|
||||||
VALUES (@IdDistribuidor, @Nombre, @Contacto, @NroDoc, @IdZona, @Calle, @Numero, @Piso, @Depto, @Telefono, @Email, @Localidad, @Id_Usuario, @FechaMod, @TipoMod);";
|
|
||||||
|
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var inserted = await connection.QuerySingleAsync<Distribuidor>(sqlInsert, nuevoDistribuidor, transaction);
|
var inserted = await connection.QuerySingleAsync<Distribuidor>(sqlInsert, nuevoDistribuidor, transaction);
|
||||||
if (inserted == null) throw new DataException("Error al crear distribuidor.");
|
if (inserted == null || inserted.IdDistribuidor == 0) throw new DataException("Error al crear distribuidor o al obtener el ID generado.");
|
||||||
|
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_dtDistribuidores_H
|
||||||
|
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
inserted.IdDistribuidor, inserted.Nombre, inserted.Contacto, inserted.NroDoc, inserted.IdZona,
|
IdDistribuidorParam = inserted.IdDistribuidor, NombreParam = inserted.Nombre, ContactoParam = inserted.Contacto, NroDocParam = inserted.NroDoc, IdZonaParam = inserted.IdZona,
|
||||||
inserted.Calle, inserted.Numero, inserted.Piso, inserted.Depto, inserted.Telefono, inserted.Email, inserted.Localidad,
|
CalleParam = inserted.Calle, NumeroParam = inserted.Numero, PisoParam = inserted.Piso, DeptoParam = inserted.Depto, TelefonoParam = inserted.Telefono, EmailParam = inserted.Email, LocalidadParam = inserted.Localidad,
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Creado"
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
return inserted;
|
return inserted;
|
||||||
}
|
}
|
||||||
@@ -160,8 +206,10 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var actual = await connection.QuerySingleOrDefaultAsync<Distribuidor>(
|
var actual = await connection.QuerySingleOrDefaultAsync<Distribuidor>(
|
||||||
"SELECT * FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdDistribuidor",
|
@"SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona,
|
||||||
new { distribuidorAActualizar.IdDistribuidor }, transaction);
|
Calle, Numero, Piso, Depto, Telefono, Email, Localidad
|
||||||
|
FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdDistribuidorParam",
|
||||||
|
new { IdDistribuidorParam = distribuidorAActualizar.IdDistribuidor }, transaction);
|
||||||
if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado.");
|
if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado.");
|
||||||
|
|
||||||
const string sqlUpdate = @"
|
const string sqlUpdate = @"
|
||||||
@@ -172,13 +220,13 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_dtDistribuidores_H
|
INSERT INTO dbo.dist_dtDistribuidores_H
|
||||||
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod)
|
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdDistribuidor, @Nombre, @Contacto, @NroDoc, @IdZona, @Calle, @Numero, @Piso, @Depto, @Telefono, @Email, @Localidad, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
IdDistribuidor = actual.IdDistribuidor, Nombre = actual.Nombre, Contacto = actual.Contacto, NroDoc = actual.NroDoc, IdZona = actual.IdZona,
|
IdDistribuidorParam = actual.IdDistribuidor, NombreParam = actual.Nombre, ContactoParam = actual.Contacto, NroDocParam = actual.NroDoc, IdZonaParam = actual.IdZona,
|
||||||
Calle = actual.Calle, Numero = actual.Numero, Piso = actual.Piso, Depto = actual.Depto, Telefono = actual.Telefono, Email = actual.Email, Localidad = actual.Localidad,
|
CalleParam = actual.Calle, NumeroParam = actual.Numero, PisoParam = actual.Piso, DeptoParam = actual.Depto, TelefonoParam = actual.Telefono, EmailParam = actual.Email, LocalidadParam = actual.Localidad,
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Actualizado"
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
|
|
||||||
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, distribuidorAActualizar, transaction);
|
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, distribuidorAActualizar, transaction);
|
||||||
@@ -189,23 +237,26 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var actual = await connection.QuerySingleOrDefaultAsync<Distribuidor>(
|
var actual = await connection.QuerySingleOrDefaultAsync<Distribuidor>(
|
||||||
"SELECT * FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @Id", new { Id = id }, transaction);
|
@"SELECT Id_Distribuidor AS IdDistribuidor, Nombre, Contacto, NroDoc, Id_Zona AS IdZona,
|
||||||
|
Calle, Numero, Piso, Depto, Telefono, Email, Localidad
|
||||||
|
FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdParam", new { IdParam = id }, transaction);
|
||||||
if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado.");
|
if (actual == null) throw new KeyNotFoundException("Distribuidor no encontrado.");
|
||||||
|
|
||||||
const string sqlDelete = "DELETE FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @Id";
|
// ... (resto del método DeleteAsync)
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.dist_dtDistribuidores WHERE Id_Distribuidor = @IdParam";
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_dtDistribuidores_H
|
INSERT INTO dbo.dist_dtDistribuidores_H
|
||||||
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod)
|
(Id_Distribuidor, Nombre, Contacto, NroDoc, Id_Zona, Calle, Numero, Piso, Depto, Telefono, Email, Localidad, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdDistribuidor, @Nombre, @Contacto, @NroDoc, @IdZona, @Calle, @Numero, @Piso, @Depto, @Telefono, @Email, @Localidad, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdDistribuidorParam, @NombreParam, @ContactoParam, @NroDocParam, @IdZonaParam, @CalleParam, @NumeroParam, @PisoParam, @DeptoParam, @TelefonoParam, @EmailParam, @LocalidadParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
IdDistribuidor = actual.IdDistribuidor, actual.Nombre, actual.Contacto, actual.NroDoc, actual.IdZona,
|
IdDistribuidorParam = actual.IdDistribuidor, NombreParam = actual.Nombre, ContactoParam = actual.Contacto, NroDocParam = actual.NroDoc, IdZonaParam = actual.IdZona,
|
||||||
actual.Calle, actual.Numero, actual.Piso, actual.Depto, actual.Telefono, actual.Email, actual.Localidad,
|
CalleParam = actual.Calle, NumeroParam = actual.Numero, PisoParam = actual.Piso, DeptoParam = actual.Depto, TelefonoParam = actual.Telefono, EmailParam = actual.Email, LocalidadParam = actual.Localidad,
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Eliminado"
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
|
|
||||||
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { Id = id }, transaction);
|
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id }, transaction);
|
||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
|
{
|
||||||
|
public class EntradaSalidaDistRepository : IEntradaSalidaDistRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<EntradaSalidaDistRepository> _log;
|
||||||
|
|
||||||
|
public EntradaSalidaDistRepository(DbConnectionFactory cf, ILogger<EntradaSalidaDistRepository> log)
|
||||||
|
{
|
||||||
|
_cf = cf;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<EntradaSalidaDist>> GetAllAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idPublicacion, int? idDistribuidor, string? tipoMovimiento)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(@"
|
||||||
|
SELECT
|
||||||
|
Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
|
||||||
|
Fecha, TipoMovimiento, Cantidad, Remito, Observacion,
|
||||||
|
Id_Precio AS IdPrecio, Id_Recargo AS IdRecargo, Id_Porcentaje AS IdPorcentaje
|
||||||
|
FROM dbo.dist_EntradasSalidas
|
||||||
|
WHERE 1=1");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
|
||||||
|
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND Fecha >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
|
||||||
|
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND Fecha <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date); }
|
||||||
|
if (idPublicacion.HasValue) { sqlBuilder.Append(" AND Id_Publicacion = @IdPublicacionParam"); parameters.Add("IdPublicacionParam", idPublicacion.Value); }
|
||||||
|
if (idDistribuidor.HasValue) { sqlBuilder.Append(" AND Id_Distribuidor = @IdDistribuidorParam"); parameters.Add("IdDistribuidorParam", idDistribuidor.Value); }
|
||||||
|
if (!string.IsNullOrWhiteSpace(tipoMovimiento)) { sqlBuilder.Append(" AND TipoMovimiento = @TipoMovimientoParam"); parameters.Add("TipoMovimientoParam", tipoMovimiento); }
|
||||||
|
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Parte DESC;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<EntradaSalidaDist>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Entradas/Salidas de Distribuidores.");
|
||||||
|
return Enumerable.Empty<EntradaSalidaDist>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EntradaSalidaDist?> GetByIdAsync(int idParte)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
|
||||||
|
Fecha, TipoMovimiento, Cantidad, Remito, Observacion,
|
||||||
|
Id_Precio AS IdPrecio, Id_Recargo AS IdRecargo, Id_Porcentaje AS IdPorcentaje
|
||||||
|
FROM dbo.dist_EntradasSalidas
|
||||||
|
WHERE Id_Parte = @IdParteParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<EntradaSalidaDist>(sql, new { IdParteParam = idParte });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener EntradaSalidaDist por ID: {IdParte}", idParte);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_EntradasSalidas WHERE Remito = @RemitoParam AND TipoMovimiento = @TipoMovimientoParam AND Id_Publicacion = @IdPublicacionParam");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("RemitoParam", remito);
|
||||||
|
parameters.Add("TipoMovimientoParam", tipoMovimiento);
|
||||||
|
parameters.Add("IdPublicacionParam", idPublicacion);
|
||||||
|
|
||||||
|
if (excludeIdParte.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Id_Parte != @ExcludeIdParteParam");
|
||||||
|
parameters.Add("ExcludeIdParteParam", excludeIdParte.Value);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en ExistsByRemitoAndTipoForPublicacionAsync. Remito: {Remito}", remito);
|
||||||
|
return true; // Asumir que existe en caso de error para prevenir duplicados
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<EntradaSalidaDist?> CreateAsync(EntradaSalidaDist nuevaES, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.dist_EntradasSalidas (Id_Publicacion, Id_Distribuidor, Fecha, TipoMovimiento, Cantidad, Remito, Observacion, Id_Precio, Id_Recargo, Id_Porcentaje)
|
||||||
|
OUTPUT INSERTED.Id_Parte AS IdParte, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Distribuidor AS IdDistribuidor,
|
||||||
|
INSERTED.Fecha, INSERTED.TipoMovimiento, INSERTED.Cantidad, INSERTED.Remito, INSERTED.Observacion,
|
||||||
|
INSERTED.Id_Precio AS IdPrecio, INSERTED.Id_Recargo AS IdRecargo, INSERTED.Id_Porcentaje AS IdPorcentaje
|
||||||
|
VALUES (@IdPublicacion, @IdDistribuidor, @Fecha, @TipoMovimiento, @Cantidad, @Remito, @Observacion, @IdPrecio, @IdRecargo, @IdPorcentaje);";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_EntradasSalidas_H
|
||||||
|
(Id_Parte, Id_Publicacion, Id_Distribuidor, Fecha, TipoMovimiento, Cantidad, Remito, Observacion, Id_Precio, Id_Recargo, Id_Porcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdParteHist, @IdPublicacionHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @CantidadHist, @RemitoHist, @ObservacionHist, @IdPrecioHist, @IdRecargoHist, @IdPorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<EntradaSalidaDist>(sqlInsert, nuevaES, transaction);
|
||||||
|
if (inserted == null || inserted.IdParte == 0) throw new DataException("Error al crear Entrada/Salida o ID no generado.");
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdParteHist = inserted.IdParte, IdPublicacionHist = inserted.IdPublicacion, IdDistribuidorHist = inserted.IdDistribuidor,
|
||||||
|
FechaHist = inserted.Fecha, TipoMovimientoHist = inserted.TipoMovimiento, CantidadHist = inserted.Cantidad, RemitoHist = inserted.Remito, ObservacionHist = inserted.Observacion,
|
||||||
|
IdPrecioHist = inserted.IdPrecio, IdRecargoHist = inserted.IdRecargo, IdPorcentajeHist = inserted.IdPorcentaje,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creada"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(EntradaSalidaDist esAActualizar, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<EntradaSalidaDist>(
|
||||||
|
@"SELECT Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor, Fecha, TipoMovimiento, Cantidad, Remito, Observacion, Id_Precio AS IdPrecio, Id_Recargo AS IdRecargo, Id_Porcentaje AS IdPorcentaje
|
||||||
|
FROM dbo.dist_EntradasSalidas WHERE Id_Parte = @IdParteParam",
|
||||||
|
new { IdParteParam = esAActualizar.IdParte }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Registro de Entrada/Salida no encontrado.");
|
||||||
|
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.dist_EntradasSalidas SET
|
||||||
|
Cantidad = @Cantidad, Observacion = @Observacion
|
||||||
|
-- Publicacion, Distribuidor, Fecha, TipoMovimiento, Remito, Ids de precio/recargo/porc no se modifican aquí
|
||||||
|
WHERE Id_Parte = @IdParte;";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_EntradasSalidas_H
|
||||||
|
(Id_Parte, Id_Publicacion, Id_Distribuidor, Fecha, TipoMovimiento, Cantidad, Remito, Observacion, Id_Precio, Id_Recargo, Id_Porcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdParteHist, @IdPublicacionHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @CantidadHist, @RemitoHist, @ObservacionHist, @IdPrecioHist, @IdRecargoHist, @IdPorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdParteHist = actual.IdParte, IdPublicacionHist = actual.IdPublicacion, IdDistribuidorHist = actual.IdDistribuidor,
|
||||||
|
FechaHist = actual.Fecha, TipoMovimientoHist = actual.TipoMovimiento, CantidadHist = actual.Cantidad, RemitoHist = actual.Remito, ObservacionHist = actual.Observacion,
|
||||||
|
IdPrecioHist = actual.IdPrecio, IdRecargoHist = actual.IdRecargo, IdPorcentajeHist = actual.IdPorcentaje, // Valores ANTERIORES
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizada"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, esAActualizar, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<EntradaSalidaDist>(
|
||||||
|
@"SELECT Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor, Fecha, TipoMovimiento, Cantidad, Remito, Observacion, Id_Precio AS IdPrecio, Id_Recargo AS IdRecargo, Id_Porcentaje AS IdPorcentaje
|
||||||
|
FROM dbo.dist_EntradasSalidas WHERE Id_Parte = @IdParteParam",
|
||||||
|
new { IdParteParam = idParte }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Registro de Entrada/Salida no encontrado para eliminar.");
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.dist_EntradasSalidas WHERE Id_Parte = @IdParteParam";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_EntradasSalidas_H
|
||||||
|
(Id_Parte, Id_Publicacion, Id_Distribuidor, Fecha, TipoMovimiento, Cantidad, Remito, Observacion, Id_Precio, Id_Recargo, Id_Porcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdParteHist, @IdPublicacionHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @CantidadHist, @RemitoHist, @ObservacionHist, @IdPrecioHist, @IdRecargoHist, @IdPorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdParteHist = actual.IdParte, IdPublicacionHist = actual.IdPublicacion, IdDistribuidorHist = actual.IdDistribuidor,
|
||||||
|
FechaHist = actual.Fecha, TipoMovimientoHist = actual.TipoMovimiento, CantidadHist = actual.Cantidad, RemitoHist = actual.Remito, ObservacionHist = actual.Observacion,
|
||||||
|
IdPrecioHist = actual.IdPrecio, IdRecargoHist = actual.IdRecargo, IdPorcentajeHist = actual.IdPorcentaje,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
|
{
|
||||||
|
public interface IEntradaSalidaDistRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<EntradaSalidaDist>> GetAllAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idPublicacion, int? idDistribuidor, string? tipoMovimiento);
|
||||||
|
Task<EntradaSalidaDist?> GetByIdAsync(int idParte);
|
||||||
|
Task<EntradaSalidaDist?> CreateAsync(EntradaSalidaDist nuevaES, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(EntradaSalidaDist esAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> ExistsByRemitoAndTipoForPublicacionAsync(int remito, string tipoMovimiento, int idPublicacion, int? excludeIdParte = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -5,6 +8,13 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
public interface IPorcMonCanillaRepository
|
public interface IPorcMonCanillaRepository
|
||||||
{
|
{
|
||||||
|
Task<IEnumerable<(PorcMonCanilla Item, string NomApeCanilla)>> GetByPublicacionIdAsync(int idPublicacion);
|
||||||
|
Task<PorcMonCanilla?> GetByIdAsync(int idPorcMon);
|
||||||
|
Task<PorcMonCanilla?> GetActiveByPublicacionCanillaAndDateAsync(int idPublicacion, int idCanilla, DateTime fecha, IDbTransaction? transaction = null);
|
||||||
|
Task<PorcMonCanilla?> CreateAsync(PorcMonCanilla nuevoItem, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(PorcMonCanilla itemAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteAsync(int idPorcMon, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction);
|
Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction);
|
||||||
|
Task<PorcMonCanilla?> GetPreviousActiveAsync(int idPublicacion, int idCanilla, DateTime vigenciaDNuevo, IDbTransaction transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,20 @@
|
|||||||
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
|
{
|
||||||
|
public interface IPorcPagoRepository
|
||||||
{
|
{
|
||||||
public interface IPorcPagoRepository {
|
Task<IEnumerable<(PorcPago PorcPago, string NombreDistribuidor)>> GetByPublicacionIdAsync(int idPublicacion);
|
||||||
|
Task<PorcPago?> GetByIdAsync(int idPorcentaje);
|
||||||
|
Task<PorcPago?> GetActiveByPublicacionDistribuidorAndDateAsync(int idPublicacion, int idDistribuidor, DateTime fecha, IDbTransaction? transaction = null);
|
||||||
|
Task<PorcPago?> CreateAsync(PorcPago nuevoPorcPago, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(PorcPago porcPagoAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteAsync(int idPorcentaje, int idUsuario, IDbTransaction transaction);
|
||||||
Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction);
|
Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction);
|
||||||
|
Task<PorcPago?> GetPreviousActivePorcPagoAsync(int idPublicacion, int idDistribuidor, DateTime vigenciaDNuevo, IDbTransaction transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -5,6 +7,13 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
public interface IPubliSeccionRepository
|
public interface IPubliSeccionRepository
|
||||||
{
|
{
|
||||||
Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction);
|
Task<IEnumerable<PubliSeccion>> GetByPublicacionIdAsync(int idPublicacion, bool? soloActivas = null);
|
||||||
|
Task<PubliSeccion?> GetByIdAsync(int idSeccion);
|
||||||
|
Task<PubliSeccion?> CreateAsync(PubliSeccion nuevaSeccion, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(PubliSeccion seccionAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteAsync(int idSeccion, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction); // Ya existe
|
||||||
|
Task<bool> ExistsByNameInPublicacionAsync(string nombre, int idPublicacion, int? excludeIdSeccion = null);
|
||||||
|
Task<bool> IsInUseAsync(int idSeccion); // Verificar en bob_RegPublicaciones, bob_StockBobinas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -5,6 +8,13 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
public interface IRecargoZonaRepository
|
public interface IRecargoZonaRepository
|
||||||
{
|
{
|
||||||
Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction);
|
Task<IEnumerable<(RecargoZona Recargo, string NombreZona)>> GetByPublicacionIdAsync(int idPublicacion);
|
||||||
|
Task<RecargoZona?> GetByIdAsync(int idRecargo); // Para obtener un recargo específico
|
||||||
|
Task<RecargoZona?> GetActiveByPublicacionZonaAndDateAsync(int idPublicacion, int idZona, DateTime fecha, IDbTransaction? transaction = null);
|
||||||
|
Task<RecargoZona?> CreateAsync(RecargoZona nuevoRecargo, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(RecargoZona recargoAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteAsync(int idRecargo, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction); // Ya existe
|
||||||
|
Task<RecargoZona?> GetPreviousActiveRecargoAsync(int idPublicacion, int idZona, DateTime vigenciaDNuevo, IDbTransaction transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
|
{
|
||||||
|
public interface ISalidaOtroDestinoRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<SalidaOtroDestino>> GetAllAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idPublicacion, int? idDestino);
|
||||||
|
Task<SalidaOtroDestino?> GetByIdAsync(int idParte);
|
||||||
|
Task<SalidaOtroDestino?> CreateAsync(SalidaOtroDestino nuevaSalida, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(SalidaOtroDestino salidaAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,56 +1,255 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using System.Data;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using GestionIntegral.Api.Models.Distribucion;
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
{
|
{
|
||||||
public class PorcMonCanillaRepository : IPorcMonCanillaRepository
|
public class PorcMonCanillaRepository : IPorcMonCanillaRepository
|
||||||
{
|
{
|
||||||
private readonly DbConnectionFactory _cf; private readonly ILogger<PorcMonCanillaRepository> _log;
|
private readonly DbConnectionFactory _cf;
|
||||||
public PorcMonCanillaRepository(DbConnectionFactory cf, ILogger<PorcMonCanillaRepository> log) { _cf = cf; _log = log; }
|
private readonly ILogger<PorcMonCanillaRepository> _log;
|
||||||
|
|
||||||
|
public PorcMonCanillaRepository(DbConnectionFactory connectionFactory, ILogger<PorcMonCanillaRepository> logger)
|
||||||
|
{
|
||||||
|
_cf = connectionFactory;
|
||||||
|
_log = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<(PorcMonCanilla Item, string NomApeCanilla)>> GetByPublicacionIdAsync(int idPublicacion)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
pmc.Id_PorcMon AS IdPorcMon, pmc.Id_Publicacion AS IdPublicacion, pmc.Id_Canilla AS IdCanilla,
|
||||||
|
pmc.VigenciaD, pmc.VigenciaH, pmc.PorcMon, pmc.EsPorcentaje,
|
||||||
|
c.NomApe AS NomApeCanilla
|
||||||
|
FROM dbo.dist_PorcMonPagoCanilla pmc
|
||||||
|
INNER JOIN dbo.dist_dtCanillas c ON pmc.Id_Canilla = c.Id_Canilla
|
||||||
|
WHERE pmc.Id_Publicacion = @IdPublicacionParam
|
||||||
|
ORDER BY c.NomApe, pmc.VigenciaD DESC";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<PorcMonCanilla, string, (PorcMonCanilla, string)>(
|
||||||
|
sql,
|
||||||
|
(item, nomApe) => (item, nomApe),
|
||||||
|
new { IdPublicacionParam = idPublicacion },
|
||||||
|
splitOn: "NomApeCanilla"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener PorcMonCanilla por Publicacion ID: {IdPublicacion}", idPublicacion);
|
||||||
|
return Enumerable.Empty<(PorcMonCanilla, string)>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PorcMonCanilla?> GetByIdAsync(int idPorcMon)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_PorcMon AS IdPorcMon, Id_Publicacion AS IdPublicacion, Id_Canilla AS IdCanilla,
|
||||||
|
VigenciaD, VigenciaH, PorcMon, EsPorcentaje
|
||||||
|
FROM dbo.dist_PorcMonPagoCanilla
|
||||||
|
WHERE Id_PorcMon = @IdPorcMonParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<PorcMonCanilla>(sql, new { IdPorcMonParam = idPorcMon });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener PorcMonCanilla por ID: {IdPorcMon}", idPorcMon);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PorcMonCanilla?> GetActiveByPublicacionCanillaAndDateAsync(int idPublicacion, int idCanilla, DateTime fecha, IDbTransaction? transaction = null)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT TOP 1
|
||||||
|
Id_PorcMon AS IdPorcMon, Id_Publicacion AS IdPublicacion, Id_Canilla AS IdCanilla,
|
||||||
|
VigenciaD, VigenciaH, PorcMon, EsPorcentaje
|
||||||
|
FROM dbo.dist_PorcMonPagoCanilla
|
||||||
|
WHERE Id_Publicacion = @IdPublicacionParam AND Id_Canilla = @IdCanillaParam
|
||||||
|
AND VigenciaD <= @FechaParam
|
||||||
|
AND (VigenciaH IS NULL OR VigenciaH >= @FechaParam)
|
||||||
|
ORDER BY VigenciaD DESC;";
|
||||||
|
|
||||||
|
var cn = transaction?.Connection ?? _cf.CreateConnection();
|
||||||
|
bool ownConnection = transaction == null;
|
||||||
|
PorcMonCanilla? result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
|
||||||
|
result = await cn.QuerySingleOrDefaultAsync<PorcMonCanilla>(sql,
|
||||||
|
new { IdPublicacionParam = idPublicacion, IdCanillaParam = idCanilla, FechaParam = fecha.Date },
|
||||||
|
transaction);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Open && cn is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync();
|
||||||
|
if (ownConnection) (cn as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PorcMonCanilla?> GetPreviousActiveAsync(int idPublicacion, int idCanilla, DateTime vigenciaDNuevo, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT TOP 1
|
||||||
|
Id_PorcMon AS IdPorcMon, Id_Publicacion AS IdPublicacion, Id_Canilla AS IdCanilla,
|
||||||
|
VigenciaD, VigenciaH, PorcMon, EsPorcentaje
|
||||||
|
FROM dbo.dist_PorcMonPagoCanilla
|
||||||
|
WHERE Id_Publicacion = @IdPublicacionParam AND Id_Canilla = @IdCanillaParam
|
||||||
|
AND VigenciaD < @VigenciaDNuevoParam
|
||||||
|
AND VigenciaH IS NULL
|
||||||
|
ORDER BY VigenciaD DESC;";
|
||||||
|
return await transaction.Connection!.QuerySingleOrDefaultAsync<PorcMonCanilla>(sql,
|
||||||
|
new { IdPublicacionParam = idPublicacion, IdCanillaParam = idCanilla, VigenciaDNuevoParam = vigenciaDNuevo.Date },
|
||||||
|
transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PorcMonCanilla?> CreateAsync(PorcMonCanilla nuevoItem, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.dist_PorcMonPagoCanilla (Id_Publicacion, Id_Canilla, VigenciaD, VigenciaH, PorcMon, EsPorcentaje)
|
||||||
|
OUTPUT INSERTED.Id_PorcMon AS IdPorcMon, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Canilla AS IdCanilla,
|
||||||
|
INSERTED.VigenciaD, INSERTED.VigenciaH, INSERTED.PorcMon, INSERTED.EsPorcentaje
|
||||||
|
VALUES (@IdPublicacion, @IdCanilla, @VigenciaD, @VigenciaH, @PorcMon, @EsPorcentaje);";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_PorcMonPagoCanilla_H (Id_PorcMon, Id_Publicacion, Id_Canilla, VigenciaD, VigenciaH, PorcMon, EsPorcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdPorcMonParam, @IdPublicacionParam, @IdCanillaParam, @VigenciaDParam, @VigenciaHParam, @PorcMonParam, @EsPorcentajeParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<PorcMonCanilla>(sqlInsert, nuevoItem, transaction);
|
||||||
|
if (inserted == null || inserted.IdPorcMon == 0) throw new DataException("Error al crear PorcMonCanilla o ID no generado.");
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdPorcMonParam = inserted.IdPorcMon,
|
||||||
|
IdPublicacionParam = inserted.IdPublicacion,
|
||||||
|
IdCanillaParam = inserted.IdCanilla,
|
||||||
|
VigenciaDParam = inserted.VigenciaD,
|
||||||
|
VigenciaHParam = inserted.VigenciaH,
|
||||||
|
PorcMonParam = inserted.PorcMon,
|
||||||
|
EsPorcentajeParam = inserted.EsPorcentaje,
|
||||||
|
IdUsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Creado"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(PorcMonCanilla itemAActualizar, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PorcMonCanilla>(
|
||||||
|
@"SELECT Id_PorcMon AS IdPorcMon, Id_Publicacion AS IdPublicacion, Id_Canilla AS IdCanilla,
|
||||||
|
VigenciaD, VigenciaH, PorcMon, EsPorcentaje
|
||||||
|
FROM dbo.dist_PorcMonPagoCanilla WHERE Id_PorcMon = @IdPorcMonParam",
|
||||||
|
new { IdPorcMonParam = itemAActualizar.IdPorcMon }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Registro PorcMonCanilla no encontrado.");
|
||||||
|
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.dist_PorcMonPagoCanilla SET
|
||||||
|
PorcMon = @PorcMon, EsPorcentaje = @EsPorcentaje, VigenciaH = @VigenciaH
|
||||||
|
WHERE Id_PorcMon = @IdPorcMon;";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_PorcMonPagoCanilla_H (Id_PorcMon, Id_Publicacion, Id_Canilla, VigenciaD, VigenciaH, PorcMon, EsPorcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdPorcMonParam, @IdPublicacionParam, @IdCanillaParam, @VigenciaDParam, @VigenciaHParam, @PorcMonParam, @EsPorcentajeParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdPorcMonParam = actual.IdPorcMon,
|
||||||
|
IdPublicacionParam = actual.IdPublicacion,
|
||||||
|
IdCanillaParam = actual.IdCanilla,
|
||||||
|
VigenciaDParam = actual.VigenciaD,
|
||||||
|
VigenciaHParam = actual.VigenciaH, // Valor ANTERIOR
|
||||||
|
PorcMonParam = actual.PorcMon, // Valor ANTERIOR
|
||||||
|
EsPorcentajeParam = actual.EsPorcentaje, // Valor ANTERIOR
|
||||||
|
IdUsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Actualizado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, itemAActualizar, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idPorcMon, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PorcMonCanilla>(
|
||||||
|
@"SELECT Id_PorcMon AS IdPorcMon, Id_Publicacion AS IdPublicacion, Id_Canilla AS IdCanilla,
|
||||||
|
VigenciaD, VigenciaH, PorcMon, EsPorcentaje
|
||||||
|
FROM dbo.dist_PorcMonPagoCanilla WHERE Id_PorcMon = @IdPorcMonParam",
|
||||||
|
new { IdPorcMonParam = idPorcMon }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Registro PorcMonCanilla no encontrado para eliminar.");
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.dist_PorcMonPagoCanilla WHERE Id_PorcMon = @IdPorcMonParam";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_PorcMonPagoCanilla_H (Id_PorcMon, Id_Publicacion, Id_Canilla, VigenciaD, VigenciaH, PorcMon, EsPorcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdPorcMonParam, @IdPublicacionParam, @IdCanillaParam, @VigenciaDParam, @VigenciaHParam, @PorcMonParam, @EsPorcentajeParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdPorcMonParam = actual.IdPorcMon,
|
||||||
|
IdPublicacionParam = actual.IdPublicacion,
|
||||||
|
IdCanillaParam = actual.IdCanilla,
|
||||||
|
VigenciaDParam = actual.VigenciaD,
|
||||||
|
VigenciaHParam = actual.VigenciaH,
|
||||||
|
PorcMonParam = actual.PorcMon,
|
||||||
|
EsPorcentajeParam = actual.EsPorcentaje,
|
||||||
|
IdUsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Eliminado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPorcMonParam = idPorcMon }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
|
public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
const string selectSql = "SELECT * FROM dbo.dist_PorcMonPagoCanilla WHERE Id_Publicacion = @IdPublicacion";
|
const string selectSql = @"
|
||||||
var itemsToDelete = await transaction.Connection!.QueryAsync<PorcMonCanilla>(selectSql, new { IdPublicacion = idPublicacion }, transaction);
|
SELECT Id_PorcMon AS IdPorcMon, Id_Publicacion AS IdPublicacion, Id_Canilla AS IdCanilla,
|
||||||
|
VigenciaD, VigenciaH, PorcMon, EsPorcentaje
|
||||||
|
FROM dbo.dist_PorcMonPagoCanilla WHERE Id_Publicacion = @IdPublicacionParam";
|
||||||
|
var itemsToDelete = await transaction.Connection!.QueryAsync<PorcMonCanilla>(selectSql, new { IdPublicacionParam = idPublicacion }, transaction);
|
||||||
|
|
||||||
if (itemsToDelete.Any())
|
if (itemsToDelete.Any())
|
||||||
{
|
{
|
||||||
const string insertHistoricoSql = @"
|
const string insertHistoricoSql = @"
|
||||||
INSERT INTO dbo.dist_PorcMonPagoCanilla_H (Id_PorcMon, Id_Publicacion, Id_Canilla, VigenciaD, VigenciaH, PorcMon, EsPorcentaje, Id_Usuario, FechaMod, TipoMod)
|
INSERT INTO dbo.dist_PorcMonPagoCanilla_H (Id_PorcMon, Id_Publicacion, Id_Canilla, VigenciaD, VigenciaH, PorcMon, EsPorcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@Id_PorcMon, @Id_Publicacion, @Id_Canilla, @VigenciaD, @VigenciaH, @PorcMon, @EsPorcentaje, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdPorcMonParam, @IdPublicacionParam, @IdCanillaParam, @VigenciaDParam, @VigenciaHParam, @PorcMonParam, @EsPorcentajeParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
foreach (var item in itemsToDelete)
|
foreach (var item in itemsToDelete)
|
||||||
{
|
{
|
||||||
await transaction.Connection!.ExecuteAsync(insertHistoricoSql, new
|
await transaction.Connection!.ExecuteAsync(insertHistoricoSql, new
|
||||||
{
|
{
|
||||||
Id_PorcMon = item.IdPorcMon, // Mapeo de propiedad a parámetro SQL
|
IdPorcMonParam = item.IdPorcMon,
|
||||||
Id_Publicacion = item.IdPublicacion,
|
IdPublicacionParam = item.IdPublicacion,
|
||||||
Id_Canilla = item.IdCanilla,
|
IdCanillaParam = item.IdCanilla,
|
||||||
item.VigenciaD,
|
VigenciaDParam = item.VigenciaD,
|
||||||
item.VigenciaH,
|
VigenciaHParam = item.VigenciaH,
|
||||||
item.PorcMon,
|
PorcMonParam = item.PorcMon,
|
||||||
item.EsPorcentaje,
|
EsPorcentajeParam = item.EsPorcentaje,
|
||||||
Id_Usuario = idUsuarioAuditoria,
|
IdUsuarioParam = idUsuarioAuditoria,
|
||||||
FechaMod = DateTime.Now,
|
FechaModParam = DateTime.Now,
|
||||||
TipoMod = "Eliminado (Cascada)"
|
TipoModParam = "Eliminado (Cascada)"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const string deleteSql = "DELETE FROM dbo.dist_PorcMonPagoCanilla WHERE Id_Publicacion = @IdPublicacionParam";
|
||||||
const string deleteSql = "DELETE FROM dbo.dist_PorcMonPagoCanilla WHERE Id_Publicacion = @IdPublicacion";
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await transaction.Connection!.ExecuteAsync(deleteSql, new { IdPublicacion = idPublicacion }, transaction);
|
await transaction.Connection!.ExecuteAsync(deleteSql, new { IdPublicacionParam = idPublicacion }, transaction: transaction);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (System.Exception e)
|
catch (System.Exception ex)
|
||||||
{
|
{
|
||||||
_log.LogError(e, "Error al eliminar PorcMonCanilla por IdPublicacion: {idPublicacion}", idPublicacion);
|
_log.LogError(ex, "Error al eliminar PorcMonCanilla por IdPublicacion: {idPublicacion}", idPublicacion);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,249 @@
|
|||||||
using Dapper; using System.Data;
|
using Dapper;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using GestionIntegral.Api.Models.Distribucion;
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
{
|
{
|
||||||
public class PorcPagoRepository : IPorcPagoRepository
|
public class PorcPagoRepository : IPorcPagoRepository
|
||||||
{
|
{
|
||||||
private readonly DbConnectionFactory _cf; private readonly ILogger<PorcPagoRepository> _log;
|
private readonly DbConnectionFactory _cf; // Renombrado para brevedad
|
||||||
public PorcPagoRepository(DbConnectionFactory cf, ILogger<PorcPagoRepository> log) { _cf = cf; _log = log; }
|
private readonly ILogger<PorcPagoRepository> _log; // Renombrado para brevedad
|
||||||
|
|
||||||
|
public PorcPagoRepository(DbConnectionFactory connectionFactory, ILogger<PorcPagoRepository> logger)
|
||||||
|
{
|
||||||
|
_cf = connectionFactory;
|
||||||
|
_log = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<(PorcPago PorcPago, string NombreDistribuidor)>> GetByPublicacionIdAsync(int idPublicacion)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
pp.Id_Porcentaje AS IdPorcentaje, pp.Id_Publicacion AS IdPublicacion, pp.Id_Distribuidor AS IdDistribuidor,
|
||||||
|
pp.VigenciaD, pp.VigenciaH, pp.Porcentaje,
|
||||||
|
d.Nombre AS NombreDistribuidor
|
||||||
|
FROM dbo.dist_PorcPago pp
|
||||||
|
INNER JOIN dbo.dist_dtDistribuidores d ON pp.Id_Distribuidor = d.Id_Distribuidor
|
||||||
|
WHERE pp.Id_Publicacion = @IdPublicacionParam
|
||||||
|
ORDER BY d.Nombre, pp.VigenciaD DESC";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<PorcPago, string, (PorcPago, string)>(
|
||||||
|
sql,
|
||||||
|
(porcPago, nombreDist) => (porcPago, nombreDist),
|
||||||
|
new { IdPublicacionParam = idPublicacion },
|
||||||
|
splitOn: "NombreDistribuidor"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Porcentajes de Pago por Publicacion ID: {IdPublicacion}", idPublicacion);
|
||||||
|
return Enumerable.Empty<(PorcPago, string)>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PorcPago?> GetByIdAsync(int idPorcentaje)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
|
||||||
|
VigenciaD, VigenciaH, Porcentaje
|
||||||
|
FROM dbo.dist_PorcPago
|
||||||
|
WHERE Id_Porcentaje = @IdPorcentajeParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<PorcPago>(sql, new { IdPorcentajeParam = idPorcentaje });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Porcentaje de Pago por ID: {IdPorcentaje}", idPorcentaje);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PorcPago?> GetActiveByPublicacionDistribuidorAndDateAsync(int idPublicacion, int idDistribuidor, DateTime fecha, IDbTransaction? transaction = null)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT TOP 1
|
||||||
|
Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
|
||||||
|
VigenciaD, VigenciaH, Porcentaje
|
||||||
|
FROM dbo.dist_PorcPago
|
||||||
|
WHERE Id_Publicacion = @IdPublicacionParam AND Id_Distribuidor = @IdDistribuidorParam
|
||||||
|
AND VigenciaD <= @FechaParam
|
||||||
|
AND (VigenciaH IS NULL OR VigenciaH >= @FechaParam)
|
||||||
|
ORDER BY VigenciaD DESC;";
|
||||||
|
|
||||||
|
var cn = transaction?.Connection ?? _cf.CreateConnection();
|
||||||
|
bool ownConnection = transaction == null;
|
||||||
|
PorcPago? result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
|
||||||
|
result = await cn.QuerySingleOrDefaultAsync<PorcPago>(sql, new { IdPublicacionParam = idPublicacion, IdDistribuidorParam = idDistribuidor, FechaParam = fecha.Date }, transaction);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Open && cn is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync();
|
||||||
|
if (ownConnection) (cn as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PorcPago?> GetPreviousActivePorcPagoAsync(int idPublicacion, int idDistribuidor, DateTime vigenciaDNuevo, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT TOP 1
|
||||||
|
Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
|
||||||
|
VigenciaD, VigenciaH, Porcentaje
|
||||||
|
FROM dbo.dist_PorcPago
|
||||||
|
WHERE Id_Publicacion = @IdPublicacionParam AND Id_Distribuidor = @IdDistribuidorParam
|
||||||
|
AND VigenciaD < @VigenciaDNuevoParam
|
||||||
|
AND VigenciaH IS NULL
|
||||||
|
ORDER BY VigenciaD DESC;";
|
||||||
|
return await transaction.Connection!.QuerySingleOrDefaultAsync<PorcPago>(sql,
|
||||||
|
new { IdPublicacionParam = idPublicacion, IdDistribuidorParam = idDistribuidor, VigenciaDNuevoParam = vigenciaDNuevo.Date },
|
||||||
|
transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PorcPago?> CreateAsync(PorcPago nuevoPorcPago, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.dist_PorcPago (Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje)
|
||||||
|
OUTPUT INSERTED.Id_Porcentaje AS IdPorcentaje, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Distribuidor AS IdDistribuidor,
|
||||||
|
INSERTED.VigenciaD, INSERTED.VigenciaH, INSERTED.Porcentaje
|
||||||
|
VALUES (@IdPublicacion, @IdDistribuidor, @VigenciaD, @VigenciaH, @Porcentaje);";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_PorcPago_H (Id_Porcentaje, Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdPorcentajeHist, @IdPublicacionHist, @IdDistribuidorHist, @VigenciaDHist, @VigenciaHHist, @PorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<PorcPago>(sqlInsert, nuevoPorcPago, transaction);
|
||||||
|
if (inserted == null || inserted.IdPorcentaje == 0) throw new DataException("Error al crear porcentaje de pago o al obtener ID.");
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdPorcentajeHist = inserted.IdPorcentaje,
|
||||||
|
IdPublicacionHist = inserted.IdPublicacion,
|
||||||
|
IdDistribuidorHist = inserted.IdDistribuidor,
|
||||||
|
VigenciaDHist = inserted.VigenciaD,
|
||||||
|
VigenciaHHist = inserted.VigenciaH,
|
||||||
|
PorcentajeHist = inserted.Porcentaje,
|
||||||
|
IdUsuarioHist = idUsuario,
|
||||||
|
FechaModHist = DateTime.Now,
|
||||||
|
TipoModHist = "Creado"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(PorcPago porcPagoAActualizar, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PorcPago>(
|
||||||
|
@"SELECT Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
|
||||||
|
VigenciaD, VigenciaH, Porcentaje
|
||||||
|
FROM dbo.dist_PorcPago WHERE Id_Porcentaje = @IdPorcentajeParam",
|
||||||
|
new { IdPorcentajeParam = porcPagoAActualizar.IdPorcentaje }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Porcentaje de pago no encontrado.");
|
||||||
|
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.dist_PorcPago SET
|
||||||
|
Porcentaje = @Porcentaje, VigenciaH = @VigenciaH
|
||||||
|
WHERE Id_Porcentaje = @IdPorcentaje;";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_PorcPago_H (Id_Porcentaje, Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdPorcentajeHist, @IdPublicacionHist, @IdDistribuidorHist, @VigenciaDHist, @VigenciaHHist, @PorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdPorcentajeHist = actual.IdPorcentaje,
|
||||||
|
IdPublicacionHist = actual.IdPublicacion,
|
||||||
|
IdDistribuidorHist = actual.IdDistribuidor,
|
||||||
|
VigenciaDHist = actual.VigenciaD,
|
||||||
|
VigenciaHHist = actual.VigenciaH, // Valor ANTERIOR de VigenciaH
|
||||||
|
PorcentajeHist = actual.Porcentaje, // Valor ANTERIOR de Porcentaje
|
||||||
|
IdUsuarioHist = idUsuario,
|
||||||
|
FechaModHist = DateTime.Now,
|
||||||
|
TipoModHist = "Actualizado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, porcPagoAActualizar, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idPorcentaje, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PorcPago>(
|
||||||
|
@"SELECT Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
|
||||||
|
VigenciaD, VigenciaH, Porcentaje
|
||||||
|
FROM dbo.dist_PorcPago WHERE Id_Porcentaje = @IdPorcentajeParam",
|
||||||
|
new { IdPorcentajeParam = idPorcentaje }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Porcentaje de pago no encontrado para eliminar.");
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.dist_PorcPago WHERE Id_Porcentaje = @IdPorcentajeParam";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_PorcPago_H (Id_Porcentaje, Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdPorcentajeHist, @IdPublicacionHist, @IdDistribuidorHist, @VigenciaDHist, @VigenciaHHist, @PorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdPorcentajeHist = actual.IdPorcentaje,
|
||||||
|
IdPublicacionHist = actual.IdPublicacion,
|
||||||
|
IdDistribuidorHist = actual.IdDistribuidor,
|
||||||
|
VigenciaDHist = actual.VigenciaD,
|
||||||
|
VigenciaHHist = actual.VigenciaH,
|
||||||
|
PorcentajeHist = actual.Porcentaje,
|
||||||
|
IdUsuarioHist = idUsuario,
|
||||||
|
FechaModHist = DateTime.Now,
|
||||||
|
TipoModHist = "Eliminado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPorcentajeParam = idPorcentaje }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
|
public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
const string selectSql = "SELECT * FROM dbo.dist_PorcPago WHERE Id_Publicacion = @IdPublicacion";
|
const string selectSql = @"
|
||||||
var itemsToDelete = await transaction.Connection!.QueryAsync<PorcPago>(selectSql, new { IdPublicacion = idPublicacion }, transaction);
|
SELECT Id_Porcentaje AS IdPorcentaje, Id_Publicacion AS IdPublicacion, Id_Distribuidor AS IdDistribuidor,
|
||||||
|
VigenciaD, VigenciaH, Porcentaje
|
||||||
|
FROM dbo.dist_PorcPago WHERE Id_Publicacion = @IdPublicacionParam";
|
||||||
|
var itemsToDelete = await transaction.Connection!.QueryAsync<PorcPago>(selectSql, new { IdPublicacionParam = idPublicacion }, transaction);
|
||||||
|
|
||||||
if (itemsToDelete.Any())
|
if (itemsToDelete.Any())
|
||||||
{
|
{
|
||||||
const string insertHistoricoSql = @"
|
const string insertHistoricoSql = @"
|
||||||
INSERT INTO dbo.dist_PorcPago_H (Id_Porcentaje, Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje, Id_Usuario, FechaMod, TipoMod)
|
INSERT INTO dbo.dist_PorcPago_H (Id_Porcentaje, Id_Publicacion, Id_Distribuidor, VigenciaD, VigenciaH, Porcentaje, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdPorcentaje, @IdPublicacion, @IdDistribuidor, @VigenciaD, @VigenciaH, @Porcentaje, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdPorcentajeHist, @IdPublicacionHist, @IdDistribuidorHist, @VigenciaDHist, @VigenciaHHist, @PorcentajeHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
foreach (var item in itemsToDelete)
|
foreach (var item in itemsToDelete)
|
||||||
{
|
{
|
||||||
await transaction.Connection!.ExecuteAsync(insertHistoricoSql, new
|
await transaction.Connection!.ExecuteAsync(insertHistoricoSql, new
|
||||||
{
|
{
|
||||||
item.IdPorcentaje, // Mapea a @Id_Porcentaje si usas nombres con _ en SQL
|
IdPorcentajeHist = item.IdPorcentaje,
|
||||||
item.IdPublicacion,
|
IdPublicacionHist = item.IdPublicacion,
|
||||||
item.IdDistribuidor,
|
IdDistribuidorHist = item.IdDistribuidor,
|
||||||
item.VigenciaD,
|
VigenciaDHist = item.VigenciaD,
|
||||||
item.VigenciaH,
|
VigenciaHHist = item.VigenciaH,
|
||||||
item.Porcentaje,
|
PorcentajeHist = item.Porcentaje,
|
||||||
Id_Usuario = idUsuarioAuditoria,
|
IdUsuarioHist = idUsuarioAuditoria,
|
||||||
FechaMod = DateTime.Now,
|
FechaModHist = DateTime.Now,
|
||||||
TipoMod = "Eliminado (Cascada)"
|
TipoModHist = "Eliminado (Cascada)"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const string deleteSql = "DELETE FROM dbo.dist_PorcPago WHERE Id_Publicacion = @IdPublicacionParam";
|
||||||
const string deleteSql = "DELETE FROM dbo.dist_PorcPago WHERE Id_Publicacion = @IdPublicacion";
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await transaction.Connection!.ExecuteAsync(deleteSql, new { IdPublicacion = idPublicacion }, transaction);
|
await transaction.Connection!.ExecuteAsync(deleteSql, new { IdPublicacionParam = idPublicacion }, transaction: transaction);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (System.Exception e)
|
catch (System.Exception ex)
|
||||||
{
|
{
|
||||||
_log.LogError(e, "Error al eliminar PorcPago por IdPublicacion: {idPublicacion}", idPublicacion);
|
_log.LogError(ex, "Error al eliminar PorcPago por IdPublicacion: {idPublicacion}", idPublicacion);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using GestionIntegral.Api.Models.Distribucion;
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -21,66 +22,127 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
|
|
||||||
public async Task<IEnumerable<Precio>> GetByPublicacionIdAsync(int idPublicacion)
|
public async Task<IEnumerable<Precio>> GetByPublicacionIdAsync(int idPublicacion)
|
||||||
{
|
{
|
||||||
const string sql = "SELECT * FROM dbo.dist_Precios WHERE Id_Publicacion = @IdPublicacion ORDER BY VigenciaD DESC";
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH,
|
||||||
|
Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
|
||||||
|
FROM dbo.dist_Precios
|
||||||
|
WHERE Id_Publicacion = @IdPublicacionParam
|
||||||
|
ORDER BY VigenciaD DESC";
|
||||||
|
try
|
||||||
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.QueryAsync<Precio>(sql, new { IdPublicacion = idPublicacion });
|
return await connection.QueryAsync<Precio>(sql, new { IdPublicacionParam = idPublicacion });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener precios por IdPublicacion: {IdPublicacion}", idPublicacion);
|
||||||
|
return Enumerable.Empty<Precio>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Precio?> GetByIdAsync(int idPrecio)
|
public async Task<Precio?> GetByIdAsync(int idPrecio)
|
||||||
{
|
{
|
||||||
const string sql = "SELECT * FROM dbo.dist_Precios WHERE Id_Precio = @IdPrecio";
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH,
|
||||||
|
Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
|
||||||
|
FROM dbo.dist_Precios
|
||||||
|
WHERE Id_Precio = @IdPrecioParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.QuerySingleOrDefaultAsync<Precio>(sql, new { IdPrecio = idPrecio });
|
return await connection.QuerySingleOrDefaultAsync<Precio>(sql, new { IdPrecioParam = idPrecio });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener precio por ID: {idPrecio}", idPrecio);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Precio?> GetActiveByPublicacionAndDateAsync(int idPublicacion, DateTime fecha, IDbTransaction? transaction = null)
|
public async Task<Precio?> GetActiveByPublicacionAndDateAsync(int idPublicacion, DateTime fecha, IDbTransaction? transaction = null)
|
||||||
{
|
{
|
||||||
// Obtiene el precio vigente para una publicación en una fecha específica
|
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT TOP 1 * FROM dbo.dist_Precios
|
SELECT TOP 1
|
||||||
WHERE Id_Publicacion = @IdPublicacion
|
Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH,
|
||||||
AND VigenciaD <= @Fecha
|
Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
|
||||||
AND (VigenciaH IS NULL OR VigenciaH >= @Fecha)
|
FROM dbo.dist_Precios
|
||||||
ORDER BY VigenciaD DESC;"; // Por si hay solapamientos incorrectos, tomar el más reciente
|
WHERE Id_Publicacion = @IdPublicacionParam AND VigenciaD <= @FechaParam
|
||||||
|
AND (VigenciaH IS NULL OR VigenciaH >= @FechaParam)
|
||||||
|
ORDER BY VigenciaD DESC;";
|
||||||
|
|
||||||
var cn = transaction?.Connection ?? _connectionFactory.CreateConnection();
|
var cn = transaction?.Connection ?? _connectionFactory.CreateConnection();
|
||||||
return await cn.QuerySingleOrDefaultAsync<Precio>(sql, new { IdPublicacion = idPublicacion, Fecha = fecha }, transaction);
|
bool ownConnection = transaction == null;
|
||||||
|
Precio? result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn)
|
||||||
|
{
|
||||||
|
await dbConn.OpenAsync();
|
||||||
|
}
|
||||||
|
result = await cn.QuerySingleOrDefaultAsync<Precio>(sql,
|
||||||
|
new { IdPublicacionParam = idPublicacion, FechaParam = fecha.Date },
|
||||||
|
transaction);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error en GetActiveByPublicacionAndDateAsync. IdPublicacion: {IdPublicacion}, Fecha: {Fecha}", idPublicacion, fecha);
|
||||||
|
throw; // Re-lanzar para que el servicio lo maneje si es necesario
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Open && cn is System.Data.Common.DbConnection dbConnClose)
|
||||||
|
{
|
||||||
|
await dbConnClose.CloseAsync();
|
||||||
|
}
|
||||||
|
if (ownConnection) (cn as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Precio?> GetPreviousActivePriceAsync(int idPublicacion, DateTime vigenciaDNuevo, IDbTransaction transaction)
|
public async Task<Precio?> GetPreviousActivePriceAsync(int idPublicacion, DateTime vigenciaDNuevo, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
// Busca el último precio activo antes de la vigenciaD del nuevo precio, que no tenga VigenciaH o cuya VigenciaH sea mayor o igual a la nueva VigenciaD - 1 día
|
|
||||||
// y que no sea el mismo periodo que se está por crear/actualizar (si tuviera un ID).
|
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT TOP 1 *
|
SELECT TOP 1
|
||||||
|
Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH,
|
||||||
|
Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
|
||||||
FROM dbo.dist_Precios
|
FROM dbo.dist_Precios
|
||||||
WHERE Id_Publicacion = @IdPublicacion
|
WHERE Id_Publicacion = @IdPublicacionParam
|
||||||
AND VigenciaD < @VigenciaDNuevo
|
AND VigenciaD < @VigenciaDNuevoParam
|
||||||
AND VigenciaH IS NULL
|
AND VigenciaH IS NULL
|
||||||
ORDER BY VigenciaD DESC;";
|
ORDER BY VigenciaD DESC;";
|
||||||
return await transaction.Connection!.QuerySingleOrDefaultAsync<Precio>(sql, new { IdPublicacion = idPublicacion, VigenciaDNuevo = vigenciaDNuevo }, transaction);
|
return await transaction.Connection!.QuerySingleOrDefaultAsync<Precio>(sql,
|
||||||
|
new { IdPublicacionParam = idPublicacion, VigenciaDNuevoParam = vigenciaDNuevo.Date },
|
||||||
|
transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<Precio?> CreateAsync(Precio nuevoPrecio, int idUsuario, IDbTransaction transaction)
|
public async Task<Precio?> CreateAsync(Precio nuevoPrecio, int idUsuario, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
const string sqlInsert = @"
|
const string sqlInsert = @"
|
||||||
INSERT INTO dbo.dist_Precios (Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo)
|
INSERT INTO dbo.dist_Precios (Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo)
|
||||||
OUTPUT INSERTED.*
|
OUTPUT INSERTED.Id_Precio AS IdPrecio, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.VigenciaD, INSERTED.VigenciaH,
|
||||||
|
INSERTED.Lunes, INSERTED.Martes, INSERTED.Miercoles, INSERTED.Jueves, INSERTED.Viernes, INSERTED.Sabado, INSERTED.Domingo
|
||||||
VALUES (@IdPublicacion, @VigenciaD, @VigenciaH, @Lunes, @Martes, @Miercoles, @Jueves, @Viernes, @Sabado, @Domingo);";
|
VALUES (@IdPublicacion, @VigenciaD, @VigenciaH, @Lunes, @Martes, @Miercoles, @Jueves, @Viernes, @Sabado, @Domingo);";
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<Precio>(sqlInsert, nuevoPrecio, transaction);
|
||||||
|
if (inserted == null || inserted.IdPrecio == 0) throw new DataException("Error al crear precio o al obtener el ID generado.");
|
||||||
|
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_Precios_H
|
INSERT INTO dbo.dist_Precios_H
|
||||||
(Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
|
(Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdPrecio, @IdPublicacion, @VigenciaD, @VigenciaH, @Lunes, @Martes, @Miercoles, @Jueves, @Viernes, @Sabado, @Domingo, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdPrecioHist, @IdPublicacionHist, @VigenciaDHist, @VigenciaHHist, @LunesHist, @MartesHist, @MiercolesHist, @JuevesHist, @ViernesHist, @SabadoHist, @DomingoHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
var inserted = await transaction.Connection!.QuerySingleAsync<Precio>(sqlInsert, nuevoPrecio, transaction);
|
|
||||||
if (inserted == null) throw new DataException("Error al crear precio.");
|
|
||||||
|
|
||||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
inserted.IdPrecio, inserted.IdPublicacion, inserted.VigenciaD, inserted.VigenciaH,
|
IdPrecioHist = inserted.IdPrecio,
|
||||||
inserted.Lunes, inserted.Martes, inserted.Miercoles, inserted.Jueves, inserted.Viernes, inserted.Sabado, inserted.Domingo,
|
IdPublicacionHist = inserted.IdPublicacion,
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Creado"
|
VigenciaDHist = inserted.VigenciaD,
|
||||||
|
VigenciaHHist = inserted.VigenciaH,
|
||||||
|
LunesHist = inserted.Lunes, MartesHist = inserted.Martes, MiercolesHist = inserted.Miercoles, JuevesHist = inserted.Jueves,
|
||||||
|
ViernesHist = inserted.Viernes, SabadoHist = inserted.Sabado, DomingoHist = inserted.Domingo,
|
||||||
|
IdUsuarioHist = idUsuario,
|
||||||
|
FechaModHist = DateTime.Now,
|
||||||
|
TipoModHist = "Creado"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
return inserted;
|
return inserted;
|
||||||
}
|
}
|
||||||
@@ -88,28 +150,32 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
public async Task<bool> UpdateAsync(Precio precioAActualizar, int idUsuario, IDbTransaction transaction)
|
public async Task<bool> UpdateAsync(Precio precioAActualizar, int idUsuario, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<Precio>(
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<Precio>(
|
||||||
"SELECT * FROM dbo.dist_Precios WHERE Id_Precio = @IdPrecio",
|
@"SELECT Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH,
|
||||||
new { precioAActualizar.IdPrecio }, transaction);
|
Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
|
||||||
|
FROM dbo.dist_Precios WHERE Id_Precio = @IdPrecioParam", // Usar parámetro con alias
|
||||||
|
new { IdPrecioParam = precioAActualizar.IdPrecio }, transaction);
|
||||||
if (actual == null) throw new KeyNotFoundException("Precio no encontrado.");
|
if (actual == null) throw new KeyNotFoundException("Precio no encontrado.");
|
||||||
|
|
||||||
const string sqlUpdate = @"
|
const string sqlUpdate = @"
|
||||||
UPDATE dbo.dist_Precios SET
|
UPDATE dbo.dist_Precios SET
|
||||||
VigenciaH = @VigenciaH, Lunes = @Lunes, Martes = @Martes, Miercoles = @Miercoles,
|
VigenciaH = @VigenciaH, Lunes = @Lunes, Martes = @Martes, Miercoles = @Miercoles,
|
||||||
Jueves = @Jueves, Viernes = @Viernes, Sabado = @Sabado, Domingo = @Domingo
|
Jueves = @Jueves, Viernes = @Viernes, Sabado = @Sabado, Domingo = @Domingo
|
||||||
-- No se permite cambiar IdPublicacion ni VigenciaD de un registro de precio existente
|
|
||||||
WHERE Id_Precio = @IdPrecio;";
|
WHERE Id_Precio = @IdPrecio;";
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_Precios_H
|
INSERT INTO dbo.dist_Precios_H
|
||||||
(Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
|
(Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdPrecio, @IdPublicacion, @VigenciaD, @VigenciaH, @Lunes, @Martes, @Miercoles, @Jueves, @Viernes, @Sabado, @Domingo, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdPrecioHist, @IdPublicacionHist, @VigenciaDHist, @VigenciaHHist, @LunesHist, @MartesHist, @MiercolesHist, @JuevesHist, @ViernesHist, @SabadoHist, @DomingoHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
actual.IdPrecio, actual.IdPublicacion, actual.VigenciaD, // VigenciaD actual para el historial
|
IdPrecioHist = actual.IdPrecio,
|
||||||
VigenciaH = actual.VigenciaH, // VigenciaH actual para el historial
|
IdPublicacionHist = actual.IdPublicacion,
|
||||||
Lunes = actual.Lunes, Martes = actual.Martes, Miercoles = actual.Miercoles, Jueves = actual.Jueves,
|
VigenciaDHist = actual.VigenciaD,
|
||||||
Viernes = actual.Viernes, Sabado = actual.Sabado, Domingo = actual.Domingo, // Precios actuales para el historial
|
VigenciaHHist = actual.VigenciaH,
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Actualizado" // O "Cerrado" si solo se actualiza VigenciaH
|
LunesHist = actual.Lunes, MartesHist = actual.Martes, MiercolesHist = actual.Miercoles, JuevesHist = actual.Jueves,
|
||||||
|
ViernesHist = actual.Viernes, SabadoHist = actual.Sabado, DomingoHist = actual.Domingo,
|
||||||
|
IdUsuarioHist = idUsuario,
|
||||||
|
FechaModHist = DateTime.Now,
|
||||||
|
TipoModHist = "Actualizado"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
|
|
||||||
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, precioAActualizar, transaction);
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, precioAActualizar, transaction);
|
||||||
@@ -119,53 +185,71 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
public async Task<bool> DeleteAsync(int idPrecio, int idUsuario, IDbTransaction transaction)
|
public async Task<bool> DeleteAsync(int idPrecio, int idUsuario, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<Precio>(
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<Precio>(
|
||||||
"SELECT * FROM dbo.dist_Precios WHERE Id_Precio = @IdPrecio", new { IdPrecio = idPrecio }, transaction);
|
@"SELECT Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH,
|
||||||
|
Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
|
||||||
|
FROM dbo.dist_Precios WHERE Id_Precio = @IdPrecioParam", new { IdPrecioParam = idPrecio }, transaction);
|
||||||
if (actual == null) throw new KeyNotFoundException("Precio no encontrado para eliminar.");
|
if (actual == null) throw new KeyNotFoundException("Precio no encontrado para eliminar.");
|
||||||
|
|
||||||
const string sqlDelete = "DELETE FROM dbo.dist_Precios WHERE Id_Precio = @IdPrecio";
|
const string sqlDelete = "DELETE FROM dbo.dist_Precios WHERE Id_Precio = @IdPrecioParam"; // Usar parámetro con alias
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_Precios_H
|
INSERT INTO dbo.dist_Precios_H
|
||||||
(Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
|
(Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdPrecio, @IdPublicacion, @VigenciaD, @VigenciaH, @Lunes, @Martes, @Miercoles, @Jueves, @Viernes, @Sabado, @Domingo, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdPrecioHist, @IdPublicacionHist, @VigenciaDHist, @VigenciaHHist, @LunesHist, @MartesHist, @MiercolesHist, @JuevesHist, @ViernesHist, @SabadoHist, @DomingoHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
actual.IdPrecio, actual.IdPublicacion, actual.VigenciaD, actual.VigenciaH,
|
IdPrecioHist = actual.IdPrecio,
|
||||||
actual.Lunes, actual.Martes, actual.Miercoles, actual.Jueves, actual.Viernes, actual.Sabado, actual.Domingo,
|
IdPublicacionHist = actual.IdPublicacion,
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Eliminado"
|
VigenciaDHist = actual.VigenciaD,
|
||||||
|
VigenciaHHist = actual.VigenciaH,
|
||||||
|
LunesHist = actual.Lunes, MartesHist = actual.Martes, MiercolesHist = actual.Miercoles, JuevesHist = actual.Jueves,
|
||||||
|
ViernesHist = actual.Viernes, SabadoHist = actual.Sabado, DomingoHist = actual.Domingo,
|
||||||
|
IdUsuarioHist = idUsuario,
|
||||||
|
FechaModHist = DateTime.Now,
|
||||||
|
TipoModHist = "Eliminado"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
|
|
||||||
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPrecio = idPrecio }, transaction);
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPrecioParam = idPrecio }, transaction); // Usar parámetro con alias
|
||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction) // MODIFICADO: Recibe idUsuarioAuditoria
|
public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
const string selectPrecios = "SELECT * FROM dbo.dist_Precios WHERE Id_Publicacion = @IdPublicacion";
|
const string selectSql = @"
|
||||||
var preciosAEliminar = await transaction.Connection!.QueryAsync<Precio>(selectPrecios, new { IdPublicacion = idPublicacion }, transaction);
|
SELECT
|
||||||
|
Id_Precio AS IdPrecio, Id_Publicacion AS IdPublicacion, VigenciaD, VigenciaH,
|
||||||
|
Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo
|
||||||
|
FROM dbo.dist_Precios WHERE Id_Publicacion = @IdPublicacionParam";
|
||||||
|
var itemsToDelete = await transaction.Connection!.QueryAsync<Precio>(selectSql, new { IdPublicacionParam = idPublicacion }, transaction);
|
||||||
|
|
||||||
const string sqlInsertHistorico = @"
|
if (itemsToDelete.Any())
|
||||||
|
{
|
||||||
|
const string insertHistoricoSql = @"
|
||||||
INSERT INTO dbo.dist_Precios_H
|
INSERT INTO dbo.dist_Precios_H
|
||||||
(Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
|
(Id_Precio, Id_Publicacion, VigenciaD, VigenciaH, Lunes, Martes, Miercoles, Jueves, Viernes, Sabado, Domingo, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdPrecio, @IdPublicacion, @VigenciaD, @VigenciaH, @Lunes, @Martes, @Miercoles, @Jueves, @Viernes, @Sabado, @Domingo, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdPrecioHist, @IdPublicacionHist, @VigenciaDHist, @VigenciaHHist, @LunesHist, @MartesHist, @MiercolesHist, @JuevesHist, @ViernesHist, @SabadoHist, @DomingoHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
foreach (var precio in preciosAEliminar)
|
foreach (var item in itemsToDelete)
|
||||||
{
|
{
|
||||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
await transaction.Connection!.ExecuteAsync(insertHistoricoSql, new
|
||||||
{
|
{
|
||||||
precio.IdPrecio, precio.IdPublicacion, precio.VigenciaD, precio.VigenciaH,
|
IdPrecioHist = item.IdPrecio,
|
||||||
precio.Lunes, precio.Martes, precio.Miercoles, precio.Jueves, precio.Viernes, precio.Sabado, precio.Domingo,
|
IdPublicacionHist = item.IdPublicacion,
|
||||||
Id_Usuario = idUsuarioAuditoria, // MODIFICADO: Usar el idUsuarioAuditoria pasado
|
VigenciaDHist = item.VigenciaD,
|
||||||
FechaMod = DateTime.Now, TipoMod = "Eliminado (Cascada)"
|
VigenciaHHist = item.VigenciaH,
|
||||||
|
LunesHist = item.Lunes, MartesHist = item.Martes, MiercolesHist = item.Miercoles, JuevesHist = item.Jueves,
|
||||||
|
ViernesHist = item.Viernes, SabadoHist = item.Sabado, DomingoHist = item.Domingo,
|
||||||
|
IdUsuarioHist = idUsuarioAuditoria,
|
||||||
|
FechaModHist = DateTime.Now,
|
||||||
|
TipoModHist = "Eliminado (Cascada)"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const string sql = "DELETE FROM dbo.dist_Precios WHERE Id_Publicacion = @IdPublicacion";
|
const string deleteSql = "DELETE FROM dbo.dist_Precios WHERE Id_Publicacion = @IdPublicacionParam";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rowsAffected = await transaction.Connection!.ExecuteAsync(sql, new { IdPublicacion = idPublicacion }, transaction: transaction);
|
await transaction.Connection!.ExecuteAsync(deleteSql, new { IdPublicacionParam = idPublicacion }, transaction: transaction);
|
||||||
// No necesitamos devolver rowsAffected >= 0 si la lógica del servicio ya valida si debe haber registros
|
return true;
|
||||||
return true; // Indica que la operación de borrado (incluyendo 0 filas) se intentó
|
|
||||||
}
|
}
|
||||||
catch (System.Exception ex)
|
catch (System.Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,53 +1,237 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using System.Data;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using GestionIntegral.Api.Models.Distribucion;
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
{
|
{
|
||||||
public class PubliSeccionRepository : IPubliSeccionRepository
|
public class PubliSeccionRepository : IPubliSeccionRepository
|
||||||
{
|
{
|
||||||
private readonly DbConnectionFactory _cf; private readonly ILogger<PubliSeccionRepository> _log;
|
private readonly DbConnectionFactory _cf;
|
||||||
public PubliSeccionRepository(DbConnectionFactory cf, ILogger<PubliSeccionRepository> log) { _cf = cf; _log = log; }
|
private readonly ILogger<PubliSeccionRepository> _log;
|
||||||
|
|
||||||
|
public PubliSeccionRepository(DbConnectionFactory connectionFactory, ILogger<PubliSeccionRepository> logger)
|
||||||
|
{
|
||||||
|
_cf = connectionFactory;
|
||||||
|
_log = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<PubliSeccion>> GetByPublicacionIdAsync(int idPublicacion, bool? soloActivas = null)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(@"
|
||||||
|
SELECT
|
||||||
|
Id_Seccion AS IdSeccion, Id_Publicacion AS IdPublicacion, Nombre, Estado
|
||||||
|
FROM dbo.dist_dtPubliSecciones
|
||||||
|
WHERE Id_Publicacion = @IdPublicacionParam");
|
||||||
|
|
||||||
|
if (soloActivas.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(soloActivas.Value ? " AND Estado = 1" : " AND Estado = 0");
|
||||||
|
}
|
||||||
|
sqlBuilder.Append(" ORDER BY Nombre;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<PubliSeccion>(sqlBuilder.ToString(), new { IdPublicacionParam = idPublicacion });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Secciones por Publicacion ID: {IdPublicacion}", idPublicacion);
|
||||||
|
return Enumerable.Empty<PubliSeccion>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PubliSeccion?> GetByIdAsync(int idSeccion)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Seccion AS IdSeccion, Id_Publicacion AS IdPublicacion, Nombre, Estado
|
||||||
|
FROM dbo.dist_dtPubliSecciones
|
||||||
|
WHERE Id_Seccion = @IdSeccionParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<PubliSeccion>(sql, new { IdSeccionParam = idSeccion });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Sección por ID: {IdSeccion}", idSeccion);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExistsByNameInPublicacionAsync(string nombre, int idPublicacion, int? excludeIdSeccion = null)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtPubliSecciones WHERE Nombre = @NombreParam AND Id_Publicacion = @IdPublicacionParam");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("NombreParam", nombre);
|
||||||
|
parameters.Add("IdPublicacionParam", idPublicacion);
|
||||||
|
if (excludeIdSeccion.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Id_Seccion != @ExcludeIdSeccionParam");
|
||||||
|
parameters.Add("ExcludeIdSeccionParam", excludeIdSeccion.Value);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en ExistsByNameInPublicacionAsync. Nombre: {Nombre}, PubID: {IdPublicacion}", nombre, idPublicacion);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsInUseAsync(int idSeccion)
|
||||||
|
{
|
||||||
|
// Verificar en bob_RegPublicaciones y bob_StockBobinas (si Id_Seccion se usa allí)
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
string[] checkQueries = {
|
||||||
|
"SELECT TOP 1 1 FROM dbo.bob_RegPublicaciones WHERE Id_Seccion = @IdSeccionParam",
|
||||||
|
"SELECT TOP 1 1 FROM dbo.bob_StockBobinas WHERE Id_Seccion = @IdSeccionParam"
|
||||||
|
};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var query in checkQueries)
|
||||||
|
{
|
||||||
|
if (await connection.ExecuteScalarAsync<int?>(query, new { IdSeccionParam = idSeccion }) == 1) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en IsInUseAsync para PubliSeccion ID: {idSeccion}", idSeccion);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PubliSeccion?> CreateAsync(PubliSeccion nuevaSeccion, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.dist_dtPubliSecciones (Id_Publicacion, Nombre, Estado)
|
||||||
|
OUTPUT INSERTED.Id_Seccion AS IdSeccion, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Nombre, INSERTED.Estado
|
||||||
|
VALUES (@IdPublicacion, @Nombre, @Estado);";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_dtPubliSecciones_H (Id_Seccion, Id_Publicacion, Nombre, Estado, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdSeccionParam, @IdPublicacionParam, @NombreParam, @EstadoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<PubliSeccion>(sqlInsert, nuevaSeccion, transaction);
|
||||||
|
if (inserted == null || inserted.IdSeccion == 0) throw new DataException("Error al crear sección o ID no generado.");
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdSeccionParam = inserted.IdSeccion,
|
||||||
|
IdPublicacionParam = inserted.IdPublicacion,
|
||||||
|
NombreParam = inserted.Nombre,
|
||||||
|
EstadoParam = inserted.Estado,
|
||||||
|
IdUsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Creada"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(PubliSeccion seccionAActualizar, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PubliSeccion>(
|
||||||
|
@"SELECT Id_Seccion AS IdSeccion, Id_Publicacion AS IdPublicacion, Nombre, Estado
|
||||||
|
FROM dbo.dist_dtPubliSecciones WHERE Id_Seccion = @IdSeccionParam",
|
||||||
|
new { IdSeccionParam = seccionAActualizar.IdSeccion }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Sección no encontrada.");
|
||||||
|
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.dist_dtPubliSecciones SET
|
||||||
|
Nombre = @Nombre, Estado = @Estado
|
||||||
|
WHERE Id_Seccion = @IdSeccion;";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_dtPubliSecciones_H (Id_Seccion, Id_Publicacion, Nombre, Estado, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdSeccionParam, @IdPublicacionParam, @NombreParam, @EstadoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdSeccionParam = actual.IdSeccion,
|
||||||
|
IdPublicacionParam = actual.IdPublicacion, // Tomar de 'actual' ya que no se modifica
|
||||||
|
NombreParam = actual.Nombre, // Valor ANTERIOR
|
||||||
|
EstadoParam = actual.Estado, // Valor ANTERIOR
|
||||||
|
IdUsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Actualizada"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, seccionAActualizar, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idSeccion, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PubliSeccion>(
|
||||||
|
@"SELECT Id_Seccion AS IdSeccion, Id_Publicacion AS IdPublicacion, Nombre, Estado
|
||||||
|
FROM dbo.dist_dtPubliSecciones WHERE Id_Seccion = @IdSeccionParam",
|
||||||
|
new { IdSeccionParam = idSeccion }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Sección no encontrada para eliminar.");
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.dist_dtPubliSecciones WHERE Id_Seccion = @IdSeccionParam";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_dtPubliSecciones_H (Id_Seccion, Id_Publicacion, Nombre, Estado, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdSeccionParam, @IdPublicacionParam, @NombreParam, @EstadoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdSeccionParam = actual.IdSeccion,
|
||||||
|
IdPublicacionParam = actual.IdPublicacion,
|
||||||
|
NombreParam = actual.Nombre,
|
||||||
|
EstadoParam = actual.Estado,
|
||||||
|
IdUsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Eliminada"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdSeccionParam = idSeccion }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
|
public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
const string selectSql = "SELECT * FROM dbo.dist_dtPubliSecciones WHERE Id_Publicacion = @IdPublicacion";
|
const string selectSql = @"
|
||||||
var itemsToDelete = await transaction.Connection!.QueryAsync<PubliSeccion>(selectSql, new { IdPublicacion = idPublicacion }, transaction);
|
SELECT Id_Seccion AS IdSeccion, Id_Publicacion AS IdPublicacion, Nombre, Estado
|
||||||
|
FROM dbo.dist_dtPubliSecciones WHERE Id_Publicacion = @IdPublicacionParam";
|
||||||
|
var itemsToDelete = await transaction.Connection!.QueryAsync<PubliSeccion>(selectSql, new { IdPublicacionParam = idPublicacion }, transaction);
|
||||||
|
|
||||||
if (itemsToDelete.Any())
|
if (itemsToDelete.Any())
|
||||||
{
|
{
|
||||||
const string insertHistoricoSql = @"
|
const string insertHistoricoSql = @"
|
||||||
INSERT INTO dbo.dist_dtPubliSecciones_H (Id_Seccion, Id_Publicacion, Nombre, Estado, Id_Usuario, FechaMod, TipoMod)
|
INSERT INTO dbo.dist_dtPubliSecciones_H (Id_Seccion, Id_Publicacion, Nombre, Estado, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@Id_Seccion, @Id_Publicacion, @Nombre, @Estado, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdSeccionParam, @IdPublicacionParam, @NombreParam, @EstadoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
foreach (var item in itemsToDelete)
|
foreach (var item in itemsToDelete)
|
||||||
{
|
{
|
||||||
await transaction.Connection!.ExecuteAsync(insertHistoricoSql, new
|
await transaction.Connection!.ExecuteAsync(insertHistoricoSql, new
|
||||||
{
|
{
|
||||||
Id_Seccion = item.IdSeccion, // Mapeo de propiedad a parámetro SQL
|
IdSeccionParam = item.IdSeccion,
|
||||||
Id_Publicacion = item.IdPublicacion,
|
IdPublicacionParam = item.IdPublicacion,
|
||||||
item.Nombre,
|
NombreParam = item.Nombre,
|
||||||
item.Estado,
|
EstadoParam = item.Estado,
|
||||||
Id_Usuario = idUsuarioAuditoria,
|
IdUsuarioParam = idUsuarioAuditoria,
|
||||||
FechaMod = DateTime.Now,
|
FechaModParam = DateTime.Now,
|
||||||
TipoMod = "Eliminado (Cascada)"
|
TipoModParam = "Eliminado (Cascada)"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const string deleteSql = "DELETE FROM dbo.dist_dtPubliSecciones WHERE Id_Publicacion = @IdPublicacionParam";
|
||||||
const string deleteSql = "DELETE FROM dbo.dist_dtPubliSecciones WHERE Id_Publicacion = @IdPublicacion";
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await transaction.Connection!.ExecuteAsync(deleteSql, new { IdPublicacion = idPublicacion }, transaction);
|
await transaction.Connection!.ExecuteAsync(deleteSql, new { IdPublicacionParam = idPublicacion }, transaction: transaction);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (System.Exception e)
|
catch (System.Exception ex)
|
||||||
{
|
{
|
||||||
_log.LogError(e, "Error al eliminar PubliSecciones por IdPublicacion: {idPublicacion}", idPublicacion);
|
_log.LogError(ex, "Error al eliminar PubliSecciones por IdPublicacion: {idPublicacion}", idPublicacion);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
// src/Data/Repositories/PublicacionRepository.cs
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using GestionIntegral.Api.Models.Distribucion;
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System; // Añadido para Exception
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -23,7 +25,10 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
public async Task<IEnumerable<(Publicacion Publicacion, string NombreEmpresa)>> GetAllAsync(string? nombreFilter, int? idEmpresaFilter, bool? soloHabilitadas)
|
public async Task<IEnumerable<(Publicacion Publicacion, string NombreEmpresa)>> GetAllAsync(string? nombreFilter, int? idEmpresaFilter, bool? soloHabilitadas)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder(@"
|
var sqlBuilder = new StringBuilder(@"
|
||||||
SELECT p.*, e.Nombre AS NombreEmpresa
|
SELECT
|
||||||
|
p.Id_Publicacion AS IdPublicacion, p.Nombre, p.Observacion, p.Id_Empresa AS IdEmpresa,
|
||||||
|
p.CtrlDevoluciones, p.Habilitada,
|
||||||
|
e.Nombre AS NombreEmpresa
|
||||||
FROM dbo.dist_dtPublicaciones p
|
FROM dbo.dist_dtPublicaciones p
|
||||||
INNER JOIN dbo.dist_dtEmpresas e ON p.Id_Empresa = e.Id_Empresa
|
INNER JOIN dbo.dist_dtEmpresas e ON p.Id_Empresa = e.Id_Empresa
|
||||||
WHERE 1=1");
|
WHERE 1=1");
|
||||||
@@ -31,33 +36,34 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
|
|
||||||
if (soloHabilitadas.HasValue)
|
if (soloHabilitadas.HasValue)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(soloHabilitadas.Value ? " AND p.Habilitada = 1" : " AND (p.Habilitada = 0 OR p.Habilitada IS NULL)");
|
sqlBuilder.Append(soloHabilitadas.Value ? " AND (p.Habilitada = 1 OR p.Habilitada IS NULL)" : " AND p.Habilitada = 0");
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(nombreFilter))
|
if (!string.IsNullOrWhiteSpace(nombreFilter))
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND p.Nombre LIKE @Nombre");
|
sqlBuilder.Append(" AND p.Nombre LIKE @NombreParam");
|
||||||
parameters.Add("Nombre", $"%{nombreFilter}%");
|
parameters.Add("NombreParam", $"%{nombreFilter}%");
|
||||||
}
|
}
|
||||||
if (idEmpresaFilter.HasValue && idEmpresaFilter > 0)
|
if (idEmpresaFilter.HasValue && idEmpresaFilter > 0)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND p.Id_Empresa = @IdEmpresa");
|
sqlBuilder.Append(" AND p.Id_Empresa = @IdEmpresaParam");
|
||||||
parameters.Add("IdEmpresa", idEmpresaFilter.Value);
|
parameters.Add("IdEmpresaParam", idEmpresaFilter.Value);
|
||||||
}
|
}
|
||||||
sqlBuilder.Append(" ORDER BY p.Nombre;");
|
sqlBuilder.Append(" ORDER BY p.Nombre;");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.QueryAsync<Publicacion, string, (Publicacion, string)>(
|
var result = await connection.QueryAsync<Publicacion, string, (Publicacion, string)>(
|
||||||
sqlBuilder.ToString(),
|
sqlBuilder.ToString(),
|
||||||
(pub, empNombre) => (pub, empNombre),
|
(publicacion, nombreEmpresa) => (publicacion, nombreEmpresa),
|
||||||
parameters,
|
parameters,
|
||||||
splitOn: "NombreEmpresa"
|
splitOn: "NombreEmpresa"
|
||||||
);
|
);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error al obtener todas las Publicaciones.");
|
_logger.LogError(ex, "Error al obtener todas las Publicaciones. Filtros: Nombre='{NombreFilter}', IdEmpresa='{IdEmpresaFilter}', SoloHabilitadas='{SoloHabilitadas}'", nombreFilter, idEmpresaFilter, soloHabilitadas);
|
||||||
return Enumerable.Empty<(Publicacion, string)>();
|
return Enumerable.Empty<(Publicacion, string)>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,17 +71,20 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
public async Task<(Publicacion? Publicacion, string? NombreEmpresa)> GetByIdAsync(int id)
|
public async Task<(Publicacion? Publicacion, string? NombreEmpresa)> GetByIdAsync(int id)
|
||||||
{
|
{
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT p.*, e.Nombre AS NombreEmpresa
|
SELECT
|
||||||
|
p.Id_Publicacion AS IdPublicacion, p.Nombre, p.Observacion, p.Id_Empresa AS IdEmpresa,
|
||||||
|
p.CtrlDevoluciones, p.Habilitada,
|
||||||
|
e.Nombre AS NombreEmpresa
|
||||||
FROM dbo.dist_dtPublicaciones p
|
FROM dbo.dist_dtPublicaciones p
|
||||||
INNER JOIN dbo.dist_dtEmpresas e ON p.Id_Empresa = e.Id_Empresa
|
INNER JOIN dbo.dist_dtEmpresas e ON p.Id_Empresa = e.Id_Empresa
|
||||||
WHERE p.Id_Publicacion = @Id";
|
WHERE p.Id_Publicacion = @IdParam";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
var result = await connection.QueryAsync<Publicacion, string, (Publicacion, string)>(
|
var result = await connection.QueryAsync<Publicacion, string, (Publicacion?, string?)>(
|
||||||
sql,
|
sql,
|
||||||
(pub, empNombre) => (pub, empNombre),
|
(publicacion, nombreEmpresa) => (publicacion, nombreEmpresa),
|
||||||
new { Id = id },
|
new { IdParam = id },
|
||||||
splitOn: "NombreEmpresa"
|
splitOn: "NombreEmpresa"
|
||||||
);
|
);
|
||||||
return result.SingleOrDefault();
|
return result.SingleOrDefault();
|
||||||
@@ -86,77 +95,107 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
return (null, null);
|
return (null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Publicacion?> GetByIdSimpleAsync(int id)
|
public async Task<Publicacion?> GetByIdSimpleAsync(int id)
|
||||||
{
|
{
|
||||||
const string sql = "SELECT * FROM dbo.dist_dtPublicaciones WHERE Id_Publicacion = @Id";
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Publicacion AS IdPublicacion, Nombre, Observacion, Id_Empresa AS IdEmpresa,
|
||||||
|
CtrlDevoluciones, Habilitada
|
||||||
|
FROM dbo.dist_dtPublicaciones
|
||||||
|
WHERE Id_Publicacion = @IdParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.QuerySingleOrDefaultAsync<Publicacion>(sql, new { Id = id });
|
return await connection.QuerySingleOrDefaultAsync<Publicacion>(sql, new { IdParam = id });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener Publicación (simple) por ID: {IdPublicacion}", id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> ExistsByNameAndEmpresaAsync(string nombre, int idEmpresa, int? excludeIdPublicacion = null)
|
public async Task<bool> ExistsByNameAndEmpresaAsync(string nombre, int idEmpresa, int? excludeIdPublicacion = null)
|
||||||
{
|
{
|
||||||
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtPublicaciones WHERE Nombre = @Nombre AND Id_Empresa = @IdEmpresa");
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtPublicaciones WHERE Nombre = @NombreParam AND Id_Empresa = @IdEmpresaParam");
|
||||||
var parameters = new DynamicParameters();
|
var parameters = new DynamicParameters();
|
||||||
parameters.Add("Nombre", nombre);
|
parameters.Add("NombreParam", nombre);
|
||||||
parameters.Add("IdEmpresa", idEmpresa);
|
parameters.Add("IdEmpresaParam", idEmpresa);
|
||||||
if (excludeIdPublicacion.HasValue)
|
if (excludeIdPublicacion.HasValue)
|
||||||
{
|
{
|
||||||
sqlBuilder.Append(" AND Id_Publicacion != @ExcludeId");
|
sqlBuilder.Append(" AND Id_Publicacion != @ExcludeIdParam");
|
||||||
parameters.Add("ExcludeId", excludeIdPublicacion.Value);
|
parameters.Add("ExcludeIdParam", excludeIdPublicacion.Value);
|
||||||
}
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error en ExistsByNameAndEmpresaAsync. Nombre: {Nombre}, IdEmpresa: {IdEmpresa}", nombre, idEmpresa);
|
||||||
|
return true; // Asumir que existe en caso de error para prevenir duplicados
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> IsInUseAsync(int id)
|
public async Task<bool> IsInUseAsync(int id)
|
||||||
{
|
{
|
||||||
// Verificar en tablas relacionadas: dist_EntradasSalidas, dist_EntradasSalidasCanillas,
|
|
||||||
// dist_Precios, dist_RecargoZona, dist_PorcPago, dist_PorcMonPagoCanilla, dist_dtPubliSecciones,
|
|
||||||
// bob_RegPublicaciones, bob_StockBobinas (donde Id_Publicacion se usa)
|
|
||||||
using var connection = _connectionFactory.CreateConnection();
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
string[] checkQueries = {
|
string[] checkQueries = {
|
||||||
"SELECT TOP 1 1 FROM dbo.dist_EntradasSalidas WHERE Id_Publicacion = @Id",
|
"SELECT TOP 1 1 FROM dbo.dist_EntradasSalidas WHERE Id_Publicacion = @IdParam",
|
||||||
"SELECT TOP 1 1 FROM dbo.dist_EntradasSalidasCanillas WHERE Id_Publicacion = @Id",
|
"SELECT TOP 1 1 FROM dbo.dist_EntradasSalidasCanillas WHERE Id_Publicacion = @IdParam",
|
||||||
"SELECT TOP 1 1 FROM dbo.dist_Precios WHERE Id_Publicacion = @Id",
|
"SELECT TOP 1 1 FROM dbo.dist_Precios WHERE Id_Publicacion = @IdParam",
|
||||||
"SELECT TOP 1 1 FROM dbo.dist_RecargoZona WHERE Id_Publicacion = @Id",
|
"SELECT TOP 1 1 FROM dbo.dist_RecargoZona WHERE Id_Publicacion = @IdParam",
|
||||||
"SELECT TOP 1 1 FROM dbo.dist_PorcPago WHERE Id_Publicacion = @Id",
|
"SELECT TOP 1 1 FROM dbo.dist_PorcPago WHERE Id_Publicacion = @IdParam",
|
||||||
"SELECT TOP 1 1 FROM dbo.dist_PorcMonPagoCanilla WHERE Id_Publicacion = @Id",
|
"SELECT TOP 1 1 FROM dbo.dist_PorcMonPagoCanilla WHERE Id_Publicacion = @IdParam",
|
||||||
"SELECT TOP 1 1 FROM dbo.dist_dtPubliSecciones WHERE Id_Publicacion = @Id",
|
"SELECT TOP 1 1 FROM dbo.dist_dtPubliSecciones WHERE Id_Publicacion = @IdParam",
|
||||||
"SELECT TOP 1 1 FROM dbo.bob_RegPublicaciones WHERE Id_Publicacion = @Id",
|
"SELECT TOP 1 1 FROM dbo.bob_RegPublicaciones WHERE Id_Publicacion = @IdParam",
|
||||||
"SELECT TOP 1 1 FROM dbo.bob_StockBobinas WHERE Id_Publicacion = @Id"
|
"SELECT TOP 1 1 FROM dbo.bob_StockBobinas WHERE Id_Publicacion = @IdParam"
|
||||||
};
|
};
|
||||||
|
try
|
||||||
|
{
|
||||||
foreach (var query in checkQueries)
|
foreach (var query in checkQueries)
|
||||||
{
|
{
|
||||||
if (await connection.ExecuteScalarAsync<int?>(query, new { Id = id }) == 1) return true;
|
if (await connection.ExecuteScalarAsync<int?>(query, new { IdParam = id }) == 1) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error en IsInUseAsync para Publicacion ID: {IdPublicacion}", id);
|
||||||
|
return true; // Asumir en uso si hay error de BD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Publicacion?> CreateAsync(Publicacion nuevaPublicacion, int idUsuario, IDbTransaction transaction)
|
public async Task<Publicacion?> CreateAsync(Publicacion nuevaPublicacion, int idUsuario, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
// Habilitada por defecto es true si es null en el modelo
|
|
||||||
nuevaPublicacion.Habilitada ??= true;
|
nuevaPublicacion.Habilitada ??= true;
|
||||||
|
|
||||||
const string sqlInsert = @"
|
const string sqlInsert = @"
|
||||||
INSERT INTO dbo.dist_dtPublicaciones (Nombre, Observacion, Id_Empresa, CtrlDevoluciones, Habilitada)
|
INSERT INTO dbo.dist_dtPublicaciones (Nombre, Observacion, Id_Empresa, CtrlDevoluciones, Habilitada)
|
||||||
OUTPUT INSERTED.*
|
OUTPUT INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Nombre, INSERTED.Observacion, INSERTED.Id_Empresa AS IdEmpresa, INSERTED.CtrlDevoluciones, INSERTED.Habilitada
|
||||||
VALUES (@Nombre, @Observacion, @IdEmpresa, @CtrlDevoluciones, @Habilitada);";
|
VALUES (@Nombre, @Observacion, @IdEmpresa, @CtrlDevoluciones, @Habilitada);";
|
||||||
const string sqlInsertHistorico = @"
|
|
||||||
INSERT INTO dbo.dist_dtPublicaciones_H
|
|
||||||
(Id_Publicacion, Nombre, Observacion, Id_Empresa, Habilitada, Id_Usuario, FechaMod, TipoMod)
|
|
||||||
VALUES (@IdPublicacion, @Nombre, @Observacion, @IdEmpresa, @Habilitada, @Id_Usuario, @FechaMod, @TipoMod);";
|
|
||||||
|
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var inserted = await connection.QuerySingleAsync<Publicacion>(sqlInsert, nuevaPublicacion, transaction);
|
var inserted = await connection.QuerySingleAsync<Publicacion>(sqlInsert, nuevaPublicacion, transaction);
|
||||||
if (inserted == null) throw new DataException("Error al crear la publicación.");
|
if (inserted == null || inserted.IdPublicacion == 0) throw new DataException("Error al crear la publicación o al obtener el ID generado.");
|
||||||
|
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_dtPublicaciones_H
|
||||||
|
(Id_Publicacion, Nombre, Observacion, Id_Empresa, Habilitada, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdPublicacionParam, @NombreParam, @ObservacionParam, @IdEmpresaParam, @HabilitadaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
inserted.IdPublicacion, inserted.Nombre, inserted.Observacion, inserted.IdEmpresa,
|
IdPublicacionParam = inserted.IdPublicacion,
|
||||||
Habilitada = inserted.Habilitada ?? true, // Asegurar que no sea null para el historial
|
NombreParam = inserted.Nombre,
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Creada"
|
ObservacionParam = inserted.Observacion,
|
||||||
|
IdEmpresaParam = inserted.IdEmpresa,
|
||||||
|
HabilitadaParam = inserted.Habilitada ?? true,
|
||||||
|
IdUsuarioParam = idUsuario, // Renombrado para claridad con el parámetro de la función
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Creada"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
return inserted;
|
return inserted;
|
||||||
}
|
}
|
||||||
@@ -165,27 +204,33 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var actual = await connection.QuerySingleOrDefaultAsync<Publicacion>(
|
var actual = await connection.QuerySingleOrDefaultAsync<Publicacion>(
|
||||||
"SELECT * FROM dbo.dist_dtPublicaciones WHERE Id_Publicacion = @IdPublicacion",
|
@"SELECT Id_Publicacion AS IdPublicacion, Nombre, Observacion, Id_Empresa AS IdEmpresa, CtrlDevoluciones, Habilitada
|
||||||
new { publicacionAActualizar.IdPublicacion }, transaction);
|
FROM dbo.dist_dtPublicaciones WHERE Id_Publicacion = @IdPublicacionParam", // Usar alias
|
||||||
|
new { IdPublicacionParam = publicacionAActualizar.IdPublicacion }, transaction);
|
||||||
if (actual == null) throw new KeyNotFoundException("Publicación no encontrada.");
|
if (actual == null) throw new KeyNotFoundException("Publicación no encontrada.");
|
||||||
|
|
||||||
publicacionAActualizar.Habilitada ??= true; // Asegurar que no sea null
|
publicacionAActualizar.Habilitada ??= true;
|
||||||
|
|
||||||
const string sqlUpdate = @"
|
const string sqlUpdate = @"
|
||||||
UPDATE dbo.dist_dtPublicaciones SET
|
UPDATE dbo.dist_dtPublicaciones SET
|
||||||
Nombre = @Nombre, Observacion = @Observacion, Id_Empresa = @IdEmpresa,
|
Nombre = @Nombre, Observacion = @Observacion, Id_Empresa = @IdEmpresa,
|
||||||
CtrlDevoluciones = @CtrlDevoluciones, Habilitada = @Habilitada
|
CtrlDevoluciones = @CtrlDevoluciones, Habilitada = @Habilitada
|
||||||
WHERE Id_Publicacion = @IdPublicacion;";
|
WHERE Id_Publicacion = @IdPublicacion;"; // Usar nombres de propiedad del objeto
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_dtPublicaciones_H
|
INSERT INTO dbo.dist_dtPublicaciones_H
|
||||||
(Id_Publicacion, Nombre, Observacion, Id_Empresa, Habilitada, Id_Usuario, FechaMod, TipoMod)
|
(Id_Publicacion, Nombre, Observacion, Id_Empresa, Habilitada, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdPublicacion, @Nombre, @Observacion, @IdEmpresa, @Habilitada, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdPublicacionParam, @NombreParam, @ObservacionParam, @IdEmpresaParam, @HabilitadaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
IdPublicacion = actual.IdPublicacion, Nombre = actual.Nombre, Observacion = actual.Observacion,
|
IdPublicacionParam = actual.IdPublicacion,
|
||||||
IdEmpresa = actual.IdEmpresa, Habilitada = actual.Habilitada ?? true,
|
NombreParam = actual.Nombre, // Valor ANTES de la actualización
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Actualizada"
|
ObservacionParam = actual.Observacion,
|
||||||
|
IdEmpresaParam = actual.IdEmpresa,
|
||||||
|
HabilitadaParam = actual.Habilitada ?? true,
|
||||||
|
IdUsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Actualizada"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
|
|
||||||
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, publicacionAActualizar, transaction);
|
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, publicacionAActualizar, transaction);
|
||||||
@@ -196,23 +241,30 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
{
|
{
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var actual = await connection.QuerySingleOrDefaultAsync<Publicacion>(
|
var actual = await connection.QuerySingleOrDefaultAsync<Publicacion>(
|
||||||
"SELECT * FROM dbo.dist_dtPublicaciones WHERE Id_Publicacion = @Id", new { Id = id }, transaction);
|
@"SELECT Id_Publicacion AS IdPublicacion, Nombre, Observacion, Id_Empresa AS IdEmpresa, CtrlDevoluciones, Habilitada
|
||||||
|
FROM dbo.dist_dtPublicaciones WHERE Id_Publicacion = @IdParam", // Usar alias
|
||||||
|
new { IdParam = id }, transaction);
|
||||||
if (actual == null) throw new KeyNotFoundException("Publicación no encontrada.");
|
if (actual == null) throw new KeyNotFoundException("Publicación no encontrada.");
|
||||||
|
|
||||||
const string sqlDelete = "DELETE FROM dbo.dist_dtPublicaciones WHERE Id_Publicacion = @Id";
|
const string sqlDelete = "DELETE FROM dbo.dist_dtPublicaciones WHERE Id_Publicacion = @IdParam";
|
||||||
const string sqlInsertHistorico = @"
|
const string sqlInsertHistorico = @"
|
||||||
INSERT INTO dbo.dist_dtPublicaciones_H
|
INSERT INTO dbo.dist_dtPublicaciones_H
|
||||||
(Id_Publicacion, Nombre, Observacion, Id_Empresa, Habilitada, Id_Usuario, FechaMod, TipoMod)
|
(Id_Publicacion, Nombre, Observacion, Id_Empresa, Habilitada, Id_Usuario, FechaMod, TipoMod)
|
||||||
VALUES (@IdPublicacion, @Nombre, @Observacion, @IdEmpresa, @Habilitada, @Id_Usuario, @FechaMod, @TipoMod);";
|
VALUES (@IdPublicacionParam, @NombreParam, @ObservacionParam, @IdEmpresaParam, @HabilitadaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
IdPublicacion = actual.IdPublicacion, actual.Nombre, actual.Observacion, actual.IdEmpresa,
|
IdPublicacionParam = actual.IdPublicacion,
|
||||||
Habilitada = actual.Habilitada ?? true,
|
NombreParam = actual.Nombre,
|
||||||
Id_Usuario = idUsuario, FechaMod = DateTime.Now, TipoMod = "Eliminada"
|
ObservacionParam = actual.Observacion,
|
||||||
|
IdEmpresaParam = actual.IdEmpresa,
|
||||||
|
HabilitadaParam = actual.Habilitada ?? true,
|
||||||
|
IdUsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Eliminada"
|
||||||
}, transaction);
|
}, transaction);
|
||||||
|
|
||||||
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { Id = id }, transaction);
|
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id }, transaction);
|
||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
// src/Data/Repositories/RecargoZonaRepository.cs
|
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using System.Data;
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using GestionIntegral.Api.Models.Distribucion; // Asegúrate que esta directiva using esté presente
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq; // Necesario para .Any()
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
{
|
{
|
||||||
public class RecargoZonaRepository : IRecargoZonaRepository
|
public class RecargoZonaRepository : IRecargoZonaRepository
|
||||||
{
|
{
|
||||||
private readonly DbConnectionFactory _connectionFactory; // _cf
|
private readonly DbConnectionFactory _connectionFactory;
|
||||||
private readonly ILogger<RecargoZonaRepository> _logger; // _log
|
private readonly ILogger<RecargoZonaRepository> _logger;
|
||||||
|
|
||||||
public RecargoZonaRepository(DbConnectionFactory connectionFactory, ILogger<RecargoZonaRepository> logger)
|
public RecargoZonaRepository(DbConnectionFactory connectionFactory, ILogger<RecargoZonaRepository> logger)
|
||||||
{
|
{
|
||||||
@@ -18,47 +20,245 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
|
public async Task<IEnumerable<(RecargoZona Recargo, string NombreZona)>> GetByPublicacionIdAsync(int idPublicacion)
|
||||||
{
|
{
|
||||||
// Obtener recargos para el historial
|
const string sql = @"
|
||||||
const string selectRecargos = "SELECT * FROM dbo.dist_RecargoZona WHERE Id_Publicacion = @IdPublicacion";
|
SELECT
|
||||||
var recargosAEliminar = await transaction.Connection!.QueryAsync<RecargoZona>(selectRecargos, new { IdPublicacion = idPublicacion }, transaction);
|
rz.Id_Recargo AS IdRecargo, rz.Id_Publicacion AS IdPublicacion, rz.Id_Zona AS IdZona,
|
||||||
|
rz.VigenciaD, rz.VigenciaH, rz.Valor,
|
||||||
// Asume que tienes una tabla dist_RecargoZona_H y un modelo RecargoZonaHistorico
|
z.Nombre AS NombreZona
|
||||||
const string sqlInsertHistorico = @"
|
FROM dbo.dist_RecargoZona rz
|
||||||
INSERT INTO dbo.dist_RecargoZona_H (Id_Recargo, Id_Publicacion, Id_Zona, VigenciaD, VigenciaH, Valor, Id_Usuario, FechaMod, TipoMod)
|
INNER JOIN dbo.dist_dtZonas z ON rz.Id_Zona = z.Id_Zona
|
||||||
VALUES (@IdRecargo, @IdPublicacion, @IdZona, @VigenciaD, @VigenciaH, @Valor, @Id_Usuario, @FechaMod, @TipoMod);"; // Nombres de parámetros corregidos
|
WHERE rz.Id_Publicacion = @IdPublicacionParam
|
||||||
|
ORDER BY z.Nombre, rz.VigenciaD DESC";
|
||||||
foreach (var recargo in recargosAEliminar)
|
|
||||||
{
|
|
||||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
|
||||||
{
|
|
||||||
// Mapear los campos de 'recargo' a los parámetros de sqlInsertHistorico
|
|
||||||
recargo.IdRecargo, // Usar las propiedades del modelo RecargoZona
|
|
||||||
recargo.IdPublicacion,
|
|
||||||
recargo.IdZona,
|
|
||||||
recargo.VigenciaD,
|
|
||||||
recargo.VigenciaH,
|
|
||||||
recargo.Valor,
|
|
||||||
Id_Usuario = idUsuarioAuditoria, // Este es el parámetro esperado por la query
|
|
||||||
FechaMod = DateTime.Now,
|
|
||||||
TipoMod = "Eliminado (Cascada)"
|
|
||||||
}, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
const string sql = "DELETE FROM dbo.dist_RecargoZona WHERE Id_Publicacion = @IdPublicacion";
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await transaction.Connection!.ExecuteAsync(sql, new { IdPublicacion = idPublicacion }, transaction: transaction);
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
return true; // Se intentó la operación
|
return await connection.QueryAsync<RecargoZona, string, (RecargoZona, string)>(
|
||||||
|
sql,
|
||||||
|
(recargo, nombreZona) => (recargo, nombreZona),
|
||||||
|
new { IdPublicacionParam = idPublicacion },
|
||||||
|
splitOn: "NombreZona"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener Recargos por Zona para Publicacion ID: {IdPublicacion}", idPublicacion);
|
||||||
|
return Enumerable.Empty<(RecargoZona, string)>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RecargoZona?> GetByIdAsync(int idRecargo)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Recargo AS IdRecargo, Id_Publicacion AS IdPublicacion, Id_Zona AS IdZona,
|
||||||
|
VigenciaD, VigenciaH, Valor
|
||||||
|
FROM dbo.dist_RecargoZona
|
||||||
|
WHERE Id_Recargo = @IdRecargoParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<RecargoZona>(sql, new { IdRecargoParam = idRecargo });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener Recargo por Zona por ID: {IdRecargo}", idRecargo);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RecargoZona?> GetActiveByPublicacionZonaAndDateAsync(int idPublicacion, int idZona, DateTime fecha, IDbTransaction? transaction = null)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT TOP 1
|
||||||
|
Id_Recargo AS IdRecargo, Id_Publicacion AS IdPublicacion, Id_Zona AS IdZona,
|
||||||
|
VigenciaD, VigenciaH, Valor
|
||||||
|
FROM dbo.dist_RecargoZona
|
||||||
|
WHERE Id_Publicacion = @IdPublicacionParam AND Id_Zona = @IdZonaParam
|
||||||
|
AND VigenciaD <= @FechaParam
|
||||||
|
AND (VigenciaH IS NULL OR VigenciaH >= @FechaParam)
|
||||||
|
ORDER BY VigenciaD DESC;";
|
||||||
|
|
||||||
|
var cn = transaction?.Connection ?? _connectionFactory.CreateConnection();
|
||||||
|
bool ownConnection = transaction == null;
|
||||||
|
RecargoZona? result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn)
|
||||||
|
{
|
||||||
|
await dbConn.OpenAsync();
|
||||||
|
}
|
||||||
|
result = await cn.QuerySingleOrDefaultAsync<RecargoZona>(sql,
|
||||||
|
new { IdPublicacionParam = idPublicacion, IdZonaParam = idZona, FechaParam = fecha.Date },
|
||||||
|
transaction);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error en GetActiveByPublicacionZonaAndDateAsync. IdPublicacion: {IdPublicacion}, IdZona: {IdZona}, Fecha: {Fecha}", idPublicacion, idZona, fecha);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Open && cn is System.Data.Common.DbConnection dbConnClose)
|
||||||
|
{
|
||||||
|
await dbConnClose.CloseAsync();
|
||||||
|
}
|
||||||
|
if (ownConnection) (cn as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RecargoZona?> GetPreviousActiveRecargoAsync(int idPublicacion, int idZona, DateTime vigenciaDNuevo, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT TOP 1
|
||||||
|
Id_Recargo AS IdRecargo, Id_Publicacion AS IdPublicacion, Id_Zona AS IdZona,
|
||||||
|
VigenciaD, VigenciaH, Valor
|
||||||
|
FROM dbo.dist_RecargoZona
|
||||||
|
WHERE Id_Publicacion = @IdPublicacionParam AND Id_Zona = @IdZonaParam
|
||||||
|
AND VigenciaD < @VigenciaDNuevoParam
|
||||||
|
AND VigenciaH IS NULL
|
||||||
|
ORDER BY VigenciaD DESC;";
|
||||||
|
return await transaction.Connection!.QuerySingleOrDefaultAsync<RecargoZona>(sql,
|
||||||
|
new { IdPublicacionParam = idPublicacion, IdZonaParam = idZona, VigenciaDNuevoParam = vigenciaDNuevo.Date },
|
||||||
|
transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RecargoZona?> CreateAsync(RecargoZona nuevoRecargo, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.dist_RecargoZona (Id_Publicacion, Id_Zona, VigenciaD, VigenciaH, Valor)
|
||||||
|
OUTPUT INSERTED.Id_Recargo AS IdRecargo, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Zona AS IdZona,
|
||||||
|
INSERTED.VigenciaD, INSERTED.VigenciaH, INSERTED.Valor
|
||||||
|
VALUES (@IdPublicacion, @IdZona, @VigenciaD, @VigenciaH, @Valor);";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_RecargoZona_H (Id_Recargo, Id_Publicacion, Id_Zona, VigenciaD, VigenciaH, Valor, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdRecargoParam, @IdPublicacionParam, @IdZonaParam, @VigenciaDParam, @VigenciaHParam, @ValorParam, @Id_UsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<RecargoZona>(sqlInsert, nuevoRecargo, transaction);
|
||||||
|
if (inserted == null || inserted.IdRecargo == 0) throw new DataException("Error al crear recargo por zona o al obtener el ID generado.");
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdRecargoParam = inserted.IdRecargo,
|
||||||
|
IdPublicacionParam = inserted.IdPublicacion,
|
||||||
|
IdZonaParam = inserted.IdZona,
|
||||||
|
VigenciaDParam = inserted.VigenciaD,
|
||||||
|
VigenciaHParam = inserted.VigenciaH,
|
||||||
|
ValorParam = inserted.Valor,
|
||||||
|
Id_UsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Creado"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(RecargoZona recargoAActualizar, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<RecargoZona>(
|
||||||
|
@"SELECT Id_Recargo AS IdRecargo, Id_Publicacion AS IdPublicacion, Id_Zona AS IdZona,
|
||||||
|
VigenciaD, VigenciaH, Valor
|
||||||
|
FROM dbo.dist_RecargoZona WHERE Id_Recargo = @IdRecargoParam", // Usar parámetro con alias
|
||||||
|
new { IdRecargoParam = recargoAActualizar.IdRecargo }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Recargo por zona no encontrado.");
|
||||||
|
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.dist_RecargoZona SET
|
||||||
|
Valor = @Valor, VigenciaH = @VigenciaH
|
||||||
|
WHERE Id_Recargo = @IdRecargo;";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_RecargoZona_H (Id_Recargo, Id_Publicacion, Id_Zona, VigenciaD, VigenciaH, Valor, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdRecargoParam, @IdPublicacionParam, @IdZonaParam, @VigenciaDParam, @VigenciaHParam, @ValorParam, @Id_UsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdRecargoParam = actual.IdRecargo,
|
||||||
|
IdPublicacionParam = actual.IdPublicacion,
|
||||||
|
IdZonaParam = actual.IdZona,
|
||||||
|
VigenciaDParam = actual.VigenciaD,
|
||||||
|
VigenciaHParam = actual.VigenciaH,
|
||||||
|
ValorParam = actual.Valor,
|
||||||
|
Id_UsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Actualizado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, recargoAActualizar, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idRecargo, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<RecargoZona>(
|
||||||
|
@"SELECT Id_Recargo AS IdRecargo, Id_Publicacion AS IdPublicacion, Id_Zona AS IdZona,
|
||||||
|
VigenciaD, VigenciaH, Valor
|
||||||
|
FROM dbo.dist_RecargoZona WHERE Id_Recargo = @IdRecargoParam", new { IdRecargoParam = idRecargo }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Recargo por zona no encontrado para eliminar.");
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.dist_RecargoZona WHERE Id_Recargo = @IdRecargoParam"; // Usar parámetro con alias
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_RecargoZona_H (Id_Recargo, Id_Publicacion, Id_Zona, VigenciaD, VigenciaH, Valor, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdRecargoParam, @IdPublicacionParam, @IdZonaParam, @VigenciaDParam, @VigenciaHParam, @ValorParam, @Id_UsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdRecargoParam = actual.IdRecargo,
|
||||||
|
IdPublicacionParam = actual.IdPublicacion,
|
||||||
|
IdZonaParam = actual.IdZona,
|
||||||
|
VigenciaDParam = actual.VigenciaD,
|
||||||
|
VigenciaHParam = actual.VigenciaH,
|
||||||
|
ValorParam = actual.Valor,
|
||||||
|
Id_UsuarioParam = idUsuario,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Eliminado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdRecargoParam = idRecargo }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteByPublicacionIdAsync(int idPublicacion, int idUsuarioAuditoria, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string selectSql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Recargo AS IdRecargo, Id_Publicacion AS IdPublicacion, Id_Zona AS IdZona,
|
||||||
|
VigenciaD, VigenciaH, Valor
|
||||||
|
FROM dbo.dist_RecargoZona WHERE Id_Publicacion = @IdPublicacionParam";
|
||||||
|
var itemsToDelete = await transaction.Connection!.QueryAsync<RecargoZona>(selectSql, new { IdPublicacionParam = idPublicacion }, transaction);
|
||||||
|
|
||||||
|
if (itemsToDelete.Any())
|
||||||
|
{
|
||||||
|
const string insertHistoricoSql = @"
|
||||||
|
INSERT INTO dbo.dist_RecargoZona_H (Id_Recargo, Id_Publicacion, Id_Zona, VigenciaD, VigenciaH, Valor, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdRecargoParam, @IdPublicacionParam, @IdZonaParam, @VigenciaDParam, @VigenciaHParam, @ValorParam, @Id_UsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
foreach (var item in itemsToDelete)
|
||||||
|
{
|
||||||
|
await transaction.Connection!.ExecuteAsync(insertHistoricoSql, new
|
||||||
|
{
|
||||||
|
IdRecargoParam = item.IdRecargo,
|
||||||
|
IdPublicacionParam = item.IdPublicacion,
|
||||||
|
IdZonaParam = item.IdZona,
|
||||||
|
VigenciaDParam = item.VigenciaD,
|
||||||
|
VigenciaHParam = item.VigenciaH,
|
||||||
|
ValorParam = item.Valor,
|
||||||
|
Id_UsuarioParam = idUsuarioAuditoria,
|
||||||
|
FechaModParam = DateTime.Now,
|
||||||
|
TipoModParam = "Eliminado (Cascada)"
|
||||||
|
}, transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const string deleteSql = "DELETE FROM dbo.dist_RecargoZona WHERE Id_Publicacion = @IdPublicacionParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await transaction.Connection!.ExecuteAsync(deleteSql, new { IdPublicacionParam = idPublicacion }, transaction: transaction);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
catch (System.Exception ex)
|
catch (System.Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error al eliminar RecargosZona por IdPublicacion: {IdPublicacion}", idPublicacion);
|
_logger.LogError(ex, "Error al eliminar RecargosZona por IdPublicacion: {IdPublicacion}", idPublicacion);
|
||||||
throw; // Re-lanzar para que la transacción padre haga rollback
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Aquí irían otros métodos CRUD para RecargoZona si se gestionan individualmente.
|
|
||||||
// Por ejemplo, GetByPublicacionId, Create, Update, Delete (para un recargo específico).
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Distribucion;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
|
{
|
||||||
|
public class SalidaOtroDestinoRepository : ISalidaOtroDestinoRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<SalidaOtroDestinoRepository> _log;
|
||||||
|
|
||||||
|
public SalidaOtroDestinoRepository(DbConnectionFactory cf, ILogger<SalidaOtroDestinoRepository> log)
|
||||||
|
{
|
||||||
|
_cf = cf;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SalidaOtroDestino>> GetAllAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idPublicacion, int? idDestino)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(@"
|
||||||
|
SELECT
|
||||||
|
Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Destino AS IdDestino,
|
||||||
|
Fecha, Cantidad, Observacion
|
||||||
|
FROM dbo.dist_SalidasOtrosDestinos
|
||||||
|
WHERE 1=1");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
|
||||||
|
if (fechaDesde.HasValue) { sqlBuilder.Append(" AND Fecha >= @FechaDesdeParam"); parameters.Add("FechaDesdeParam", fechaDesde.Value.Date); }
|
||||||
|
if (fechaHasta.HasValue) { sqlBuilder.Append(" AND Fecha <= @FechaHastaParam"); parameters.Add("FechaHastaParam", fechaHasta.Value.Date); }
|
||||||
|
if (idPublicacion.HasValue) { sqlBuilder.Append(" AND Id_Publicacion = @IdPublicacionParam"); parameters.Add("IdPublicacionParam", idPublicacion.Value); }
|
||||||
|
if (idDestino.HasValue) { sqlBuilder.Append(" AND Id_Destino = @IdDestinoParam"); parameters.Add("IdDestinoParam", idDestino.Value); }
|
||||||
|
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Publicacion;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<SalidaOtroDestino>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Salidas a Otros Destinos.");
|
||||||
|
return Enumerable.Empty<SalidaOtroDestino>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SalidaOtroDestino?> GetByIdAsync(int idParte)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Destino AS IdDestino,
|
||||||
|
Fecha, Cantidad, Observacion
|
||||||
|
FROM dbo.dist_SalidasOtrosDestinos
|
||||||
|
WHERE Id_Parte = @IdParteParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<SalidaOtroDestino>(sql, new { IdParteParam = idParte });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener SalidaOtroDestino por ID: {IdParte}", idParte);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SalidaOtroDestino?> CreateAsync(SalidaOtroDestino nuevaSalida, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.dist_SalidasOtrosDestinos (Id_Publicacion, Id_Destino, Fecha, Cantidad, Observacion)
|
||||||
|
OUTPUT INSERTED.Id_Parte AS IdParte, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Destino AS IdDestino,
|
||||||
|
INSERTED.Fecha, INSERTED.Cantidad, INSERTED.Observacion
|
||||||
|
VALUES (@IdPublicacion, @IdDestino, @Fecha, @Cantidad, @Observacion);";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_SalidasOtrosDestinos_H (Id_Parte, Id_Publicacion, Id_Destino, Fecha, Cantidad, Observacion, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdParteHist, @IdPublicacionHist, @IdDestinoHist, @FechaHist, @CantidadHist, @ObservacionHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<SalidaOtroDestino>(sqlInsert, nuevaSalida, transaction);
|
||||||
|
if (inserted == null || inserted.IdParte == 0) throw new DataException("Error al crear salida o ID no generado.");
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdParteHist = inserted.IdParte, IdPublicacionHist = inserted.IdPublicacion, IdDestinoHist = inserted.IdDestino,
|
||||||
|
FechaHist = inserted.Fecha, CantidadHist = inserted.Cantidad, ObservacionHist = inserted.Observacion,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creada"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(SalidaOtroDestino salidaAActualizar, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<SalidaOtroDestino>(
|
||||||
|
@"SELECT Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Destino AS IdDestino, Fecha, Cantidad, Observacion
|
||||||
|
FROM dbo.dist_SalidasOtrosDestinos WHERE Id_Parte = @IdParteParam",
|
||||||
|
new { IdParteParam = salidaAActualizar.IdParte }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Salida no encontrada.");
|
||||||
|
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.dist_SalidasOtrosDestinos SET
|
||||||
|
Cantidad = @Cantidad, Observacion = @Observacion
|
||||||
|
-- No se permite cambiar Publicacion, Destino o Fecha de una salida ya registrada
|
||||||
|
WHERE Id_Parte = @IdParte;";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_SalidasOtrosDestinos_H (Id_Parte, Id_Publicacion, Id_Destino, Fecha, Cantidad, Observacion, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdParteHist, @IdPublicacionHist, @IdDestinoHist, @FechaHist, @CantidadHist, @ObservacionHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdParteHist = actual.IdParte, IdPublicacionHist = actual.IdPublicacion, IdDestinoHist = actual.IdDestino,
|
||||||
|
FechaHist = actual.Fecha, CantidadHist = actual.Cantidad, ObservacionHist = actual.Observacion, // Valores ANTERIORES
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizada"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, salidaAActualizar, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<SalidaOtroDestino>(
|
||||||
|
@"SELECT Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Destino AS IdDestino, Fecha, Cantidad, Observacion
|
||||||
|
FROM dbo.dist_SalidasOtrosDestinos WHERE Id_Parte = @IdParteParam",
|
||||||
|
new { IdParteParam = idParte }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Salida no encontrada para eliminar.");
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.dist_SalidasOtrosDestinos WHERE Id_Parte = @IdParteParam";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_SalidasOtrosDestinos_H (Id_Parte, Id_Publicacion, Id_Destino, Fecha, Cantidad, Observacion, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdParteHist, @IdPublicacionHist, @IdDestinoHist, @FechaHist, @CantidadHist, @ObservacionHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdParteHist = actual.IdParte, IdPublicacionHist = actual.IdPublicacion, IdDestinoHist = actual.IdDestino,
|
||||||
|
FechaHist = actual.Fecha, CantidadHist = actual.Cantidad, ObservacionHist = actual.Observacion,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdParteParam = idParte }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using GestionIntegral.Api.Models.Impresion;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Impresion
|
||||||
|
{
|
||||||
|
public interface IRegTiradaRepository // Para bob_RegTiradas
|
||||||
|
{
|
||||||
|
Task<RegTirada?> GetByIdAsync(int idRegistro);
|
||||||
|
Task<IEnumerable<RegTirada>> GetByCriteriaAsync(DateTime? fecha, int? idPublicacion, int? idPlanta);
|
||||||
|
Task<RegTirada?> CreateAsync(RegTirada nuevaTirada, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteAsync(int idRegistro, int idUsuario, IDbTransaction transaction); // Si se borra el registro principal
|
||||||
|
Task<bool> DeleteByFechaPublicacionPlantaAsync(DateTime fecha, int idPublicacion, int idPlanta, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<RegTirada?> GetByFechaPublicacionPlantaAsync(DateTime fecha, int idPublicacion, int idPlanta, IDbTransaction? transaction = null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IRegPublicacionSeccionRepository // Para bob_RegPublicaciones
|
||||||
|
{
|
||||||
|
Task<IEnumerable<RegPublicacionSeccion>> GetByFechaPublicacionPlantaAsync(DateTime fecha, int idPublicacion, int idPlanta);
|
||||||
|
Task<RegPublicacionSeccion?> CreateAsync(RegPublicacionSeccion nuevaSeccionTirada, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteByFechaPublicacionPlantaAsync(DateTime fecha, int idPublicacion, int idPlanta, int idUsuario, IDbTransaction transaction);
|
||||||
|
// Podría tener un DeleteByIdAsync si se permite borrar secciones individuales de una tirada
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using GestionIntegral.Api.Models.Impresion;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
|
{
|
||||||
|
public interface IStockBobinaRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<StockBobina>> GetAllAsync( // Para listados generales, DTO se arma en servicio
|
||||||
|
int? idTipoBobina,
|
||||||
|
string? nroBobinaFilter,
|
||||||
|
int? idPlanta,
|
||||||
|
int? idEstadoBobina,
|
||||||
|
string? remitoFilter,
|
||||||
|
DateTime? fechaDesde,
|
||||||
|
DateTime? fechaHasta);
|
||||||
|
|
||||||
|
Task<StockBobina?> GetByIdAsync(int idBobina);
|
||||||
|
Task<StockBobina?> GetByNroBobinaAsync(string nroBobina); // Para validar unicidad de NroBobina
|
||||||
|
Task<StockBobina?> CreateAsync(StockBobina nuevaBobina, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(StockBobina bobinaAActualizar, int idUsuario, IDbTransaction transaction, string tipoMod = "Actualizada"); // tipoMod para historial
|
||||||
|
Task<bool> DeleteAsync(int idBobina, int idUsuario, IDbTransaction transaction); // Solo si está en estado "Disponible"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Impresion;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Impresion
|
||||||
|
{
|
||||||
|
public class RegTiradaRepository : IRegTiradaRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<RegTiradaRepository> _log;
|
||||||
|
|
||||||
|
public RegTiradaRepository(DbConnectionFactory cf, ILogger<RegTiradaRepository> log)
|
||||||
|
{
|
||||||
|
_cf = cf;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RegTirada?> GetByIdAsync(int idRegistro)
|
||||||
|
{
|
||||||
|
const string sql = "SELECT Id_Registro AS IdRegistro, Ejemplares, Id_Publicacion AS IdPublicacion, Fecha, Id_Planta AS IdPlanta FROM dbo.bob_RegTiradas WHERE Id_Registro = @IdRegistroParam";
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<RegTirada>(sql, new { IdRegistroParam = idRegistro });
|
||||||
|
}
|
||||||
|
public async Task<RegTirada?> GetByFechaPublicacionPlantaAsync(DateTime fecha, int idPublicacion, int idPlanta, IDbTransaction? transaction = null)
|
||||||
|
{
|
||||||
|
const string sql = "SELECT Id_Registro AS IdRegistro, Ejemplares, Id_Publicacion AS IdPublicacion, Fecha, Id_Planta AS IdPlanta FROM dbo.bob_RegTiradas WHERE Fecha = @FechaParam AND Id_Publicacion = @IdPublicacionParam AND Id_Planta = @IdPlantaParam";
|
||||||
|
var cn = transaction?.Connection ?? _cf.CreateConnection();
|
||||||
|
bool ownConnection = transaction == null;
|
||||||
|
RegTirada? result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
|
||||||
|
result = await cn.QuerySingleOrDefaultAsync<RegTirada>(sql, new { FechaParam = fecha.Date, IdPublicacionParam = idPublicacion, IdPlantaParam = idPlanta }, transaction);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Open && cn is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync();
|
||||||
|
if (ownConnection) (cn as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<IEnumerable<RegTirada>> GetByCriteriaAsync(DateTime? fecha, int? idPublicacion, int? idPlanta)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder("SELECT Id_Registro AS IdRegistro, Ejemplares, Id_Publicacion AS IdPublicacion, Fecha, Id_Planta AS IdPlanta FROM dbo.bob_RegTiradas WHERE 1=1 ");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
if (fecha.HasValue) { sqlBuilder.Append("AND Fecha = @FechaParam "); parameters.Add("FechaParam", fecha.Value.Date); }
|
||||||
|
if (idPublicacion.HasValue) { sqlBuilder.Append("AND Id_Publicacion = @IdPublicacionParam "); parameters.Add("IdPublicacionParam", idPublicacion.Value); }
|
||||||
|
if (idPlanta.HasValue) { sqlBuilder.Append("AND Id_Planta = @IdPlantaParam "); parameters.Add("IdPlantaParam", idPlanta.Value); }
|
||||||
|
sqlBuilder.Append("ORDER BY Fecha DESC, Id_Publicacion;");
|
||||||
|
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<RegTirada>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RegTirada?> CreateAsync(RegTirada nuevaTirada, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.bob_RegTiradas (Ejemplares, Id_Publicacion, Fecha, Id_Planta)
|
||||||
|
OUTPUT INSERTED.Id_Registro AS IdRegistro, INSERTED.Ejemplares, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Fecha, INSERTED.Id_Planta AS IdPlanta
|
||||||
|
VALUES (@Ejemplares, @IdPublicacion, @Fecha, @IdPlanta);";
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<RegTirada>(sqlInsert, nuevaTirada, transaction);
|
||||||
|
|
||||||
|
const string sqlHistorico = @"INSERT INTO dbo.bob_RegTiradas_H (Id_Registro, Ejemplares, Id_Publicacion, Fecha, Id_Planta, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdRegistroParam, @EjemplaresParam, @IdPublicacionParam, @FechaParam, @IdPlantaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdRegistroParam = inserted.IdRegistro, EjemplaresParam = inserted.Ejemplares, IdPublicacionParam = inserted.IdPublicacion, FechaParam = inserted.Fecha, IdPlantaParam = inserted.IdPlanta,
|
||||||
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idRegistro, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await GetByIdAsync(idRegistro); // No necesita TX aquí ya que es solo para historial
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Registro de tirada no encontrado.");
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.bob_RegTiradas WHERE Id_Registro = @IdRegistroParam";
|
||||||
|
const string sqlHistorico = @"INSERT INTO dbo.bob_RegTiradas_H (Id_Registro, Ejemplares, Id_Publicacion, Fecha, Id_Planta, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdRegistroParam, @EjemplaresParam, @IdPublicacionParam, @FechaParam, @IdPlantaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdRegistroParam = actual.IdRegistro, EjemplaresParam = actual.Ejemplares, IdPublicacionParam = actual.IdPublicacion, FechaParam = actual.Fecha, IdPlantaParam = actual.IdPlanta,
|
||||||
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdRegistroParam = idRegistro }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
public async Task<bool> DeleteByFechaPublicacionPlantaAsync(DateTime fecha, int idPublicacion, int idPlanta, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var tiradasAEliminar = await GetByCriteriaAsync(fecha.Date, idPublicacion, idPlanta); // Obtener antes de borrar para historial
|
||||||
|
|
||||||
|
foreach(var actual in tiradasAEliminar)
|
||||||
|
{
|
||||||
|
const string sqlHistorico = @"INSERT INTO dbo.bob_RegTiradas_H (Id_Registro, Ejemplares, Id_Publicacion, Fecha, Id_Planta, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdRegistroParam, @EjemplaresParam, @IdPublicacionParam, @FechaParam, @IdPlantaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdRegistroParam = actual.IdRegistro, EjemplaresParam = actual.Ejemplares, IdPublicacionParam = actual.IdPublicacion, FechaParam = actual.Fecha, IdPlantaParam = actual.IdPlanta,
|
||||||
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado (Por Fecha/Pub/Planta)"
|
||||||
|
}, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.bob_RegTiradas WHERE Fecha = @FechaParam AND Id_Publicacion = @IdPublicacionParam AND Id_Planta = @IdPlantaParam";
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete,
|
||||||
|
new { FechaParam = fecha.Date, IdPublicacionParam = idPublicacion, IdPlantaParam = idPlanta }, transaction);
|
||||||
|
return rowsAffected > 0; // Devuelve true si se borró al menos una fila
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RegPublicacionSeccionRepository : IRegPublicacionSeccionRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<RegPublicacionSeccionRepository> _log;
|
||||||
|
public RegPublicacionSeccionRepository(DbConnectionFactory cf, ILogger<RegPublicacionSeccionRepository> log){ _cf = cf; _log = log; }
|
||||||
|
|
||||||
|
public async Task<IEnumerable<RegPublicacionSeccion>> GetByFechaPublicacionPlantaAsync(DateTime fecha, int idPublicacion, int idPlanta)
|
||||||
|
{
|
||||||
|
const string sql = @"SELECT Id_Tirada AS IdTirada, Id_Publicacion AS IdPublicacion, Id_Seccion AS IdSeccion, CantPag, Fecha, Id_Planta AS IdPlanta
|
||||||
|
FROM dbo.bob_RegPublicaciones
|
||||||
|
WHERE Fecha = @FechaParam AND Id_Publicacion = @IdPublicacionParam AND Id_Planta = @IdPlantaParam";
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<RegPublicacionSeccion>(sql, new { FechaParam = fecha.Date, IdPublicacionParam = idPublicacion, IdPlantaParam = idPlanta });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RegPublicacionSeccion?> CreateAsync(RegPublicacionSeccion nuevaSeccionTirada, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.bob_RegPublicaciones (Id_Publicacion, Id_Seccion, CantPag, Fecha, Id_Planta)
|
||||||
|
OUTPUT INSERTED.Id_Tirada AS IdTirada, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Seccion AS IdSeccion, INSERTED.CantPag, INSERTED.Fecha, INSERTED.Id_Planta AS IdPlanta
|
||||||
|
VALUES (@IdPublicacion, @IdSeccion, @CantPag, @Fecha, @IdPlanta);";
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<RegPublicacionSeccion>(sqlInsert, nuevaSeccionTirada, transaction);
|
||||||
|
|
||||||
|
const string sqlHistorico = @"INSERT INTO dbo.bob_RegPublicaciones_H (Id_Tirada, Id_Publicacion, Id_Seccion, CantPag, Fecha, Id_Planta, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdTiradaParam, @IdPublicacionParam, @IdSeccionParam, @CantPagParam, @FechaParam, @IdPlantaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdTiradaParam = inserted.IdTirada, IdPublicacionParam = inserted.IdPublicacion, IdSeccionParam = inserted.IdSeccion, CantPagParam = inserted.CantPag, FechaParam = inserted.Fecha, IdPlantaParam = inserted.IdPlanta,
|
||||||
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteByFechaPublicacionPlantaAsync(DateTime fecha, int idPublicacion, int idPlanta, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var seccionesAEliminar = await GetByFechaPublicacionPlantaAsync(fecha.Date, idPublicacion, idPlanta); // No necesita TX, es SELECT
|
||||||
|
|
||||||
|
foreach(var actual in seccionesAEliminar)
|
||||||
|
{
|
||||||
|
const string sqlHistorico = @"INSERT INTO dbo.bob_RegPublicaciones_H (Id_Tirada, Id_Publicacion, Id_Seccion, CantPag, Fecha, Id_Planta, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdTiradaParam, @IdPublicacionParam, @IdSeccionParam, @CantPagParam, @FechaParam, @IdPlantaParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdTiradaParam = actual.IdTirada, IdPublicacionParam = actual.IdPublicacion, IdSeccionParam = actual.IdSeccion, CantPagParam = actual.CantPag, FechaParam = actual.Fecha, IdPlantaParam = actual.IdPlanta,
|
||||||
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado (Por Fecha/Pub/Planta)"
|
||||||
|
}, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.bob_RegPublicaciones WHERE Fecha = @FechaParam AND Id_Publicacion = @IdPublicacionParam AND Id_Planta = @IdPlantaParam";
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete,
|
||||||
|
new { FechaParam = fecha.Date, IdPublicacionParam = idPublicacion, IdPlantaParam = idPlanta }, transaction);
|
||||||
|
return rowsAffected >= 0; // Devuelve true incluso si no había nada que borrar (para que no falle la TX si es parte de una cadena)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Impresion;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||||
|
{
|
||||||
|
public class StockBobinaRepository : IStockBobinaRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _connectionFactory;
|
||||||
|
private readonly ILogger<StockBobinaRepository> _logger;
|
||||||
|
|
||||||
|
public StockBobinaRepository(DbConnectionFactory connectionFactory, ILogger<StockBobinaRepository> logger)
|
||||||
|
{
|
||||||
|
_connectionFactory = connectionFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<StockBobina>> GetAllAsync(
|
||||||
|
int? idTipoBobina, string? nroBobinaFilter, int? idPlanta,
|
||||||
|
int? idEstadoBobina, string? remitoFilter, DateTime? fechaDesde, DateTime? fechaHasta)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(@"
|
||||||
|
SELECT
|
||||||
|
sb.Id_Bobina AS IdBobina, sb.Id_TipoBobina AS IdTipoBobina, sb.NroBobina, sb.Peso,
|
||||||
|
sb.Id_Planta AS IdPlanta, sb.Id_EstadoBobina AS IdEstadoBobina, sb.Remito, sb.FechaRemito,
|
||||||
|
sb.FechaEstado, sb.Id_Publicacion AS IdPublicacion, sb.Id_Seccion AS IdSeccion, sb.Obs
|
||||||
|
FROM dbo.bob_StockBobinas sb
|
||||||
|
WHERE 1=1");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
|
||||||
|
if (idTipoBobina.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND sb.Id_TipoBobina = @IdTipoBobinaParam");
|
||||||
|
parameters.Add("IdTipoBobinaParam", idTipoBobina.Value);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(nroBobinaFilter))
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND sb.NroBobina LIKE @NroBobinaParam");
|
||||||
|
parameters.Add("NroBobinaParam", $"%{nroBobinaFilter}%");
|
||||||
|
}
|
||||||
|
if (idPlanta.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND sb.Id_Planta = @IdPlantaParam");
|
||||||
|
parameters.Add("IdPlantaParam", idPlanta.Value);
|
||||||
|
}
|
||||||
|
if (idEstadoBobina.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND sb.Id_EstadoBobina = @IdEstadoBobinaParam");
|
||||||
|
parameters.Add("IdEstadoBobinaParam", idEstadoBobina.Value);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(remitoFilter))
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND sb.Remito LIKE @RemitoParam");
|
||||||
|
parameters.Add("RemitoParam", $"%{remitoFilter}%");
|
||||||
|
}
|
||||||
|
if (fechaDesde.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND sb.FechaRemito >= @FechaDesdeParam"); // O FechaEstado según el contexto del filtro
|
||||||
|
parameters.Add("FechaDesdeParam", fechaDesde.Value.Date);
|
||||||
|
}
|
||||||
|
if (fechaHasta.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND sb.FechaRemito <= @FechaHastaParam"); // O FechaEstado
|
||||||
|
parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); // Hasta el final del día
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlBuilder.Append(" ORDER BY sb.FechaRemito DESC, sb.NroBobina;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<StockBobina>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener Stock de Bobinas con filtros.");
|
||||||
|
return Enumerable.Empty<StockBobina>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StockBobina?> GetByIdAsync(int idBobina)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Bobina AS IdBobina, Id_TipoBobina AS IdTipoBobina, NroBobina, Peso,
|
||||||
|
Id_Planta AS IdPlanta, Id_EstadoBobina AS IdEstadoBobina, Remito, FechaRemito,
|
||||||
|
FechaEstado, Id_Publicacion AS IdPublicacion, Id_Seccion AS IdSeccion, Obs
|
||||||
|
FROM dbo.bob_StockBobinas
|
||||||
|
WHERE Id_Bobina = @IdBobinaParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<StockBobina>(sql, new { IdBobinaParam = idBobina });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener StockBobina por ID: {IdBobina}", idBobina);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StockBobina?> GetByNroBobinaAsync(string nroBobina)
|
||||||
|
{
|
||||||
|
const string sql = @"
|
||||||
|
SELECT
|
||||||
|
Id_Bobina AS IdBobina, Id_TipoBobina AS IdTipoBobina, NroBobina, Peso,
|
||||||
|
Id_Planta AS IdPlanta, Id_EstadoBobina AS IdEstadoBobina, Remito, FechaRemito,
|
||||||
|
FechaEstado, Id_Publicacion AS IdPublicacion, Id_Seccion AS IdSeccion, Obs
|
||||||
|
FROM dbo.bob_StockBobinas
|
||||||
|
WHERE NroBobina = @NroBobinaParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<StockBobina>(sql, new { NroBobinaParam = nroBobina });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener StockBobina por NroBobina: {NroBobina}", nroBobina);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<StockBobina?> CreateAsync(StockBobina nuevaBobina, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.bob_StockBobinas
|
||||||
|
(Id_TipoBobina, NroBobina, Peso, Id_Planta, Id_EstadoBobina, Remito, FechaRemito, FechaEstado, Id_Publicacion, Id_Seccion, Obs)
|
||||||
|
OUTPUT INSERTED.Id_Bobina AS IdBobina, INSERTED.Id_TipoBobina AS IdTipoBobina, INSERTED.NroBobina, INSERTED.Peso,
|
||||||
|
INSERTED.Id_Planta AS IdPlanta, INSERTED.Id_EstadoBobina AS IdEstadoBobina, INSERTED.Remito, INSERTED.FechaRemito,
|
||||||
|
INSERTED.FechaEstado, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Seccion AS IdSeccion, INSERTED.Obs
|
||||||
|
VALUES (@IdTipoBobina, @NroBobina, @Peso, @IdPlanta, @IdEstadoBobina, @Remito, @FechaRemito, @FechaEstado, @IdPublicacion, @IdSeccion, @Obs);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<StockBobina>(sqlInsert, nuevaBobina, transaction);
|
||||||
|
if (inserted == null || inserted.IdBobina == 0) throw new DataException("Error al ingresar bobina o ID no generado.");
|
||||||
|
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.bob_StockBobinas_H
|
||||||
|
(Id_Bobina, Id_TipoBobina, NroBobina, Peso, Id_Planta, Id_EstadoBobina, Remito, FechaRemito, FechaEstado, Id_Publicacion, Id_Seccion, Obs, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdBobinaHist, @IdTipoBobinaHist, @NroBobinaHist, @PesoHist, @IdPlantaHist, @IdEstadoBobinaHist, @RemitoHist, @FechaRemitoHist, @FechaEstadoHist, @IdPublicacionHist, @IdSeccionHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdBobinaHist = inserted.IdBobina, IdTipoBobinaHist = inserted.IdTipoBobina, NroBobinaHist = inserted.NroBobina, PesoHist = inserted.Peso,
|
||||||
|
IdPlantaHist = inserted.IdPlanta, IdEstadoBobinaHist = inserted.IdEstadoBobina, RemitoHist = inserted.Remito, FechaRemitoHist = inserted.FechaRemito,
|
||||||
|
FechaEstadoHist = inserted.FechaEstado, IdPublicacionHist = inserted.IdPublicacion, IdSeccionHist = inserted.IdSeccion, ObsHist = inserted.Obs,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Ingreso"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(StockBobina bobinaAActualizar, int idUsuario, IDbTransaction transaction, string tipoMod = "Actualizada")
|
||||||
|
{
|
||||||
|
// Obtener estado actual para el historial
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<StockBobina>(
|
||||||
|
@"SELECT Id_Bobina AS IdBobina, Id_TipoBobina AS IdTipoBobina, NroBobina, Peso,
|
||||||
|
Id_Planta AS IdPlanta, Id_EstadoBobina AS IdEstadoBobina, Remito, FechaRemito,
|
||||||
|
FechaEstado, Id_Publicacion AS IdPublicacion, Id_Seccion AS IdSeccion, Obs
|
||||||
|
FROM dbo.bob_StockBobinas WHERE Id_Bobina = @IdBobinaParam",
|
||||||
|
new { IdBobinaParam = bobinaAActualizar.IdBobina }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Bobina no encontrada para actualizar.");
|
||||||
|
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.bob_StockBobinas SET
|
||||||
|
Id_TipoBobina = @IdTipoBobina, NroBobina = @NroBobina, Peso = @Peso, Id_Planta = @IdPlanta,
|
||||||
|
Id_EstadoBobina = @IdEstadoBobina, Remito = @Remito, FechaRemito = @FechaRemito, FechaEstado = @FechaEstado,
|
||||||
|
Id_Publicacion = @IdPublicacion, Id_Seccion = @IdSeccion, Obs = @Obs
|
||||||
|
WHERE Id_Bobina = @IdBobina;";
|
||||||
|
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.bob_StockBobinas_H
|
||||||
|
(Id_Bobina, Id_TipoBobina, NroBobina, Peso, Id_Planta, Id_EstadoBobina, Remito, FechaRemito, FechaEstado, Id_Publicacion, Id_Seccion, Obs, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdBobinaHist, @IdTipoBobinaHist, @NroBobinaHist, @PesoHist, @IdPlantaHist, @IdEstadoBobinaHist, @RemitoHist, @FechaRemitoHist, @FechaEstadoHist, @IdPublicacionHist, @IdSeccionHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdBobinaHist = actual.IdBobina, IdTipoBobinaHist = actual.IdTipoBobina, NroBobinaHist = actual.NroBobina, PesoHist = actual.Peso,
|
||||||
|
IdPlantaHist = actual.IdPlanta, IdEstadoBobinaHist = actual.IdEstadoBobina, RemitoHist = actual.Remito, FechaRemitoHist = actual.FechaRemito,
|
||||||
|
FechaEstadoHist = actual.FechaEstado, IdPublicacionHist = actual.IdPublicacion, IdSeccionHist = actual.IdSeccion, ObsHist = actual.Obs,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = tipoMod // "Actualizada" o "Estado Cambiado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, bobinaAActualizar, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idBobina, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
// Normalmente, una bobina en stock no se "elimina" físicamente si ya tuvo movimientos.
|
||||||
|
// Este método sería para borrar un ingreso erróneo que aún esté "Disponible".
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<StockBobina>(
|
||||||
|
@"SELECT Id_Bobina AS IdBobina, Id_TipoBobina AS IdTipoBobina, NroBobina, Peso,
|
||||||
|
Id_Planta AS IdPlanta, Id_EstadoBobina AS IdEstadoBobina, Remito, FechaRemito,
|
||||||
|
FechaEstado, Id_Publicacion AS IdPublicacion, Id_Seccion AS IdSeccion, Obs
|
||||||
|
FROM dbo.bob_StockBobinas WHERE Id_Bobina = @IdBobinaParam",
|
||||||
|
new { IdBobinaParam = idBobina }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Bobina no encontrada para eliminar.");
|
||||||
|
|
||||||
|
// VALIDACIÓN IMPORTANTE: Solo permitir borrar si está en estado "Disponible" (o el que se defina)
|
||||||
|
if (actual.IdEstadoBobina != 1) // Asumiendo 1 = Disponible
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Intento de eliminar bobina {IdBobina} que no está en estado 'Disponible'. Estado actual: {EstadoActual}", idBobina, actual.IdEstadoBobina);
|
||||||
|
// No lanzar excepción para que la transacción no falle si es una validación de negocio
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.bob_StockBobinas WHERE Id_Bobina = @IdBobinaParam";
|
||||||
|
const string sqlInsertHistorico = @"
|
||||||
|
INSERT INTO dbo.bob_StockBobinas_H
|
||||||
|
(Id_Bobina, Id_TipoBobina, NroBobina, Peso, Id_Planta, Id_EstadoBobina, Remito, FechaRemito, FechaEstado, Id_Publicacion, Id_Seccion, Obs, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdBobinaHist, @IdTipoBobinaHist, @NroBobinaHist, @PesoHist, @IdPlantaHist, @IdEstadoBobinaHist, @RemitoHist, @FechaRemitoHist, @FechaEstadoHist, @IdPublicacionHist, @IdSeccionHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||||
|
{
|
||||||
|
IdBobinaHist = actual.IdBobina, IdTipoBobinaHist = actual.IdTipoBobina, NroBobinaHist = actual.NroBobina, PesoHist = actual.Peso,
|
||||||
|
IdPlantaHist = actual.IdPlanta, IdEstadoBobinaHist = actual.IdEstadoBobina, RemitoHist = actual.Remito, FechaRemitoHist = actual.FechaRemito,
|
||||||
|
FechaEstadoHist = actual.FechaEstado, IdPublicacionHist = actual.IdPublicacion, IdSeccionHist = actual.IdSeccion, ObsHist = actual.Obs,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdBobinaParam = idBobina }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
namespace GestionIntegral.Api.Models.Distribucion
|
||||||
|
{
|
||||||
|
public class EntradaSalidaDist // Corresponde a dist_EntradasSalidas
|
||||||
|
{
|
||||||
|
public int IdParte { get; set; }
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public int IdDistribuidor { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public string TipoMovimiento { get; set; } = string.Empty; // "Salida" o "Entrada"
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
public int Remito { get; set; } // Número de remito
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
public int IdPrecio { get; set; } // FK a dist_Precios
|
||||||
|
public int IdRecargo { get; set; } // FK a dist_RecargoZona (puede ser 0 si no aplica)
|
||||||
|
public int IdPorcentaje { get; set; } // FK a dist_PorcPago (puede ser 0 si no aplica)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
namespace GestionIntegral.Api.Models.Distribucion
|
||||||
|
{
|
||||||
|
public class EntradaSalidaDistHistorico // Corresponde a dist_EntradasSalidas_H
|
||||||
|
{
|
||||||
|
public int Id_Parte { get; set; } // Coincide con columna en _H
|
||||||
|
public int Id_Publicacion { get; set; }
|
||||||
|
public int Id_Distribuidor { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public string TipoMovimiento { get; set; } = string.Empty;
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
public int Remito { get; set; }
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
public int Id_Precio { get; set; }
|
||||||
|
public int Id_Recargo { get; set; }
|
||||||
|
public int Id_Porcentaje { get; set; }
|
||||||
|
public int Id_Usuario { get; set; }
|
||||||
|
public DateTime FechaMod { get; set; }
|
||||||
|
public string TipoMod { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Models.Distribucion
|
||||||
|
{
|
||||||
|
public class SalidaOtroDestino
|
||||||
|
{
|
||||||
|
public int IdParte { get; set; } // Id_Parte (PK, Identity)
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public int IdDestino { get; set; } // FK a dist_dtOtrosDestinos
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Models.Distribucion
|
||||||
|
{
|
||||||
|
public class SalidaOtroDestinoHistorico // Corresponde a dist_SalidasOtrosDestinos_H
|
||||||
|
{
|
||||||
|
// No hay PK autoincremental explícita en el script de _H
|
||||||
|
public int Id_Parte { get; set; } // Coincide con columna en _H
|
||||||
|
public int Id_Publicacion { get; set; }
|
||||||
|
public int Id_Destino { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
|
||||||
|
// Auditoría
|
||||||
|
public int Id_Usuario { get; set; }
|
||||||
|
public DateTime FechaMod { get; set; }
|
||||||
|
public string TipoMod { get; set; } = string.Empty; // "Creada", "Actualizada", "Eliminada"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class CreateEntradaSalidaDistDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int IdDistribuidor { get; set; }
|
||||||
|
[Required]
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
[Required]
|
||||||
|
[RegularExpression("^(Salida|Entrada)$", ErrorMessage = "Tipo de movimiento debe ser 'Salida' o 'Entrada'.")]
|
||||||
|
public string TipoMovimiento { get; set; } = string.Empty;
|
||||||
|
[Required, Range(1, int.MaxValue)]
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
[Required, Range(1, int.MaxValue)] // Asumiendo que el remito es un número > 0
|
||||||
|
public int Remito { get; set; }
|
||||||
|
[StringLength(150)]
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
// IdPrecio, IdRecargo, IdPorcentaje se determinarán en el backend.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class CreatePorcMonCanillaDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El canillita es obligatorio.")]
|
||||||
|
[Range(1, int.MaxValue, ErrorMessage = "Debe seleccionar un canillita válido.")]
|
||||||
|
public int IdCanilla { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La fecha de Vigencia Desde es obligatoria.")]
|
||||||
|
public DateTime VigenciaD { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El valor (porcentaje o monto) es obligatorio.")]
|
||||||
|
[Range(0, double.MaxValue, ErrorMessage = "El valor debe ser un monto positivo.")] // double.MaxValue para permitir porcentajes > 100 si fuera necesario, ajustar
|
||||||
|
public decimal PorcMon { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public bool EsPorcentaje { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class CreatePorcPagoDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El distribuidor es obligatorio.")]
|
||||||
|
[Range(1, int.MaxValue, ErrorMessage = "Debe seleccionar un distribuidor válido.")]
|
||||||
|
public int IdDistribuidor { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La fecha de Vigencia Desde es obligatoria.")]
|
||||||
|
public DateTime VigenciaD { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El porcentaje es obligatorio.")]
|
||||||
|
[Range(0, 100, ErrorMessage = "El porcentaje debe estar entre 0 y 100.")] // Asumiendo que es un porcentaje
|
||||||
|
public decimal Porcentaje { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class CreatePubliSeccionDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El nombre de la sección es obligatorio.")]
|
||||||
|
[StringLength(100)]
|
||||||
|
public string Nombre { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool Estado { get; set; } = true; // Por defecto activa al crear
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class CreateRecargoZonaDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "El ID de la publicación es obligatorio.")]
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La zona es obligatoria.")]
|
||||||
|
[Range(1, int.MaxValue, ErrorMessage = "Debe seleccionar una zona válida.")]
|
||||||
|
public int IdZona { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La fecha de Vigencia Desde es obligatoria.")]
|
||||||
|
public DateTime VigenciaD { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El valor del recargo es obligatorio.")]
|
||||||
|
[Range(0, 999999.99, ErrorMessage = "El valor del recargo debe ser un monto positivo.")]
|
||||||
|
public decimal Valor { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class CreateSalidaOtroDestinoDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int IdDestino { get; set; }
|
||||||
|
[Required]
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
[Required, Range(1, int.MaxValue)]
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
[StringLength(150)]
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class EntradaSalidaDistDto
|
||||||
|
{
|
||||||
|
public int IdParte { get; set; }
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public string NombrePublicacion { get; set; } = string.Empty;
|
||||||
|
public string NombreEmpresaPublicacion { get; set; } = string.Empty; // Para identificar la empresa dueña del saldo
|
||||||
|
public int IdEmpresaPublicacion { get; set; } // Para afectar el saldo correcto
|
||||||
|
public int IdDistribuidor { get; set; }
|
||||||
|
public string NombreDistribuidor { get; set; } = string.Empty;
|
||||||
|
public string Fecha { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public string TipoMovimiento { get; set; } = string.Empty; // "Salida" o "Entrada"
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
public int Remito { get; set; }
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
public decimal MontoCalculado { get; set; } // Calculado por el backend
|
||||||
|
// No exponemos Ids de Precio, Recargo, Porcentaje directamente, solo el resultado.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class PorcMonCanillaDto // Para Porcentaje/Monto de Pago de Canillitas
|
||||||
|
{
|
||||||
|
public int IdPorcMon { get; set; }
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public int IdCanilla { get; set; }
|
||||||
|
public string NomApeCanilla { get; set; } = string.Empty; // Para mostrar en UI
|
||||||
|
public string VigenciaD { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public string? VigenciaH { get; set; } // yyyy-MM-dd
|
||||||
|
public decimal PorcMon { get; set; } // Valor (puede ser % o monto)
|
||||||
|
public bool EsPorcentaje { get; set; } // True si PorcMon es un %, False si es un monto fijo
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class PorcPagoDto // Para Porcentaje de Pago de Distribuidores
|
||||||
|
{
|
||||||
|
public int IdPorcentaje { get; set; }
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
// public string NombrePublicacion { get; set; } = string.Empty; // Opcional, si se necesita en una vista general de porcentajes
|
||||||
|
public int IdDistribuidor { get; set; }
|
||||||
|
public string NombreDistribuidor { get; set; } = string.Empty; // Para mostrar en UI
|
||||||
|
public string VigenciaD { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public string? VigenciaH { get; set; } // yyyy-MM-dd
|
||||||
|
public decimal Porcentaje { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class PubliSeccionDto
|
||||||
|
{
|
||||||
|
public int IdSeccion { get; set; }
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
// public string NombrePublicacion { get; set; } = string.Empty; // Opcional si se muestra en una lista global
|
||||||
|
public string Nombre { get; set; } = string.Empty; // Nombre de la sección
|
||||||
|
public bool Estado { get; set; } // Habilitada o no
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class RecargoZonaDto
|
||||||
|
{
|
||||||
|
public int IdRecargo { get; set; }
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public int IdZona { get; set; }
|
||||||
|
public string NombreZona { get; set; } = string.Empty; // Para mostrar en UI
|
||||||
|
public string VigenciaD { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public string? VigenciaH { get; set; } // yyyy-MM-dd
|
||||||
|
public decimal Valor { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class SalidaOtroDestinoDto
|
||||||
|
{
|
||||||
|
public int IdParte { get; set; }
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public string NombrePublicacion { get; set; } = string.Empty;
|
||||||
|
public int IdDestino { get; set; }
|
||||||
|
public string NombreDestino { get; set; } = string.Empty;
|
||||||
|
public string Fecha { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// La edición de una entrada/salida puede ser compleja por la afectación de saldos.
|
||||||
|
// Por ahora, podríamos permitir editar Cantidad y Observacion si el TipoMovimiento no cambia.
|
||||||
|
// Cambiar Publicacion, Distribuidor, Fecha o TipoMovimiento podría requerir anular y recrear.
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class UpdateEntradaSalidaDistDto
|
||||||
|
{
|
||||||
|
[Required, Range(1, int.MaxValue)]
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
[StringLength(150)]
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
// No se permite cambiar TipoMovimiento, Fecha, Publicacion, Distribuidor, Remito aquí.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class UpdatePorcMonCanillaDto
|
||||||
|
{
|
||||||
|
// IdPublicacion, IdCanilla y VigenciaD no deberían cambiar.
|
||||||
|
// Solo se actualiza PorcMon, EsPorcentaje y opcionalmente se cierra con VigenciaH.
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El valor (porcentaje o monto) es obligatorio.")]
|
||||||
|
[Range(0, double.MaxValue, ErrorMessage = "El valor debe ser un monto positivo.")]
|
||||||
|
public decimal PorcMon { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public bool EsPorcentaje { get; set; }
|
||||||
|
|
||||||
|
public DateTime? VigenciaH { get; set; } // Para cerrar el período
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class UpdatePorcPagoDto
|
||||||
|
{
|
||||||
|
// IdPublicacion, IdDistribuidor y VigenciaD no deberían cambiar.
|
||||||
|
// Si cambian, se considera un nuevo registro.
|
||||||
|
// Solo se actualiza el Porcentaje y opcionalmente se cierra con VigenciaH.
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El porcentaje es obligatorio.")]
|
||||||
|
[Range(0, 100, ErrorMessage = "El porcentaje debe estar entre 0 y 100.")]
|
||||||
|
public decimal Porcentaje { get; set; }
|
||||||
|
|
||||||
|
public DateTime? VigenciaH { get; set; } // Para cerrar el período
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class UpdatePubliSeccionDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "El nombre de la sección es obligatorio.")]
|
||||||
|
[StringLength(100)]
|
||||||
|
public string Nombre { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public bool Estado { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class UpdateRecargoZonaDto // Para actualizar un IdRecargo específico
|
||||||
|
{
|
||||||
|
// IdPublicacion, IdZona y VigenciaD no deberían cambiar. Si cambian, es un nuevo registro.
|
||||||
|
// Solo se actualiza Valor y opcionalmente se cierra con VigenciaH.
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El valor del recargo es obligatorio.")]
|
||||||
|
[Range(0, 999999.99, ErrorMessage = "El valor del recargo debe ser un monto positivo.")]
|
||||||
|
public decimal Valor { get; set; }
|
||||||
|
|
||||||
|
public DateTime? VigenciaH { get; set; } // Para cerrar el período
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class UpdateSalidaOtroDestinoDto
|
||||||
|
{
|
||||||
|
// IdPublicacion, IdDestino y Fecha usualmente no se cambian en una "salida" ya registrada.
|
||||||
|
// Se podría permitir cambiar cantidad y observación.
|
||||||
|
[Required, Range(1, int.MaxValue)]
|
||||||
|
public int Cantidad { get; set; }
|
||||||
|
[StringLength(150)]
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Impresion
|
||||||
|
{
|
||||||
|
public class CambiarEstadoBobinaDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int NuevoEstadoId { get; set; } // ID del nuevo estado (ej. 2 para "En Uso", 3 para "Dañada")
|
||||||
|
|
||||||
|
public int? IdPublicacion { get; set; } // Requerido si NuevoEstadoId es "En Uso"
|
||||||
|
public int? IdSeccion { get; set; } // Requerido si NuevoEstadoId es "En Uso"
|
||||||
|
|
||||||
|
[StringLength(250)]
|
||||||
|
public string? Obs { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public DateTime FechaCambioEstado { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Impresion
|
||||||
|
{
|
||||||
|
public class CreateStockBobinaDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdTipoBobina { get; set; }
|
||||||
|
[Required, StringLength(15)]
|
||||||
|
public string NroBobina { get; set; } = string.Empty;
|
||||||
|
[Required, Range(1, int.MaxValue)]
|
||||||
|
public int Peso { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int IdPlanta { get; set; }
|
||||||
|
[Required, StringLength(15)]
|
||||||
|
public string Remito { get; set; } = string.Empty;
|
||||||
|
[Required]
|
||||||
|
public DateTime FechaRemito { get; set; }
|
||||||
|
// IdEstadoBobina se setea a "Disponible" (1) en el backend
|
||||||
|
// FechaEstado se setea a FechaRemito en el backend
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Impresion
|
||||||
|
{
|
||||||
|
public class CreateTiradaRequestDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
[Required]
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int IdPlanta { get; set; }
|
||||||
|
[Required, Range(1, int.MaxValue)]
|
||||||
|
public int Ejemplares { get; set; } // Para bob_RegTiradas
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MinLength(1, ErrorMessage = "Debe especificar al menos una sección para la tirada.")]
|
||||||
|
public List<DetalleSeccionTiradaDto> Secciones { get; set; } = new List<DetalleSeccionTiradaDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Impresion
|
||||||
|
{
|
||||||
|
public class DetalleSeccionTiradaDto
|
||||||
|
{
|
||||||
|
public int IdSeccion { get; set; } // ID de la PubliSeccion
|
||||||
|
// public string NombreSeccion { get; set; } // Opcional, para mostrar en UI de creación/edición
|
||||||
|
[Required, Range(1, 1000)] // Ajustar rango según necesidad
|
||||||
|
public int CantPag { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Impresion
|
||||||
|
{
|
||||||
|
public class StockBobinaDto
|
||||||
|
{
|
||||||
|
public int IdBobina { get; set; }
|
||||||
|
public int IdTipoBobina { get; set; }
|
||||||
|
public string NombreTipoBobina { get; set; } = string.Empty;
|
||||||
|
public string NroBobina { get; set; } = string.Empty;
|
||||||
|
public int Peso { get; set; }
|
||||||
|
public int IdPlanta { get; set; }
|
||||||
|
public string NombrePlanta { get; set; } = string.Empty;
|
||||||
|
public int IdEstadoBobina { get; set; }
|
||||||
|
public string NombreEstadoBobina { get; set; } = string.Empty;
|
||||||
|
public string Remito { get; set; } = string.Empty;
|
||||||
|
public string FechaRemito { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public string? FechaEstado { get; set; } // yyyy-MM-dd
|
||||||
|
public int? IdPublicacion { get; set; }
|
||||||
|
public string? NombrePublicacion { get; set; }
|
||||||
|
public int? IdSeccion { get; set; }
|
||||||
|
public string? NombreSeccion { get; set; }
|
||||||
|
public string? Obs { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Este DTO es más complejo para mostrar la información de forma útil.
|
||||||
|
namespace GestionIntegral.Api.Dtos.Impresion
|
||||||
|
{
|
||||||
|
public class DetalleSeccionEnListadoDto
|
||||||
|
{
|
||||||
|
public int IdSeccion { get; set; }
|
||||||
|
public string NombreSeccion { get; set; } = string.Empty;
|
||||||
|
public int CantPag { get; set; }
|
||||||
|
public int IdRegPublicacionSeccion {get;set;} // Id_Tirada de bob_RegPublicaciones
|
||||||
|
}
|
||||||
|
public class TiradaDto
|
||||||
|
{
|
||||||
|
public int IdRegistroTirada { get; set; } // Id de bob_RegTiradas
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public string NombrePublicacion { get; set; } = string.Empty;
|
||||||
|
public string Fecha { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public int IdPlanta { get; set; }
|
||||||
|
public string NombrePlanta { get; set; } = string.Empty;
|
||||||
|
public int Ejemplares { get; set; }
|
||||||
|
public List<DetalleSeccionEnListadoDto> SeccionesImpresas { get; set; } = new List<DetalleSeccionEnListadoDto>();
|
||||||
|
public int TotalPaginasSumadas { get; set; } // Suma de CantPag de las secciones impresas
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// (Para editar datos básicos de una bobina *Disponible*)
|
||||||
|
// Los cambios de estado tendrán DTOs dedicados.
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Impresion
|
||||||
|
{
|
||||||
|
public class UpdateStockBobinaDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdTipoBobina { get; set; }
|
||||||
|
[Required, StringLength(15)]
|
||||||
|
public string NroBobina { get; set; } = string.Empty;
|
||||||
|
[Required, Range(1, int.MaxValue)]
|
||||||
|
public int Peso { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int IdPlanta { get; set; }
|
||||||
|
[Required, StringLength(15)]
|
||||||
|
public string Remito { get; set; } = string.Empty;
|
||||||
|
[Required]
|
||||||
|
public DateTime FechaRemito { get; set; }
|
||||||
|
// No se puede cambiar el estado desde aquí directamente
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace GestionIntegral.Api.Models.Impresion
|
||||||
|
{
|
||||||
|
public class RegPublicacionSeccion
|
||||||
|
{
|
||||||
|
public int IdTirada { get; set; } // Id_Tirada (PK, Identity en bob_RegPublicaciones)
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public int IdSeccion { get; set; }
|
||||||
|
public int CantPag { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public int IdPlanta { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace GestionIntegral.Api.Models.Impresion
|
||||||
|
{
|
||||||
|
public class RegPublicacionSeccionHistorico
|
||||||
|
{
|
||||||
|
public int Id_Tirada { get; set; } // Nombre de columna en _H
|
||||||
|
public int Id_Publicacion { get; set; }
|
||||||
|
public int Id_Seccion { get; set; }
|
||||||
|
public int CantPag { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public int Id_Planta { get; set; }
|
||||||
|
public int Id_Usuario { get; set; }
|
||||||
|
public DateTime FechaMod { get; set; }
|
||||||
|
public string TipoMod { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Backend/GestionIntegral.Api/Models/Impresion/RegTirada.cs
Normal file
11
Backend/GestionIntegral.Api/Models/Impresion/RegTirada.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace GestionIntegral.Api.Models.Impresion
|
||||||
|
{
|
||||||
|
public class RegTirada
|
||||||
|
{
|
||||||
|
public int IdRegistro { get; set; } // Id_Registro (PK, Identity)
|
||||||
|
public int Ejemplares { get; set; }
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public int IdPlanta { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace GestionIntegral.Api.Models.Impresion
|
||||||
|
{
|
||||||
|
public class RegTiradaHistorico
|
||||||
|
{
|
||||||
|
public int Id_Registro { get; set; } // Nombre de columna en _H
|
||||||
|
public int Ejemplares { get; set; }
|
||||||
|
public int Id_Publicacion { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public int Id_Planta { get; set; }
|
||||||
|
public int Id_Usuario { get; set; }
|
||||||
|
public DateTime FechaMod { get; set; }
|
||||||
|
public string TipoMod { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Backend/GestionIntegral.Api/Models/Impresion/StockBobina.cs
Normal file
20
Backend/GestionIntegral.Api/Models/Impresion/StockBobina.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Models.Impresion
|
||||||
|
{
|
||||||
|
public class StockBobina
|
||||||
|
{
|
||||||
|
public int IdBobina { get; set; } // Id_Bobina (PK, Identity)
|
||||||
|
public int IdTipoBobina { get; set; } // Id_TipoBobina (FK)
|
||||||
|
public string NroBobina { get; set; } = string.Empty; // NroBobina (varchar(15), NOT NULL)
|
||||||
|
public int Peso { get; set; } // Peso (int, NOT NULL)
|
||||||
|
public int IdPlanta { get; set; } // Id_Planta (FK)
|
||||||
|
public int IdEstadoBobina { get; set; } // Id_EstadoBobina (FK)
|
||||||
|
public string Remito { get; set; } = string.Empty; // Remito (varchar(15), NOT NULL)
|
||||||
|
public DateTime FechaRemito { get; set; } // FechaRemito (datetime2(0), NOT NULL)
|
||||||
|
public DateTime? FechaEstado { get; set; } // FechaEstado (datetime2(0), NULL)
|
||||||
|
public int? IdPublicacion { get; set; } // Id_Publicacion (FK, NULL)
|
||||||
|
public int? IdSeccion { get; set; } // Id_Seccion (FK, NULL)
|
||||||
|
public string? Obs { get; set; } // Obs (varchar(250), NULL)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Models.Impresion
|
||||||
|
{
|
||||||
|
public class StockBobinaHistorico
|
||||||
|
{
|
||||||
|
public int Id_Bobina { get; set; } // Columna en _H
|
||||||
|
public int Id_TipoBobina { get; set; }
|
||||||
|
public string NroBobina { get; set; } = string.Empty;
|
||||||
|
public int Peso { get; set; }
|
||||||
|
public int Id_Planta { get; set; }
|
||||||
|
public int Id_EstadoBobina { get; set; }
|
||||||
|
public string Remito { get; set; } = string.Empty;
|
||||||
|
public DateTime FechaRemito { get; set; }
|
||||||
|
public DateTime? FechaEstado { get; set; }
|
||||||
|
public int? Id_Publicacion { get; set; }
|
||||||
|
public int? Id_Seccion { get; set; }
|
||||||
|
public string? Obs { get; set; }
|
||||||
|
|
||||||
|
// Auditoría
|
||||||
|
public int Id_Usuario { get; set; }
|
||||||
|
public DateTime FechaMod { get; set; }
|
||||||
|
public string TipoMod { get; set; } = string.Empty; // "Ingreso", "Estado Cambiado", "Actualizacion", "Eliminada"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ using GestionIntegral.Api.Data.Repositories.Impresion;
|
|||||||
using GestionIntegral.Api.Services.Impresion;
|
using GestionIntegral.Api.Services.Impresion;
|
||||||
using GestionIntegral.Api.Services.Usuarios;
|
using GestionIntegral.Api.Services.Usuarios;
|
||||||
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -46,11 +47,24 @@ builder.Services.AddScoped<IDistribuidorService, DistribuidorService>();
|
|||||||
builder.Services.AddScoped<IPublicacionRepository, PublicacionRepository>();
|
builder.Services.AddScoped<IPublicacionRepository, PublicacionRepository>();
|
||||||
builder.Services.AddScoped<IPublicacionService, PublicacionService>();
|
builder.Services.AddScoped<IPublicacionService, PublicacionService>();
|
||||||
builder.Services.AddScoped<IRecargoZonaRepository, RecargoZonaRepository>();
|
builder.Services.AddScoped<IRecargoZonaRepository, RecargoZonaRepository>();
|
||||||
|
builder.Services.AddScoped<IRecargoZonaService, RecargoZonaService>();
|
||||||
builder.Services.AddScoped<IPorcPagoRepository, PorcPagoRepository>();
|
builder.Services.AddScoped<IPorcPagoRepository, PorcPagoRepository>();
|
||||||
|
builder.Services.AddScoped<IPorcPagoService, PorcPagoService>();
|
||||||
builder.Services.AddScoped<IPorcMonCanillaRepository, PorcMonCanillaRepository>();
|
builder.Services.AddScoped<IPorcMonCanillaRepository, PorcMonCanillaRepository>();
|
||||||
|
builder.Services.AddScoped<IPorcMonCanillaService, PorcMonCanillaService>();
|
||||||
builder.Services.AddScoped<IPubliSeccionRepository, PubliSeccionRepository>();
|
builder.Services.AddScoped<IPubliSeccionRepository, PubliSeccionRepository>();
|
||||||
|
builder.Services.AddScoped<IPubliSeccionService, PubliSeccionService>();
|
||||||
builder.Services.AddScoped<IPrecioRepository, PrecioRepository>();
|
builder.Services.AddScoped<IPrecioRepository, PrecioRepository>();
|
||||||
builder.Services.AddScoped<IPrecioService, PrecioService>();
|
builder.Services.AddScoped<IPrecioService, PrecioService>();
|
||||||
|
builder.Services.AddScoped<IStockBobinaRepository, StockBobinaRepository>();
|
||||||
|
builder.Services.AddScoped<IStockBobinaService, StockBobinaService>();
|
||||||
|
builder.Services.AddScoped<IRegTiradaRepository, RegTiradaRepository>();
|
||||||
|
builder.Services.AddScoped<IRegPublicacionSeccionRepository, RegPublicacionSeccionRepository>();
|
||||||
|
builder.Services.AddScoped<ITiradaService, TiradaService>();
|
||||||
|
builder.Services.AddScoped<ISalidaOtroDestinoRepository, SalidaOtroDestinoRepository>();
|
||||||
|
builder.Services.AddScoped<ISalidaOtroDestinoService, SalidaOtroDestinoService>();
|
||||||
|
builder.Services.AddScoped<IEntradaSalidaDistRepository, EntradaSalidaDistRepository>();
|
||||||
|
builder.Services.AddScoped<IEntradaSalidaDistService, EntradaSalidaDistService>();
|
||||||
|
|
||||||
// --- Configuración de Autenticación JWT ---
|
// --- Configuración de Autenticación JWT ---
|
||||||
var jwtSettings = builder.Configuration.GetSection("Jwt");
|
var jwtSettings = builder.Configuration.GetSection("Jwt");
|
||||||
@@ -61,6 +75,7 @@ builder.Services.AddAuthentication(options =>
|
|||||||
{
|
{
|
||||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
|
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||||
})
|
})
|
||||||
.AddJwtBearer(options =>
|
.AddJwtBearer(options =>
|
||||||
{
|
{
|
||||||
@@ -82,7 +97,7 @@ builder.Services.AddAuthentication(options =>
|
|||||||
// --- Configuración de Autorización ---
|
// --- Configuración de Autorización ---
|
||||||
builder.Services.AddAuthorization();
|
builder.Services.AddAuthorization();
|
||||||
|
|
||||||
// --- Configuración de CORS --- // <--- MOVER AQUÍ LA CONFIGURACIÓN DE SERVICIOS CORS
|
// --- Configuración de CORS --- //
|
||||||
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
@@ -101,26 +116,75 @@ builder.Services.AddCors(options =>
|
|||||||
// --- Servicios del Contenedor ---
|
// --- Servicios del Contenedor ---
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
|
||||||
|
// --- Configuración Avanzada de Swagger/OpenAPI ---
|
||||||
|
builder.Services.AddSwaggerGen(options =>
|
||||||
|
{
|
||||||
|
// Definición general del documento Swagger
|
||||||
|
options.SwaggerDoc("v1", new OpenApiInfo
|
||||||
|
{
|
||||||
|
Version = "v1",
|
||||||
|
Title = "GestionIntegral API",
|
||||||
|
Description = "API para el sistema de Gestión Integral (Migración VB.NET)",
|
||||||
|
// Puedes añadir TermsOfService, Contact, License si lo deseas
|
||||||
|
});
|
||||||
|
|
||||||
|
// (Opcional) Incluir comentarios XML si los usas en tus controladores/DTOs
|
||||||
|
// var xmlFilename = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||||
|
// options.IncludeXmlComments(System.IO.Path.Combine(AppContext.BaseDirectory, xmlFilename));
|
||||||
|
|
||||||
|
// Definición de Seguridad para JWT en Swagger UI
|
||||||
|
// Esto añade el botón "Authorize" en la UI de Swagger para poder pegar el token Bearer
|
||||||
|
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
Description = "Por favor, introduce 'Bearer' seguido de un espacio y luego tu token JWT.",
|
||||||
|
Name = "Authorization",
|
||||||
|
Type = SecuritySchemeType.ApiKey, // Usar ApiKey para el formato Bearer simple
|
||||||
|
Scheme = "Bearer" // Esquema a usar
|
||||||
|
});
|
||||||
|
|
||||||
|
options.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||||
|
{
|
||||||
|
{
|
||||||
|
new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Type = ReferenceType.SecurityScheme,
|
||||||
|
Id = "Bearer" // Debe coincidir con el Id definido en AddSecurityDefinition
|
||||||
|
},
|
||||||
|
Scheme = "oauth2", // Aunque el scheme sea ApiKey, a veces se usa oauth2 aquí para la UI
|
||||||
|
Name = "Bearer",
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
},
|
||||||
|
new List<string>() // Lista de scopes (vacía si no usas scopes OAuth2 complejos)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// --- Fin Configuración Swagger ---
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// --- Configuración del Pipeline HTTP ---
|
// --- Configuración del Pipeline HTTP ---
|
||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseSwagger();
|
app.UseSwagger(); // Habilita el middleware para servir el JSON de Swagger
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI(options => // Habilita el middleware para servir la UI de Swagger
|
||||||
|
{
|
||||||
|
options.SwaggerEndpoint("/swagger/v1/swagger.json", "GestionIntegral API v1");
|
||||||
|
// options.RoutePrefix = string.Empty; // Para servir la UI de Swagger en la raíz (ej: http://localhost:5183/)
|
||||||
|
// Comenta esto si prefieres /swagger (ej: http://localhost:5183/swagger/)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ¡¡¡NO USAR UseHttpsRedirection si tu API corre en HTTP!!!
|
// ¡¡¡NO USAR UseHttpsRedirection si tu API corre en HTTP!!!
|
||||||
// Comenta o elimina la siguiente línea si SÓLO usas http://localhost:5183
|
// Comenta o elimina la siguiente línea si SÓLO usas http://localhost:5183
|
||||||
// app.UseHttpsRedirection(); // <--- COMENTAR/ELIMINAR SI NO USAS HTTPS EN API
|
// app.UseHttpsRedirection();
|
||||||
|
|
||||||
// --- Aplicar CORS ANTES de Autenticación/Autorización ---
|
|
||||||
app.UseCors(MyAllowSpecificOrigins);
|
app.UseCors(MyAllowSpecificOrigins);
|
||||||
// --- Fin aplicar CORS ---
|
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication(); // Debe ir ANTES de UseAuthorization
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|||||||
@@ -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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+daf84d27081c399bf1dbd5db88606c4b562cee46")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+b6ba52f074807c7a2fddc76ab3cc2c45c446c1f8")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","cInNtDBnkQU/n2nz4dl\u002BN2Fu06kTT6IORBeDdunxooU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","kULolnJcJq9du0a0dBwZaPVupTEFX15sai6mOONU2qk="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","cInNtDBnkQU/n2nz4dl\u002BN2Fu06kTT6IORBeDdunxooU="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","kULolnJcJq9du0a0dBwZaPVupTEFX15sai6mOONU2qk="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, RadioGroup, FormControlLabel, Radio
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { EntradaSalidaDistDto } from '../../../models/dtos/Distribucion/EntradaSalidaDistDto';
|
||||||
|
import type { CreateEntradaSalidaDistDto } from '../../../models/dtos/Distribucion/CreateEntradaSalidaDistDto';
|
||||||
|
import type { UpdateEntradaSalidaDistDto } from '../../../models/dtos/Distribucion/UpdateEntradaSalidaDistDto';
|
||||||
|
import type { PublicacionDto } from '../../../models/dtos/Distribucion/PublicacionDto';
|
||||||
|
import type { DistribuidorDto } from '../../../models/dtos/Distribucion/DistribuidorDto';
|
||||||
|
import publicacionService from '../../../services/Distribucion/publicacionService';
|
||||||
|
import distribuidorService from '../../../services/Distribucion/distribuidorService';
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 550 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface EntradaSalidaDistFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreateEntradaSalidaDistDto | UpdateEntradaSalidaDistDto, idParte?: number) => Promise<void>;
|
||||||
|
initialData?: EntradaSalidaDistDto | null; // Para editar
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EntradaSalidaDistFormModal: React.FC<EntradaSalidaDistFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
|
||||||
|
const [idDistribuidor, setIdDistribuidor] = useState<number | string>('');
|
||||||
|
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [tipoMovimiento, setTipoMovimiento] = useState<'Salida' | 'Entrada'>('Salida');
|
||||||
|
const [cantidad, setCantidad] = useState<string>('');
|
||||||
|
const [remito, setRemito] = useState<string>('');
|
||||||
|
const [observacion, setObservacion] = useState('');
|
||||||
|
|
||||||
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
|
const [distribuidores, setDistribuidores] = useState<DistribuidorDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDropdownData = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const [pubsData, distData] = await Promise.all([
|
||||||
|
publicacionService.getAllPublicaciones(undefined, undefined, true), // Solo habilitadas
|
||||||
|
distribuidorService.getAllDistribuidores()
|
||||||
|
]);
|
||||||
|
setPublicaciones(pubsData);
|
||||||
|
setDistribuidores(distData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar datos para dropdowns", error);
|
||||||
|
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar datos necesarios.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
fetchDropdownData();
|
||||||
|
setIdPublicacion(initialData?.idPublicacion || '');
|
||||||
|
setIdDistribuidor(initialData?.idDistribuidor || '');
|
||||||
|
setFecha(initialData?.fecha || new Date().toISOString().split('T')[0]);
|
||||||
|
setTipoMovimiento(initialData?.tipoMovimiento || 'Salida');
|
||||||
|
setCantidad(initialData?.cantidad?.toString() || '');
|
||||||
|
setRemito(initialData?.remito?.toString() || '');
|
||||||
|
setObservacion(initialData?.observacion || '');
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idPublicacion) errors.idPublicacion = 'Seleccione una publicación.';
|
||||||
|
if (!idDistribuidor) errors.idDistribuidor = 'Seleccione un distribuidor.';
|
||||||
|
if (!fecha.trim()) errors.fecha = 'La fecha es obligatoria.';
|
||||||
|
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fecha)) errors.fecha = 'Formato de fecha inválido.';
|
||||||
|
if (!tipoMovimiento) errors.tipoMovimiento = 'Seleccione un tipo de movimiento.';
|
||||||
|
if (!cantidad.trim() || isNaN(parseInt(cantidad)) || parseInt(cantidad) <= 0) {
|
||||||
|
errors.cantidad = 'La cantidad debe ser un número positivo.';
|
||||||
|
}
|
||||||
|
if (!isEditing && (!remito.trim() || isNaN(parseInt(remito)) || parseInt(remito) <= 0)) {
|
||||||
|
errors.remito = 'El Nro. Remito es obligatorio y debe ser un número positivo.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (fieldName: string) => {
|
||||||
|
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
const dataToSubmit: UpdateEntradaSalidaDistDto = {
|
||||||
|
cantidad: parseInt(cantidad, 10),
|
||||||
|
observacion: observacion || undefined,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit, initialData.idParte);
|
||||||
|
} else {
|
||||||
|
const dataToSubmit: CreateEntradaSalidaDistDto = {
|
||||||
|
idPublicacion: Number(idPublicacion),
|
||||||
|
idDistribuidor: Number(idDistribuidor),
|
||||||
|
fecha,
|
||||||
|
tipoMovimiento,
|
||||||
|
cantidad: parseInt(cantidad, 10),
|
||||||
|
remito: parseInt(remito, 10),
|
||||||
|
observacion: observacion || undefined,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de EntradaSalidaDistFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Movimiento Distribuidor' : 'Registrar Movimiento Distribuidor'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idPublicacion} required>
|
||||||
|
<InputLabel id="publicacion-esd-select-label">Publicación</InputLabel>
|
||||||
|
<Select labelId="publicacion-esd-select-label" label="Publicación" value={idPublicacion}
|
||||||
|
onChange={(e) => {setIdPublicacion(e.target.value as number); handleInputChange('idPublicacion');}}
|
||||||
|
disabled={loading || loadingDropdowns || isEditing}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{publicaciones.map((p) => (<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre} ({p.nombreEmpresa})</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPublicacion && <Typography color="error" variant="caption">{localErrors.idPublicacion}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idDistribuidor} required>
|
||||||
|
<InputLabel id="distribuidor-esd-select-label">Distribuidor</InputLabel>
|
||||||
|
<Select labelId="distribuidor-esd-select-label" label="Distribuidor" value={idDistribuidor}
|
||||||
|
onChange={(e) => {setIdDistribuidor(e.target.value as number); handleInputChange('idDistribuidor');}}
|
||||||
|
disabled={loading || loadingDropdowns || isEditing}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{distribuidores.map((d) => (<MenuItem key={d.idDistribuidor} value={d.idDistribuidor}>{d.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idDistribuidor && <Typography color="error" variant="caption">{localErrors.idDistribuidor}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField label="Fecha Movimiento" type="date" value={fecha} required
|
||||||
|
onChange={(e) => {setFecha(e.target.value); handleInputChange('fecha');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.fecha} helperText={localErrors.fecha || ''}
|
||||||
|
disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormControl component="fieldset" margin="dense" error={!!localErrors.tipoMovimiento} required>
|
||||||
|
<Typography component="legend" variant="body2" sx={{mb:0.5}}>Tipo de Movimiento</Typography>
|
||||||
|
<RadioGroup row value={tipoMovimiento} onChange={(e) => {setTipoMovimiento(e.target.value as 'Salida' | 'Entrada'); handleInputChange('tipoMovimiento');}} >
|
||||||
|
<FormControlLabel value="Salida" control={<Radio size="small"/>} label="Salida (a Distribuidor)" disabled={loading || isEditing}/>
|
||||||
|
<FormControlLabel value="Entrada" control={<Radio size="small"/>} label="Entrada (de Distribuidor)" disabled={loading || isEditing}/>
|
||||||
|
</RadioGroup>
|
||||||
|
{localErrors.tipoMovimiento && <Typography color="error" variant="caption">{localErrors.tipoMovimiento}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField label="Nro. Remito" type="number" value={remito} required={!isEditing}
|
||||||
|
onChange={(e) => {setRemito(e.target.value); handleInputChange('remito');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.remito} helperText={localErrors.remito || ''}
|
||||||
|
disabled={loading || isEditing} inputProps={{min:1}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField label="Cantidad" type="number" value={cantidad} required
|
||||||
|
onChange={(e) => {setCantidad(e.target.value); handleInputChange('cantidad');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.cantidad} helperText={localErrors.cantidad || ''}
|
||||||
|
disabled={loading} inputProps={{min:1}}
|
||||||
|
/>
|
||||||
|
<TextField label="Observación (Opcional)" value={observacion}
|
||||||
|
onChange={(e) => setObservacion(e.target.value)}
|
||||||
|
margin="dense" fullWidth multiline rows={2} disabled={loading}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Registrar Movimiento')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EntradaSalidaDistFormModal;
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, FormControlLabel, Checkbox, InputAdornment
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PorcMonCanillaDto } from '../../../models/dtos/Distribucion/PorcMonCanillaDto';
|
||||||
|
import type { CreatePorcMonCanillaDto } from '../../../models/dtos/Distribucion/CreatePorcMonCanillaDto';
|
||||||
|
import type { UpdatePorcMonCanillaDto } from '../../../models/dtos/Distribucion/UpdatePorcMonCanillaDto';
|
||||||
|
import type { CanillaDto } from '../../../models/dtos/Distribucion/CanillaDto'; // Para el dropdown
|
||||||
|
import canillaService from '../../../services/Distribucion/canillaService'; // Para cargar canillitas
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 550 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PorcMonCanillaFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreatePorcMonCanillaDto | UpdatePorcMonCanillaDto, idPorcMon?: number) => Promise<void>;
|
||||||
|
idPublicacion: number;
|
||||||
|
initialData?: PorcMonCanillaDto | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PorcMonCanillaFormModal: React.FC<PorcMonCanillaFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
idPublicacion,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idCanilla, setIdCanilla] = useState<number | string>('');
|
||||||
|
const [vigenciaD, setVigenciaD] = useState('');
|
||||||
|
const [vigenciaH, setVigenciaH] = useState('');
|
||||||
|
const [porcMon, setPorcMon] = useState<string>('');
|
||||||
|
const [esPorcentaje, setEsPorcentaje] = useState(true); // Default a porcentaje
|
||||||
|
|
||||||
|
const [canillitas, setCanillitas] = useState<CanillaDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingCanillitas, setLoadingCanillitas] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCanillitas = async () => {
|
||||||
|
setLoadingCanillitas(true);
|
||||||
|
try {
|
||||||
|
// Aquí podríamos querer filtrar solo canillitas accionistas si la regla de negocio lo impone
|
||||||
|
// o todos los activos. Por ahora, todos los activos.
|
||||||
|
const data = await canillaService.getAllCanillas(undefined, undefined, true);
|
||||||
|
setCanillitas(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar canillitas", error);
|
||||||
|
setLocalErrors(prev => ({...prev, canillitas: 'Error al cargar canillitas.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingCanillitas(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
fetchCanillitas();
|
||||||
|
setIdCanilla(initialData?.idCanilla || '');
|
||||||
|
setVigenciaD(initialData?.vigenciaD || '');
|
||||||
|
setVigenciaH(initialData?.vigenciaH || '');
|
||||||
|
setPorcMon(initialData?.porcMon?.toString() || '');
|
||||||
|
setEsPorcentaje(initialData ? initialData.esPorcentaje : true);
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idCanilla) errors.idCanilla = 'Debe seleccionar un canillita.';
|
||||||
|
if (!isEditing && !vigenciaD.trim()) {
|
||||||
|
errors.vigenciaD = 'La Vigencia Desde es obligatoria.';
|
||||||
|
} else if (vigenciaD.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaD)) {
|
||||||
|
errors.vigenciaD = 'Formato de Vigencia Desde inválido (YYYY-MM-DD).';
|
||||||
|
}
|
||||||
|
if (vigenciaH.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaH)) {
|
||||||
|
errors.vigenciaH = 'Formato de Vigencia Hasta inválido (YYYY-MM-DD).';
|
||||||
|
} else if (vigenciaH.trim() && vigenciaD.trim() && new Date(vigenciaH) < new Date(vigenciaD)) {
|
||||||
|
errors.vigenciaH = 'Vigencia Hasta no puede ser anterior a Vigencia Desde.';
|
||||||
|
}
|
||||||
|
if (!porcMon.trim()) errors.porcMon = 'El valor es obligatorio.';
|
||||||
|
else {
|
||||||
|
const numVal = parseFloat(porcMon);
|
||||||
|
if (isNaN(numVal) || numVal < 0) {
|
||||||
|
errors.porcMon = 'El valor debe ser un número positivo.';
|
||||||
|
} else if (esPorcentaje && numVal > 100) {
|
||||||
|
errors.porcMon = 'El porcentaje no puede ser mayor a 100.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (fieldName: string) => {
|
||||||
|
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const valorNum = parseFloat(porcMon);
|
||||||
|
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
const dataToSubmit: UpdatePorcMonCanillaDto = {
|
||||||
|
porcMon: valorNum,
|
||||||
|
esPorcentaje,
|
||||||
|
vigenciaH: vigenciaH.trim() ? vigenciaH : null,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit, initialData.idPorcMon);
|
||||||
|
} else {
|
||||||
|
const dataToSubmit: CreatePorcMonCanillaDto = {
|
||||||
|
idPublicacion,
|
||||||
|
idCanilla: Number(idCanilla),
|
||||||
|
vigenciaD,
|
||||||
|
porcMon: valorNum,
|
||||||
|
esPorcentaje,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de PorcMonCanillaFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Porcentaje/Monto Canillita' : 'Agregar Nuevo Porcentaje/Monto Canillita'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idCanilla}>
|
||||||
|
<InputLabel id="canilla-pmc-select-label" required>Canillita</InputLabel>
|
||||||
|
<Select labelId="canilla-pmc-select-label" label="Canillita" value={idCanilla}
|
||||||
|
onChange={(e) => {setIdCanilla(e.target.value as number); handleInputChange('idCanilla');}}
|
||||||
|
disabled={loading || loadingCanillitas || isEditing}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione un canillita</em></MenuItem>
|
||||||
|
{canillitas.map((c) => (<MenuItem key={c.idCanilla} value={c.idCanilla}>{`${c.nomApe} (Leg: ${c.legajo || 'S/L'})`}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idCanilla && <Typography color="error" variant="caption">{localErrors.idCanilla}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Vigencia Desde" type="date" value={vigenciaD} required={!isEditing}
|
||||||
|
onChange={(e) => {setVigenciaD(e.target.value); handleInputChange('vigenciaD');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.vigenciaD} helperText={localErrors.vigenciaD || ''}
|
||||||
|
disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing}
|
||||||
|
/>
|
||||||
|
{isEditing && (
|
||||||
|
<TextField label="Vigencia Hasta (Opcional)" type="date" value={vigenciaH}
|
||||||
|
onChange={(e) => {setVigenciaH(e.target.value); handleInputChange('vigenciaH');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.vigenciaH} helperText={localErrors.vigenciaH || ''}
|
||||||
|
disabled={loading} InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TextField label="Valor" type="number" value={porcMon} required
|
||||||
|
onChange={(e) => {setPorcMon(e.target.value); handleInputChange('porcMon');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.porcMon} helperText={localErrors.porcMon || ''}
|
||||||
|
disabled={loading}
|
||||||
|
InputProps={{ startAdornment: esPorcentaje ? undefined : <InputAdornment position="start">$</InputAdornment>,
|
||||||
|
endAdornment: esPorcentaje ? <InputAdornment position="end">%</InputAdornment> : undefined }}
|
||||||
|
inputProps={{ step: "0.01", lang:"es-AR" }}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox checked={esPorcentaje} onChange={(e) => setEsPorcentaje(e.target.checked)} disabled={loading}/>}
|
||||||
|
label="Es Porcentaje (si no, es Monto Fijo)" sx={{mt:1}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.canillitas && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.canillitas}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button type="submit" variant="contained" disabled={loading || loadingCanillitas}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Agregar')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PorcMonCanillaFormModal;
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, InputAdornment
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PorcPagoDto } from '../../../models/dtos/Distribucion/PorcPagoDto';
|
||||||
|
import type { CreatePorcPagoDto } from '../../../models/dtos/Distribucion/CreatePorcPagoDto';
|
||||||
|
import type { UpdatePorcPagoDto } from '../../../models/dtos/Distribucion/UpdatePorcPagoDto';
|
||||||
|
import type { DistribuidorDto } from '../../../models/dtos/Distribucion/DistribuidorDto';
|
||||||
|
import distribuidorService from '../../../services/Distribucion/distribuidorService'; // Para cargar distribuidores
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 500 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PorcPagoFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreatePorcPagoDto | UpdatePorcPagoDto, idPorcentaje?: number) => Promise<void>;
|
||||||
|
idPublicacion: number;
|
||||||
|
initialData?: PorcPagoDto | null; // Para editar
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PorcPagoFormModal: React.FC<PorcPagoFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
idPublicacion,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idDistribuidor, setIdDistribuidor] = useState<number | string>('');
|
||||||
|
const [vigenciaD, setVigenciaD] = useState(''); // "yyyy-MM-dd"
|
||||||
|
const [vigenciaH, setVigenciaH] = useState(''); // "yyyy-MM-dd"
|
||||||
|
const [porcentaje, setPorcentaje] = useState<string>('');
|
||||||
|
|
||||||
|
const [distribuidores, setDistribuidores] = useState<DistribuidorDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingDistribuidores, setLoadingDistribuidores] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDistribuidores = async () => {
|
||||||
|
setLoadingDistribuidores(true);
|
||||||
|
try {
|
||||||
|
const data = await distribuidorService.getAllDistribuidores();
|
||||||
|
setDistribuidores(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar distribuidores", error);
|
||||||
|
setLocalErrors(prev => ({...prev, distribuidores: 'Error al cargar distribuidores.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDistribuidores(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
fetchDistribuidores();
|
||||||
|
setIdDistribuidor(initialData?.idDistribuidor || '');
|
||||||
|
setVigenciaD(initialData?.vigenciaD || '');
|
||||||
|
setVigenciaH(initialData?.vigenciaH || '');
|
||||||
|
setPorcentaje(initialData?.porcentaje?.toString() || '');
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idDistribuidor) errors.idDistribuidor = 'Debe seleccionar un distribuidor.';
|
||||||
|
if (!isEditing && !vigenciaD.trim()) {
|
||||||
|
errors.vigenciaD = 'La Vigencia Desde es obligatoria.';
|
||||||
|
} else if (vigenciaD.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaD)) {
|
||||||
|
errors.vigenciaD = 'Formato de Vigencia Desde inválido (YYYY-MM-DD).';
|
||||||
|
}
|
||||||
|
if (vigenciaH.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaH)) {
|
||||||
|
errors.vigenciaH = 'Formato de Vigencia Hasta inválido (YYYY-MM-DD).';
|
||||||
|
} else if (vigenciaH.trim() && vigenciaD.trim() && new Date(vigenciaH) < new Date(vigenciaD)) {
|
||||||
|
errors.vigenciaH = 'Vigencia Hasta no puede ser anterior a Vigencia Desde.';
|
||||||
|
}
|
||||||
|
if (!porcentaje.trim()) errors.porcentaje = 'El porcentaje es obligatorio.';
|
||||||
|
else {
|
||||||
|
const porcNum = parseFloat(porcentaje);
|
||||||
|
if (isNaN(porcNum) || porcNum < 0 || porcNum > 100) {
|
||||||
|
errors.porcentaje = 'El porcentaje debe ser un número entre 0 y 100.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (fieldName: string) => {
|
||||||
|
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const porcentajeNum = parseFloat(porcentaje);
|
||||||
|
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
const dataToSubmit: UpdatePorcPagoDto = {
|
||||||
|
porcentaje: porcentajeNum,
|
||||||
|
vigenciaH: vigenciaH.trim() ? vigenciaH : null,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit, initialData.idPorcentaje);
|
||||||
|
} else {
|
||||||
|
const dataToSubmit: CreatePorcPagoDto = {
|
||||||
|
idPublicacion,
|
||||||
|
idDistribuidor: Number(idDistribuidor),
|
||||||
|
vigenciaD,
|
||||||
|
porcentaje: porcentajeNum,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de PorcPagoFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Porcentaje de Pago' : 'Agregar Nuevo Porcentaje de Pago'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idDistribuidor}>
|
||||||
|
<InputLabel id="distribuidor-porc-select-label" required>Distribuidor</InputLabel>
|
||||||
|
<Select labelId="distribuidor-porc-select-label" label="Distribuidor" value={idDistribuidor}
|
||||||
|
onChange={(e) => {setIdDistribuidor(e.target.value as number); handleInputChange('idDistribuidor');}}
|
||||||
|
disabled={loading || loadingDistribuidores || isEditing} // Distribuidor no se edita
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione un distribuidor</em></MenuItem>
|
||||||
|
{distribuidores.map((d) => (<MenuItem key={d.idDistribuidor} value={d.idDistribuidor}>{d.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idDistribuidor && <Typography color="error" variant="caption">{localErrors.idDistribuidor}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Vigencia Desde" type="date" value={vigenciaD} required={!isEditing}
|
||||||
|
onChange={(e) => {setVigenciaD(e.target.value); handleInputChange('vigenciaD');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.vigenciaD} helperText={localErrors.vigenciaD || ''}
|
||||||
|
disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing}
|
||||||
|
/>
|
||||||
|
{isEditing && (
|
||||||
|
<TextField label="Vigencia Hasta (Opcional)" type="date" value={vigenciaH}
|
||||||
|
onChange={(e) => {setVigenciaH(e.target.value); handleInputChange('vigenciaH');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.vigenciaH} helperText={localErrors.vigenciaH || ''}
|
||||||
|
disabled={loading} InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TextField label="Porcentaje" type="number" value={porcentaje} required
|
||||||
|
onChange={(e) => {setPorcentaje(e.target.value); handleInputChange('porcentaje');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.porcentaje} helperText={localErrors.porcentaje || ''}
|
||||||
|
disabled={loading}
|
||||||
|
InputProps={{ endAdornment: <InputAdornment position="end">%</InputAdornment> }}
|
||||||
|
inputProps={{ step: "0.01", lang:"es-AR" }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.distribuidores && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.distribuidores}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button type="submit" variant="contained" disabled={loading || loadingDistribuidores}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Agregar Porcentaje')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PorcPagoFormModal;
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControlLabel, Checkbox
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PubliSeccionDto } from '../../../models/dtos/Distribucion/PubliSeccionDto';
|
||||||
|
import type { CreatePubliSeccionDto } from '../../../models/dtos/Distribucion/CreatePubliSeccionDto';
|
||||||
|
import type { UpdatePubliSeccionDto } from '../../../models/dtos/Distribucion/UpdatePubliSeccionDto';
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 450 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PubliSeccionFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreatePubliSeccionDto | UpdatePubliSeccionDto, idSeccion?: number) => Promise<void>;
|
||||||
|
idPublicacion: number; // Siempre necesario para la creación
|
||||||
|
initialData?: PubliSeccionDto | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PubliSeccionFormModal: React.FC<PubliSeccionFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
idPublicacion,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [nombre, setNombre] = useState('');
|
||||||
|
const [estado, setEstado] = useState(true); // Default a activa
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [localErrorNombre, setLocalErrorNombre] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setNombre(initialData?.nombre || '');
|
||||||
|
setEstado(initialData ? initialData.estado : true);
|
||||||
|
setLocalErrorNombre(null);
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
let isValid = true;
|
||||||
|
if (!nombre.trim()) {
|
||||||
|
setLocalErrorNombre('El nombre de la sección es obligatorio.');
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = () => {
|
||||||
|
if (localErrorNombre) setLocalErrorNombre(null);
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
const dataToSubmit: UpdatePubliSeccionDto = { nombre, estado };
|
||||||
|
await onSubmit(dataToSubmit, initialData.idSeccion);
|
||||||
|
} else {
|
||||||
|
const dataToSubmit: CreatePubliSeccionDto = {
|
||||||
|
idPublicacion, // Viene de props
|
||||||
|
nombre,
|
||||||
|
estado
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de PubliSeccionFormModal:", error);
|
||||||
|
// El error de API se maneja en la página
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Sección' : 'Agregar Nueva Sección'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<TextField label="Nombre de la Sección" value={nombre} required
|
||||||
|
onChange={(e) => {setNombre(e.target.value); handleInputChange();}}
|
||||||
|
margin="dense" fullWidth error={!!localErrorNombre} helperText={localErrorNombre || ''}
|
||||||
|
disabled={loading} autoFocus
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox checked={estado} onChange={(e) => setEstado(e.target.checked)} disabled={loading}/>}
|
||||||
|
label="Activa" sx={{mt:1}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button type="submit" variant="contained" disabled={loading}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Agregar Sección')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PubliSeccionFormModal;
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, InputAdornment
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { RecargoZonaDto } from '../../../models/dtos/Distribucion/RecargoZonaDto';
|
||||||
|
import type { CreateRecargoZonaDto } from '../../../models/dtos/Distribucion/CreateRecargoZonaDto';
|
||||||
|
import type { UpdateRecargoZonaDto } from '../../../models/dtos/Distribucion/UpdateRecargoZonaDto';
|
||||||
|
import type { ZonaDto } from '../../../models/dtos/Zonas/ZonaDto'; // Para el dropdown de zonas
|
||||||
|
import zonaService from '../../../services/Distribucion/zonaService'; // Para cargar zonas
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 500 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface RecargoZonaFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreateRecargoZonaDto | UpdateRecargoZonaDto, idRecargo?: number) => Promise<void>;
|
||||||
|
idPublicacion: number;
|
||||||
|
initialData?: RecargoZonaDto | null; // Para editar
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RecargoZonaFormModal: React.FC<RecargoZonaFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
idPublicacion,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idZona, setIdZona] = useState<number | string>('');
|
||||||
|
const [vigenciaD, setVigenciaD] = useState(''); // "yyyy-MM-dd"
|
||||||
|
const [vigenciaH, setVigenciaH] = useState(''); // "yyyy-MM-dd"
|
||||||
|
const [valor, setValor] = useState<string>('');
|
||||||
|
|
||||||
|
const [zonas, setZonas] = useState<ZonaDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingZonas, setLoadingZonas] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchZonas = async () => {
|
||||||
|
setLoadingZonas(true);
|
||||||
|
try {
|
||||||
|
const data = await zonaService.getAllZonas(); // Asume que devuelve zonas activas
|
||||||
|
setZonas(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar zonas", error);
|
||||||
|
setLocalErrors(prev => ({...prev, zonas: 'Error al cargar zonas.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingZonas(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
fetchZonas();
|
||||||
|
setIdZona(initialData?.idZona || '');
|
||||||
|
setVigenciaD(initialData?.vigenciaD || '');
|
||||||
|
setVigenciaH(initialData?.vigenciaH || '');
|
||||||
|
setValor(initialData?.valor?.toString() || '');
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idZona) errors.idZona = 'Debe seleccionar una zona.';
|
||||||
|
if (!isEditing && !vigenciaD.trim()) {
|
||||||
|
errors.vigenciaD = 'La Vigencia Desde es obligatoria.';
|
||||||
|
} else if (vigenciaD.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaD)) {
|
||||||
|
errors.vigenciaD = 'Formato de Vigencia Desde inválido (YYYY-MM-DD).';
|
||||||
|
}
|
||||||
|
if (vigenciaH.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaH)) {
|
||||||
|
errors.vigenciaH = 'Formato de Vigencia Hasta inválido (YYYY-MM-DD).';
|
||||||
|
} else if (vigenciaH.trim() && vigenciaD.trim() && new Date(vigenciaH) < new Date(vigenciaD)) {
|
||||||
|
errors.vigenciaH = 'Vigencia Hasta no puede ser anterior a Vigencia Desde.';
|
||||||
|
}
|
||||||
|
if (!valor.trim()) errors.valor = 'El valor es obligatorio.';
|
||||||
|
else if (isNaN(parseFloat(valor)) || parseFloat(valor) < 0) {
|
||||||
|
errors.valor = 'El valor debe ser un número positivo.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (fieldName: string) => {
|
||||||
|
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const valorNum = parseFloat(valor);
|
||||||
|
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
const dataToSubmit: UpdateRecargoZonaDto = {
|
||||||
|
valor: valorNum,
|
||||||
|
vigenciaH: vigenciaH.trim() ? vigenciaH : null,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit, initialData.idRecargo);
|
||||||
|
} else {
|
||||||
|
const dataToSubmit: CreateRecargoZonaDto = {
|
||||||
|
idPublicacion,
|
||||||
|
idZona: Number(idZona),
|
||||||
|
vigenciaD,
|
||||||
|
valor: valorNum,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de RecargoZonaFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Recargo por Zona' : 'Agregar Nuevo Recargo por Zona'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idZona}>
|
||||||
|
<InputLabel id="zona-recargo-select-label" required>Zona</InputLabel>
|
||||||
|
<Select labelId="zona-recargo-select-label" label="Zona" value={idZona}
|
||||||
|
onChange={(e) => {setIdZona(e.target.value as number); handleInputChange('idZona');}}
|
||||||
|
disabled={loading || loadingZonas || isEditing} // Zona no se edita
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una zona</em></MenuItem>
|
||||||
|
{zonas.map((z) => (<MenuItem key={z.idZona} value={z.idZona}>{z.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idZona && <Typography color="error" variant="caption">{localErrors.idZona}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Vigencia Desde" type="date" value={vigenciaD} required={!isEditing}
|
||||||
|
onChange={(e) => {setVigenciaD(e.target.value); handleInputChange('vigenciaD');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.vigenciaD} helperText={localErrors.vigenciaD || ''}
|
||||||
|
disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing}
|
||||||
|
/>
|
||||||
|
{isEditing && (
|
||||||
|
<TextField label="Vigencia Hasta (Opcional)" type="date" value={vigenciaH}
|
||||||
|
onChange={(e) => {setVigenciaH(e.target.value); handleInputChange('vigenciaH');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.vigenciaH} helperText={localErrors.vigenciaH || ''}
|
||||||
|
disabled={loading} InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TextField label="Valor Recargo" type="number" value={valor} required
|
||||||
|
onChange={(e) => {setValor(e.target.value); handleInputChange('valor');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.valor} helperText={localErrors.valor || ''}
|
||||||
|
disabled={loading}
|
||||||
|
InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
|
||||||
|
inputProps={{ step: "0.01", lang:"es-AR" }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.zonas && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.zonas}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button type="submit" variant="contained" disabled={loading || loadingZonas}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Agregar Recargo')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RecargoZonaFormModal;
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { SalidaOtroDestinoDto } from '../../../models/dtos/Distribucion/SalidaOtroDestinoDto';
|
||||||
|
import type { CreateSalidaOtroDestinoDto } from '../../../models/dtos/Distribucion/CreateSalidaOtroDestinoDto';
|
||||||
|
import type { UpdateSalidaOtroDestinoDto } from '../../../models/dtos/Distribucion/UpdateSalidaOtroDestinoDto';
|
||||||
|
import type { PublicacionDto } from '../../../models/dtos/Distribucion/PublicacionDto';
|
||||||
|
import type { OtroDestinoDto } from '../../../models/dtos/Distribucion/OtroDestinoDto';
|
||||||
|
import publicacionService from '../../../services/Distribucion/publicacionService';
|
||||||
|
import otroDestinoService from '../../../services/Distribucion/otroDestinoService';
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 500 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SalidaOtroDestinoFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreateSalidaOtroDestinoDto | UpdateSalidaOtroDestinoDto, idParte?: number) => Promise<void>;
|
||||||
|
initialData?: SalidaOtroDestinoDto | null; // Para editar
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SalidaOtroDestinoFormModal: React.FC<SalidaOtroDestinoFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
|
||||||
|
const [idDestino, setIdDestino] = useState<number | string>('');
|
||||||
|
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [cantidad, setCantidad] = useState<string>('');
|
||||||
|
const [observacion, setObservacion] = useState('');
|
||||||
|
|
||||||
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
|
const [otrosDestinos, setOtrosDestinos] = useState<OtroDestinoDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDropdownData = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const [pubsData, destinosData] = await Promise.all([
|
||||||
|
publicacionService.getAllPublicaciones(undefined, undefined, true), // Solo habilitadas
|
||||||
|
otroDestinoService.getAllOtrosDestinos()
|
||||||
|
]);
|
||||||
|
setPublicaciones(pubsData);
|
||||||
|
setOtrosDestinos(destinosData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar datos para dropdowns", error);
|
||||||
|
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar datos necesarios.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
fetchDropdownData();
|
||||||
|
setIdPublicacion(initialData?.idPublicacion || '');
|
||||||
|
setIdDestino(initialData?.idDestino || '');
|
||||||
|
setFecha(initialData?.fecha || new Date().toISOString().split('T')[0]);
|
||||||
|
setCantidad(initialData?.cantidad?.toString() || '');
|
||||||
|
setObservacion(initialData?.observacion || '');
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idPublicacion) errors.idPublicacion = 'Seleccione una publicación.';
|
||||||
|
if (!idDestino) errors.idDestino = 'Seleccione un destino.';
|
||||||
|
if (!fecha.trim()) errors.fecha = 'La fecha es obligatoria.';
|
||||||
|
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fecha)) errors.fecha = 'Formato de fecha inválido.';
|
||||||
|
if (!cantidad.trim() || isNaN(parseInt(cantidad)) || parseInt(cantidad) <= 0) {
|
||||||
|
errors.cantidad = 'La cantidad debe ser un número positivo.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (fieldName: string) => {
|
||||||
|
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
const dataToSubmit: UpdateSalidaOtroDestinoDto = {
|
||||||
|
cantidad: parseInt(cantidad, 10),
|
||||||
|
observacion: observacion || undefined,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit, initialData.idParte);
|
||||||
|
} else {
|
||||||
|
const dataToSubmit: CreateSalidaOtroDestinoDto = {
|
||||||
|
idPublicacion: Number(idPublicacion),
|
||||||
|
idDestino: Number(idDestino),
|
||||||
|
fecha,
|
||||||
|
cantidad: parseInt(cantidad, 10),
|
||||||
|
observacion: observacion || undefined,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de SalidaOtroDestinoFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Salida a Otro Destino' : 'Registrar Salida a Otro Destino'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idPublicacion} required>
|
||||||
|
<InputLabel id="publicacion-sod-select-label">Publicación</InputLabel>
|
||||||
|
<Select labelId="publicacion-sod-select-label" label="Publicación" value={idPublicacion}
|
||||||
|
onChange={(e) => {setIdPublicacion(e.target.value as number); handleInputChange('idPublicacion');}}
|
||||||
|
disabled={loading || loadingDropdowns || isEditing} // No se edita publicación
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{publicaciones.map((p) => (<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre} ({p.nombreEmpresa})</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPublicacion && <Typography color="error" variant="caption">{localErrors.idPublicacion}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idDestino} required>
|
||||||
|
<InputLabel id="destino-sod-select-label">Otro Destino</InputLabel>
|
||||||
|
<Select labelId="destino-sod-select-label" label="Otro Destino" value={idDestino}
|
||||||
|
onChange={(e) => {setIdDestino(e.target.value as number); handleInputChange('idDestino');}}
|
||||||
|
disabled={loading || loadingDropdowns || isEditing} // No se edita destino
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{otrosDestinos.map((d) => (<MenuItem key={d.idDestino} value={d.idDestino}>{d.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idDestino && <Typography color="error" variant="caption">{localErrors.idDestino}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Fecha" type="date" value={fecha} required
|
||||||
|
onChange={(e) => {setFecha(e.target.value); handleInputChange('fecha');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.fecha} helperText={localErrors.fecha || ''}
|
||||||
|
disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing}
|
||||||
|
/>
|
||||||
|
<TextField label="Cantidad" type="number" value={cantidad} required
|
||||||
|
onChange={(e) => {setCantidad(e.target.value); handleInputChange('cantidad');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.cantidad} helperText={localErrors.cantidad || ''}
|
||||||
|
disabled={loading} inputProps={{min:1}}
|
||||||
|
/>
|
||||||
|
<TextField label="Observación (Opcional)" value={observacion}
|
||||||
|
onChange={(e) => setObservacion(e.target.value)}
|
||||||
|
margin="dense" fullWidth multiline rows={3} disabled={loading}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Registrar Salida')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SalidaOtroDestinoFormModal;
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { StockBobinaDto } from '../../../models/dtos/Impresion/StockBobinaDto';
|
||||||
|
import type { CambiarEstadoBobinaDto } from '../../../models/dtos/Impresion/CambiarEstadoBobinaDto';
|
||||||
|
import type { EstadoBobinaDto } from '../../../models/dtos/Impresion/EstadoBobinaDto';
|
||||||
|
import type { PublicacionDto } from '../../../models/dtos/Distribucion/PublicacionDto';
|
||||||
|
import type { PubliSeccionDto } from '../../../models/dtos/Distribucion/PubliSeccionDto'; // Asumiendo que tienes este DTO
|
||||||
|
import estadoBobinaService from '../../../services/Impresion/estadoBobinaService'; // Para cargar estados
|
||||||
|
import publicacionService from '../../../services/Distribucion/publicacionService'; // Para cargar publicaciones
|
||||||
|
import publiSeccionService from '../../../services/Distribucion/publiSeccionService'; // Para cargar secciones
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 500 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
// IDs de estados conocidos (ajusta según tu BD)
|
||||||
|
const ID_ESTADO_EN_USO = 2;
|
||||||
|
const ID_ESTADO_DANADA = 3;
|
||||||
|
// const ID_ESTADO_DISPONIBLE = 1; // No se cambia a Disponible desde este modal
|
||||||
|
|
||||||
|
interface StockBobinaCambioEstadoModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (idBobina: number, data: CambiarEstadoBobinaDto) => Promise<void>;
|
||||||
|
bobinaActual: StockBobinaDto | null; // La bobina cuyo estado se va a cambiar
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StockBobinaCambioEstadoModal: React.FC<StockBobinaCambioEstadoModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
bobinaActual,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [nuevoEstadoId, setNuevoEstadoId] = useState<number | string>('');
|
||||||
|
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
|
||||||
|
const [idSeccion, setIdSeccion] = useState<number | string>('');
|
||||||
|
const [obs, setObs] = useState('');
|
||||||
|
const [fechaCambioEstado, setFechaCambioEstado] = useState('');
|
||||||
|
|
||||||
|
const [estadosDisponibles, setEstadosDisponibles] = useState<EstadoBobinaDto[]>([]);
|
||||||
|
const [publicacionesDisponibles, setPublicacionesDisponibles] = useState<PublicacionDto[]>([]);
|
||||||
|
const [seccionesDisponibles, setSeccionesDisponibles] = useState<PubliSeccionDto[]>([]);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDropdownData = async () => {
|
||||||
|
if (!bobinaActual) return;
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const estadosData = await estadoBobinaService.getAllEstadosBobina();
|
||||||
|
// Filtrar estados: no se puede volver a "Disponible" o al mismo estado actual desde aquí.
|
||||||
|
// Y si está "Dañada", no se puede cambiar.
|
||||||
|
let estadosFiltrados = estadosData.filter(e => e.idEstadoBobina !== bobinaActual.idEstadoBobina && e.idEstadoBobina !== 1);
|
||||||
|
if (bobinaActual.idEstadoBobina === ID_ESTADO_DANADA) { // Si ya está dañada, no hay más cambios
|
||||||
|
estadosFiltrados = [];
|
||||||
|
} else if (bobinaActual.idEstadoBobina === ID_ESTADO_EN_USO) { // Si está en uso, solo puede pasar a Dañada
|
||||||
|
estadosFiltrados = estadosData.filter(e => e.idEstadoBobina === ID_ESTADO_DANADA);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setEstadosDisponibles(estadosFiltrados);
|
||||||
|
|
||||||
|
if (estadosFiltrados.some(e => e.idEstadoBobina === ID_ESTADO_EN_USO)) { // Solo cargar publicaciones si "En Uso" es una opción
|
||||||
|
const publicacionesData = await publicacionService.getAllPublicaciones(undefined, undefined, true); // Solo habilitadas
|
||||||
|
setPublicacionesDisponibles(publicacionesData);
|
||||||
|
} else {
|
||||||
|
setPublicacionesDisponibles([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar datos para dropdowns (Cambio Estado Bobina)", error);
|
||||||
|
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar datos necesarios.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open && bobinaActual) {
|
||||||
|
fetchDropdownData();
|
||||||
|
setNuevoEstadoId('');
|
||||||
|
setIdPublicacion('');
|
||||||
|
setIdSeccion('');
|
||||||
|
setObs(bobinaActual.obs || ''); // Pre-cargar obs existente
|
||||||
|
setFechaCambioEstado(new Date().toISOString().split('T')[0]); // Default a hoy
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, bobinaActual, clearErrorMessage]);
|
||||||
|
|
||||||
|
|
||||||
|
// Cargar secciones cuando cambia la publicación seleccionada y el estado es "En Uso"
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSecciones = async () => {
|
||||||
|
if (nuevoEstadoId === ID_ESTADO_EN_USO && idPublicacion) {
|
||||||
|
setLoadingDropdowns(true); // Podrías tener un loader específico para secciones
|
||||||
|
try {
|
||||||
|
const data = await publiSeccionService.getSeccionesPorPublicacion(Number(idPublicacion), true); // Solo activas
|
||||||
|
setSeccionesDisponibles(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar secciones:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, secciones: 'Error al cargar secciones.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setSeccionesDisponibles([]); // Limpiar secciones si no aplica
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchSecciones();
|
||||||
|
}, [nuevoEstadoId, idPublicacion]);
|
||||||
|
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!nuevoEstadoId) errors.nuevoEstadoId = 'Seleccione un nuevo estado.';
|
||||||
|
if (!fechaCambioEstado.trim()) errors.fechaCambioEstado = 'La fecha de cambio es obligatoria.';
|
||||||
|
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fechaCambioEstado)) errors.fechaCambioEstado = 'Formato de fecha inválido.';
|
||||||
|
|
||||||
|
if (Number(nuevoEstadoId) === ID_ESTADO_EN_USO) {
|
||||||
|
if (!idPublicacion) errors.idPublicacion = 'Seleccione una publicación.';
|
||||||
|
if (!idSeccion) errors.idSeccion = 'Seleccione una sección.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (fieldName: string) => {
|
||||||
|
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
if (fieldName === 'nuevoEstadoId') { // Si cambia el estado, resetear pub/secc
|
||||||
|
setIdPublicacion('');
|
||||||
|
setIdSeccion('');
|
||||||
|
}
|
||||||
|
if (fieldName === 'idPublicacion') { // Si cambia la publicación, resetear seccion
|
||||||
|
setIdSeccion('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate() || !bobinaActual) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const dataToSubmit: CambiarEstadoBobinaDto = {
|
||||||
|
nuevoEstadoId: Number(nuevoEstadoId),
|
||||||
|
idPublicacion: Number(nuevoEstadoId) === ID_ESTADO_EN_USO ? Number(idPublicacion) : null,
|
||||||
|
idSeccion: Number(nuevoEstadoId) === ID_ESTADO_EN_USO ? Number(idSeccion) : null,
|
||||||
|
obs: obs || undefined,
|
||||||
|
fechaCambioEstado,
|
||||||
|
};
|
||||||
|
await onSubmit(bobinaActual.idBobina, dataToSubmit);
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de StockBobinaCambioEstadoModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!bobinaActual) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
Cambiar Estado de Bobina: {bobinaActual.nroBobina}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" gutterBottom>
|
||||||
|
Estado Actual: <strong>{bobinaActual.nombreEstadoBobina}</strong>
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.nuevoEstadoId}>
|
||||||
|
<InputLabel id="nuevo-estado-select-label" required>Nuevo Estado</InputLabel>
|
||||||
|
<Select labelId="nuevo-estado-select-label" label="Nuevo Estado" value={nuevoEstadoId}
|
||||||
|
onChange={(e) => {setNuevoEstadoId(e.target.value as number); handleInputChange('nuevoEstadoId');}}
|
||||||
|
disabled={loading || loadingDropdowns || estadosDisponibles.length === 0}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione un estado</em></MenuItem>
|
||||||
|
{estadosDisponibles.map((e) => (<MenuItem key={e.idEstadoBobina} value={e.idEstadoBobina}>{e.denominacion}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.nuevoEstadoId && <Typography color="error" variant="caption">{localErrors.nuevoEstadoId}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
{Number(nuevoEstadoId) === ID_ESTADO_EN_USO && (
|
||||||
|
<>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idPublicacion}>
|
||||||
|
<InputLabel id="publicacion-estado-select-label" required>Publicación</InputLabel>
|
||||||
|
<Select labelId="publicacion-estado-select-label" label="Publicación" value={idPublicacion}
|
||||||
|
onChange={(e) => {setIdPublicacion(e.target.value as number); handleInputChange('idPublicacion');}}
|
||||||
|
disabled={loading || loadingDropdowns}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione publicación</em></MenuItem>
|
||||||
|
{publicacionesDisponibles.map((p) => (<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPublicacion && <Typography color="error" variant="caption">{localErrors.idPublicacion}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idSeccion}>
|
||||||
|
<InputLabel id="seccion-estado-select-label" required>Sección</InputLabel>
|
||||||
|
<Select labelId="seccion-estado-select-label" label="Sección" value={idSeccion}
|
||||||
|
onChange={(e) => {setIdSeccion(e.target.value as number); handleInputChange('idSeccion');}}
|
||||||
|
disabled={loading || loadingDropdowns || !idPublicacion || seccionesDisponibles.length === 0}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>{idPublicacion ? 'Seleccione sección' : 'Seleccione publicación primero'}</em></MenuItem>
|
||||||
|
{seccionesDisponibles.map((s) => (<MenuItem key={s.idSeccion} value={s.idSeccion}>{s.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idSeccion && <Typography color="error" variant="caption">{localErrors.idSeccion}</Typography>}
|
||||||
|
{localErrors.secciones && <Alert severity="warning" sx={{mt:0.5}}>{localErrors.secciones}</Alert>}
|
||||||
|
</FormControl>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<TextField label="Fecha Cambio de Estado" type="date" value={fechaCambioEstado} required
|
||||||
|
onChange={(e) => {setFechaCambioEstado(e.target.value); handleInputChange('fechaCambioEstado');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.fechaCambioEstado} helperText={localErrors.fechaCambioEstado || ''}
|
||||||
|
disabled={loading} InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<TextField label="Observaciones (Opcional)" value={obs}
|
||||||
|
onChange={(e) => setObs(e.target.value)}
|
||||||
|
margin="dense" fullWidth multiline rows={3} disabled={loading}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns || estadosDisponibles.length === 0}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : 'Guardar Cambio de Estado'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StockBobinaCambioEstadoModal;
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { StockBobinaDto } from '../../../models/dtos/Impresion/StockBobinaDto';
|
||||||
|
import type { UpdateStockBobinaDto } from '../../../models/dtos/Impresion/UpdateStockBobinaDto';
|
||||||
|
import type { TipoBobinaDto } from '../../../models/dtos/Impresion/TipoBobinaDto';
|
||||||
|
import type { PlantaDto } from '../../../models/dtos/Impresion/PlantaDto';
|
||||||
|
import tipoBobinaService from '../../../services/Impresion/tipoBobinaService';
|
||||||
|
import plantaService from '../../../services/Impresion/plantaService';
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo que StockBobinaIngresoFormModal) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 550 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StockBobinaEditFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (idBobina: number, data: UpdateStockBobinaDto) => Promise<void>;
|
||||||
|
initialData: StockBobinaDto | null; // Siempre habrá initialData para editar
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StockBobinaEditFormModal: React.FC<StockBobinaEditFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idTipoBobina, setIdTipoBobina] = useState<number | string>('');
|
||||||
|
const [nroBobina, setNroBobina] = useState('');
|
||||||
|
const [peso, setPeso] = useState<string>('');
|
||||||
|
const [idPlanta, setIdPlanta] = useState<number | string>('');
|
||||||
|
const [remito, setRemito] = useState('');
|
||||||
|
const [fechaRemito, setFechaRemito] = useState(''); // yyyy-MM-dd
|
||||||
|
|
||||||
|
const [tiposBobina, setTiposBobina] = useState<TipoBobinaDto[]>([]);
|
||||||
|
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDropdownData = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const [tiposData, plantasData] = await Promise.all([
|
||||||
|
tipoBobinaService.getAllTiposBobina(),
|
||||||
|
plantaService.getAllPlantas()
|
||||||
|
]);
|
||||||
|
setTiposBobina(tiposData);
|
||||||
|
setPlantas(plantasData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar datos para dropdowns (StockBobina Edit)", error);
|
||||||
|
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar tipos/plantas.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open && initialData) {
|
||||||
|
fetchDropdownData();
|
||||||
|
setIdTipoBobina(initialData.idTipoBobina || '');
|
||||||
|
setNroBobina(initialData.nroBobina || '');
|
||||||
|
setPeso(initialData.peso?.toString() || '');
|
||||||
|
setIdPlanta(initialData.idPlanta || '');
|
||||||
|
setRemito(initialData.remito || '');
|
||||||
|
setFechaRemito(initialData.fechaRemito || ''); // Asume yyyy-MM-dd del DTO
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idTipoBobina) errors.idTipoBobina = 'Seleccione un tipo.';
|
||||||
|
if (!nroBobina.trim()) errors.nroBobina = 'Nro. Bobina es obligatorio.';
|
||||||
|
if (!peso.trim() || isNaN(parseInt(peso)) || parseInt(peso) <= 0) errors.peso = 'Peso debe ser un número positivo.';
|
||||||
|
if (!idPlanta) errors.idPlanta = 'Seleccione una planta.';
|
||||||
|
if (!remito.trim()) errors.remito = 'Remito es obligatorio.';
|
||||||
|
if (!fechaRemito.trim()) errors.fechaRemito = 'Fecha de Remito es obligatoria.';
|
||||||
|
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fechaRemito)) errors.fechaRemito = 'Formato de fecha inválido.';
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (fieldName: string) => {
|
||||||
|
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate() || !initialData) return; // initialData siempre debería existir aquí
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const dataToSubmit: UpdateStockBobinaDto = {
|
||||||
|
idTipoBobina: Number(idTipoBobina),
|
||||||
|
nroBobina,
|
||||||
|
peso: parseInt(peso, 10),
|
||||||
|
idPlanta: Number(idPlanta),
|
||||||
|
remito,
|
||||||
|
fechaRemito,
|
||||||
|
};
|
||||||
|
await onSubmit(initialData.idBobina, dataToSubmit);
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de StockBobinaEditFormModal:", error);
|
||||||
|
// El error de API lo maneja la página que llama a este modal
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!initialData) return null; // No renderizar si no hay datos iniciales (aunque open lo controla)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
Editar Datos de Bobina (ID: {initialData.idBobina})
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idTipoBobina} sx={{flex:1, minWidth: '200px'}}>
|
||||||
|
<InputLabel id="edit-tipo-bobina-select-label" required>Tipo Bobina</InputLabel>
|
||||||
|
<Select labelId="edit-tipo-bobina-select-label" label="Tipo Bobina" value={idTipoBobina}
|
||||||
|
onChange={(e) => {setIdTipoBobina(e.target.value as number); handleInputChange('idTipoBobina');}}
|
||||||
|
disabled={loading || loadingDropdowns}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione un tipo</em></MenuItem>
|
||||||
|
{tiposBobina.map((t) => (<MenuItem key={t.idTipoBobina} value={t.idTipoBobina}>{t.denominacion}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idTipoBobina && <Typography color="error" variant="caption">{localErrors.idTipoBobina}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Nro. Bobina" value={nroBobina} required
|
||||||
|
onChange={(e) => {setNroBobina(e.target.value); handleInputChange('nroBobina');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.nroBobina} helperText={localErrors.nroBobina || ''}
|
||||||
|
disabled={loading} sx={{flex:1, minWidth: '200px'}} autoFocus
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||||
|
<TextField label="Peso (Kg)" type="number" value={peso} required
|
||||||
|
onChange={(e) => {setPeso(e.target.value); handleInputChange('peso');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.peso} helperText={localErrors.peso || ''}
|
||||||
|
disabled={loading} sx={{flex:1, minWidth: '150px'}}
|
||||||
|
/>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idPlanta} sx={{flex:1, minWidth: '200px'}}>
|
||||||
|
<InputLabel id="edit-planta-select-label" required>Planta Destino</InputLabel>
|
||||||
|
<Select labelId="edit-planta-select-label" label="Planta Destino" value={idPlanta}
|
||||||
|
onChange={(e) => {setIdPlanta(e.target.value as number); handleInputChange('idPlanta');}}
|
||||||
|
disabled={loading || loadingDropdowns}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una planta</em></MenuItem>
|
||||||
|
{plantas.map((p) => (<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPlanta && <Typography color="error" variant="caption">{localErrors.idPlanta}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||||
|
<TextField label="Nro. Remito" value={remito} required
|
||||||
|
onChange={(e) => {setRemito(e.target.value); handleInputChange('remito');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.remito} helperText={localErrors.remito || ''}
|
||||||
|
disabled={loading} sx={{flex:1, minWidth: '200px'}}
|
||||||
|
/>
|
||||||
|
<TextField label="Fecha Remito" type="date" value={fechaRemito} required
|
||||||
|
onChange={(e) => {setFechaRemito(e.target.value); handleInputChange('fechaRemito');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.fechaRemito} helperText={localErrors.fechaRemito || ''}
|
||||||
|
disabled={loading} InputLabelProps={{ shrink: true }} sx={{flex:1, minWidth: '200px'}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : 'Guardar Cambios'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StockBobinaEditFormModal;
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Modal, Box, Typography, TextField, Button, CircularProgress, Alert, FormControl, InputLabel, Select, MenuItem } from '@mui/material';
|
||||||
|
import type { CreateStockBobinaDto } from '../../../models/dtos/Impresion/CreateStockBobinaDto';
|
||||||
|
import type { TipoBobinaDto } from '../../../models/dtos/Impresion/TipoBobinaDto';
|
||||||
|
import type { PlantaDto } from '../../../models/dtos/Impresion/PlantaDto';
|
||||||
|
import tipoBobinaService from '../../../services/Impresion/tipoBobinaService';
|
||||||
|
import plantaService from '../../../services/Impresion/plantaService';
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 550 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StockBobinaIngresoFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreateStockBobinaDto) => Promise<void>; // Solo para crear
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
// initialData no es necesario para un modal de solo creación
|
||||||
|
}
|
||||||
|
|
||||||
|
const StockBobinaIngresoFormModal: React.FC<StockBobinaIngresoFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idTipoBobina, setIdTipoBobina] = useState<number | string>('');
|
||||||
|
const [nroBobina, setNroBobina] = useState('');
|
||||||
|
const [peso, setPeso] = useState<string>('');
|
||||||
|
const [idPlanta, setIdPlanta] = useState<number | string>('');
|
||||||
|
const [remito, setRemito] = useState('');
|
||||||
|
const [fechaRemito, setFechaRemito] = useState(''); // yyyy-MM-dd
|
||||||
|
|
||||||
|
const [tiposBobina, setTiposBobina] = useState<TipoBobinaDto[]>([]);
|
||||||
|
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDropdownData = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const [tiposData, plantasData] = await Promise.all([
|
||||||
|
tipoBobinaService.getAllTiposBobina(),
|
||||||
|
plantaService.getAllPlantas()
|
||||||
|
]);
|
||||||
|
setTiposBobina(tiposData);
|
||||||
|
setPlantas(plantasData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar datos para dropdowns (StockBobina)", error);
|
||||||
|
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar tipos/plantas.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
fetchDropdownData();
|
||||||
|
// Resetear campos
|
||||||
|
setIdTipoBobina(''); setNroBobina(''); setPeso(''); setIdPlanta('');
|
||||||
|
setRemito(''); setFechaRemito(new Date().toISOString().split('T')[0]); // Default a hoy
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idTipoBobina) errors.idTipoBobina = 'Seleccione un tipo.';
|
||||||
|
if (!nroBobina.trim()) errors.nroBobina = 'Nro. Bobina es obligatorio.';
|
||||||
|
if (!peso.trim() || isNaN(parseInt(peso)) || parseInt(peso) <= 0) errors.peso = 'Peso debe ser un número positivo.';
|
||||||
|
if (!idPlanta) errors.idPlanta = 'Seleccione una planta.';
|
||||||
|
if (!remito.trim()) errors.remito = 'Remito es obligatorio.';
|
||||||
|
if (!fechaRemito.trim()) errors.fechaRemito = 'Fecha de Remito es obligatoria.';
|
||||||
|
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fechaRemito)) errors.fechaRemito = 'Formato de fecha inválido.';
|
||||||
|
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (fieldName: string) => {
|
||||||
|
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const dataToSubmit: CreateStockBobinaDto = {
|
||||||
|
idTipoBobina: Number(idTipoBobina),
|
||||||
|
nroBobina,
|
||||||
|
peso: parseInt(peso, 10),
|
||||||
|
idPlanta: Number(idPlanta),
|
||||||
|
remito,
|
||||||
|
fechaRemito,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de StockBobinaIngresoFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>Ingresar Nueva Bobina a Stock</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idTipoBobina} sx={{flex:1, minWidth: '200px'}}>
|
||||||
|
<InputLabel id="tipo-bobina-select-label" required>Tipo Bobina</InputLabel>
|
||||||
|
<Select labelId="tipo-bobina-select-label" label="Tipo Bobina" value={idTipoBobina}
|
||||||
|
onChange={(e) => {setIdTipoBobina(e.target.value as number); handleInputChange('idTipoBobina');}}
|
||||||
|
disabled={loading || loadingDropdowns}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione un tipo</em></MenuItem>
|
||||||
|
{tiposBobina.map((t) => (<MenuItem key={t.idTipoBobina} value={t.idTipoBobina}>{t.denominacion}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idTipoBobina && <Typography color="error" variant="caption">{localErrors.idTipoBobina}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Nro. Bobina" value={nroBobina} required
|
||||||
|
onChange={(e) => {setNroBobina(e.target.value); handleInputChange('nroBobina');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.nroBobina} helperText={localErrors.nroBobina || ''}
|
||||||
|
disabled={loading} sx={{flex:1, minWidth: '200px'}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||||
|
<TextField label="Peso (Kg)" type="number" value={peso} required
|
||||||
|
onChange={(e) => {setPeso(e.target.value); handleInputChange('peso');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.peso} helperText={localErrors.peso || ''}
|
||||||
|
disabled={loading} sx={{flex:1, minWidth: '150px'}}
|
||||||
|
/>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idPlanta} sx={{flex:1, minWidth: '200px'}}>
|
||||||
|
<InputLabel id="planta-select-label" required>Planta Destino</InputLabel>
|
||||||
|
<Select labelId="planta-select-label" label="Planta Destino" value={idPlanta}
|
||||||
|
onChange={(e) => {setIdPlanta(e.target.value as number); handleInputChange('idPlanta');}}
|
||||||
|
disabled={loading || loadingDropdowns}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una planta</em></MenuItem>
|
||||||
|
{plantas.map((p) => (<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPlanta && <Typography color="error" variant="caption">{localErrors.idPlanta}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, mb: 1, flexWrap: 'wrap' }}>
|
||||||
|
<TextField label="Nro. Remito" value={remito} required
|
||||||
|
onChange={(e) => {setRemito(e.target.value); handleInputChange('remito');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.remito} helperText={localErrors.remito || ''}
|
||||||
|
disabled={loading} sx={{flex:1, minWidth: '200px'}}
|
||||||
|
/>
|
||||||
|
<TextField label="Fecha Remito" type="date" value={fechaRemito} required
|
||||||
|
onChange={(e) => {setFechaRemito(e.target.value); handleInputChange('fechaRemito');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.fechaRemito} helperText={localErrors.fechaRemito || ''}
|
||||||
|
disabled={loading} InputLabelProps={{ shrink: true }} sx={{flex:1, minWidth: '200px'}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : 'Ingresar Bobina'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StockBobinaIngresoFormModal;
|
||||||
334
Frontend/src/components/Modals/Impresion/TiradaFormModal.tsx
Normal file
334
Frontend/src/components/Modals/Impresion/TiradaFormModal.tsx
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
// src/components/Modals/TiradaFormModal.tsx
|
||||||
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, IconButton, Paper,
|
||||||
|
Table, TableHead, TableRow, TableCell, TableBody,
|
||||||
|
TableContainer
|
||||||
|
} from '@mui/material';
|
||||||
|
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
|
||||||
|
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||||
|
import type { CreateTiradaRequestDto } from '../../../models/dtos/Impresion/CreateTiradaRequestDto';
|
||||||
|
import type { PublicacionDto } from '../../../models/dtos/Distribucion/PublicacionDto';
|
||||||
|
import type { PlantaDto } from '../../../models/dtos/Impresion/PlantaDto';
|
||||||
|
import type { PubliSeccionDto } from '../../../models/dtos/Distribucion/PubliSeccionDto';
|
||||||
|
import publicacionService from '../../../services/Distribucion/publicacionService';
|
||||||
|
import plantaService from '../../../services/Impresion/plantaService';
|
||||||
|
import publiSeccionService from '../../../services/Distribucion/publiSeccionService';
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '95%', sm: '80%', md: '750px' },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 3,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
// CORREGIDO: Ajustar el tipo para los inputs. Usaremos string para los inputs,
|
||||||
|
// y convertiremos a number al hacer submit o al validar donde sea necesario.
|
||||||
|
interface DetalleSeccionFormState {
|
||||||
|
idSeccion: number | ''; // Permitir string vacío para el Select no seleccionado
|
||||||
|
nombreSeccion?: string;
|
||||||
|
cantPag: string; // TextField de cantPag siempre es string
|
||||||
|
idTemporal: string; // Para la key de React
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface TiradaFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreateTiradaRequestDto) => Promise<void>;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TiradaFormModal: React.FC<TiradaFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
|
||||||
|
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [idPlanta, setIdPlanta] = useState<number | string>('');
|
||||||
|
const [ejemplares, setEjemplares] = useState<string>('');
|
||||||
|
// CORREGIDO: Usar el nuevo tipo para el estado del formulario de secciones
|
||||||
|
const [seccionesDeTirada, setSeccionesDeTirada] = useState<DetalleSeccionFormState[]>([]);
|
||||||
|
|
||||||
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
|
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||||
|
const [seccionesPublicacion, setSeccionesPublicacion] = useState<PubliSeccionDto[]>([]);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setIdPublicacion('');
|
||||||
|
setFecha(new Date().toISOString().split('T')[0]);
|
||||||
|
setIdPlanta('');
|
||||||
|
setEjemplares('');
|
||||||
|
setSeccionesDeTirada([]);
|
||||||
|
setSeccionesPublicacion([]);
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchInitialDropdowns = useCallback(async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const [pubsData, plantasData] = await Promise.all([
|
||||||
|
publicacionService.getAllPublicaciones(undefined, undefined, true),
|
||||||
|
plantaService.getAllPlantas()
|
||||||
|
]);
|
||||||
|
setPublicaciones(pubsData);
|
||||||
|
setPlantas(plantasData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar publicaciones/plantas", error);
|
||||||
|
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar datos iniciales.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
resetForm(); // Llama a resetForm aquí
|
||||||
|
fetchInitialDropdowns();
|
||||||
|
}
|
||||||
|
}, [open, fetchInitialDropdowns]); // resetForm no necesita estar en las dependencias si su contenido no cambia basado en props/estado que también estén en las dependencias.
|
||||||
|
|
||||||
|
const fetchSeccionesDePublicacion = useCallback(async (pubId: number) => {
|
||||||
|
if (!pubId) {
|
||||||
|
setSeccionesPublicacion([]);
|
||||||
|
setSeccionesDeTirada([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const data = await publiSeccionService.getSeccionesPorPublicacion(pubId, true);
|
||||||
|
setSeccionesPublicacion(data);
|
||||||
|
setSeccionesDeTirada([]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar secciones de la publicación", error);
|
||||||
|
setLocalErrors(prev => ({...prev, secciones: 'Error al cargar secciones.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (idPublicacion) {
|
||||||
|
fetchSeccionesDePublicacion(Number(idPublicacion));
|
||||||
|
} else {
|
||||||
|
setSeccionesPublicacion([]);
|
||||||
|
setSeccionesDeTirada([]);
|
||||||
|
}
|
||||||
|
}, [idPublicacion, fetchSeccionesDePublicacion]);
|
||||||
|
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idPublicacion) errors.idPublicacion = 'Seleccione una publicación.';
|
||||||
|
if (!fecha.trim()) errors.fecha = 'La fecha es obligatoria.';
|
||||||
|
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fecha)) errors.fecha = 'Formato de fecha inválido.';
|
||||||
|
if (!idPlanta) errors.idPlanta = 'Seleccione una planta.';
|
||||||
|
if (!ejemplares.trim() || isNaN(parseInt(ejemplares)) || parseInt(ejemplares) <= 0) errors.ejemplares = 'Ejemplares debe ser un número positivo.';
|
||||||
|
|
||||||
|
if (seccionesDeTirada.length === 0) {
|
||||||
|
errors.seccionesArray = 'Debe agregar al menos una sección a la tirada.';
|
||||||
|
} else {
|
||||||
|
seccionesDeTirada.forEach((sec, index) => {
|
||||||
|
if (sec.idSeccion === '') errors[`seccion_${index}_id`] = `Fila ${index + 1}: Debe seleccionar una sección.`;
|
||||||
|
if (!sec.cantPag.trim() || isNaN(Number(sec.cantPag)) || Number(sec.cantPag) <= 0) {
|
||||||
|
errors[`seccion_${index}_pag`] = `Fila ${index + 1}: Cant. Páginas debe ser un número positivo.`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddSeccion = () => {
|
||||||
|
setSeccionesDeTirada([...seccionesDeTirada, { idSeccion: '', cantPag: '', nombreSeccion: '', idTemporal: crypto.randomUUID() }]);
|
||||||
|
if (localErrors.seccionesArray) setLocalErrors(prev => ({ ...prev, seccionesArray: null }));
|
||||||
|
};
|
||||||
|
const handleRemoveSeccion = (index: number) => {
|
||||||
|
setSeccionesDeTirada(seccionesDeTirada.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSeccionChange = (index: number, field: 'idSeccion' | 'cantPag', value: string | number) => {
|
||||||
|
const nuevasSecciones = [...seccionesDeTirada];
|
||||||
|
const targetSeccion = nuevasSecciones[index];
|
||||||
|
|
||||||
|
if (field === 'idSeccion') {
|
||||||
|
const numValue = Number(value); // El valor del Select es string, pero lo guardamos como number | ''
|
||||||
|
targetSeccion.idSeccion = numValue === 0 ? '' : numValue; // Si es 0 (placeholder), guardar ''
|
||||||
|
const seccionSeleccionada = seccionesPublicacion.find(s => s.idSeccion === numValue);
|
||||||
|
targetSeccion.nombreSeccion = seccionSeleccionada?.nombre || '';
|
||||||
|
} else { // cantPag
|
||||||
|
targetSeccion.cantPag = value as string; // Guardar como string, validar como número después
|
||||||
|
}
|
||||||
|
setSeccionesDeTirada(nuevasSecciones);
|
||||||
|
if (localErrors[`seccion_${index}_id`]) setLocalErrors(prev => ({ ...prev, [`seccion_${index}_id`]: null }));
|
||||||
|
if (localErrors[`seccion_${index}_pag`]) setLocalErrors(prev => ({ ...prev, [`seccion_${index}_pag`]: null }));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const dataToSubmit: CreateTiradaRequestDto = {
|
||||||
|
idPublicacion: Number(idPublicacion),
|
||||||
|
fecha,
|
||||||
|
idPlanta: Number(idPlanta),
|
||||||
|
ejemplares: parseInt(ejemplares, 10),
|
||||||
|
// CORREGIDO: Asegurar que los datos de secciones sean números
|
||||||
|
secciones: seccionesDeTirada.map(s => ({
|
||||||
|
idSeccion: Number(s.idSeccion), // Convertir a número aquí
|
||||||
|
cantPag: Number(s.cantPag) // Convertir a número aquí
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de TiradaFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>Registrar Nueva Tirada</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
{/* ... (campos de Publicacion, Fecha, Planta, Ejemplares sin cambios) ... */}
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idPublicacion} sx={{flex:1, minWidth: 200}}>
|
||||||
|
<InputLabel id="publicacion-tirada-select-label" required>Publicación</InputLabel>
|
||||||
|
<Select labelId="publicacion-tirada-select-label" label="Publicación" value={idPublicacion}
|
||||||
|
onChange={(e) => { setIdPublicacion(e.target.value as number); setLocalErrors(p => ({...p, idPublicacion: null})); }}
|
||||||
|
disabled={loading || loadingDropdowns}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{publicaciones.map((p) => (<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre} ({p.nombreEmpresa})</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPublicacion && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPublicacion}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Fecha Tirada" type="date" value={fecha} required
|
||||||
|
onChange={(e) => {setFecha(e.target.value); setLocalErrors(p => ({...p, fecha: null}));}}
|
||||||
|
margin="dense" error={!!localErrors.fecha} helperText={localErrors.fecha || ''}
|
||||||
|
disabled={loading} InputLabelProps={{ shrink: true }} sx={{flex:1, minWidth: 160}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 2 }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idPlanta} sx={{flex:1, minWidth: 200}}>
|
||||||
|
<InputLabel id="planta-tirada-select-label" required>Planta</InputLabel>
|
||||||
|
<Select labelId="planta-tirada-select-label" label="Planta" value={idPlanta}
|
||||||
|
onChange={(e) => {setIdPlanta(e.target.value as number); setLocalErrors(p => ({...p, idPlanta: null}));}}
|
||||||
|
disabled={loading || loadingDropdowns}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{plantas.map((p) => (<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPlanta && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPlanta}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Total Ejemplares" type="number" value={ejemplares} required
|
||||||
|
onChange={(e) => {setEjemplares(e.target.value); setLocalErrors(p => ({...p, ejemplares: null}));}}
|
||||||
|
margin="dense" error={!!localErrors.ejemplares} helperText={localErrors.ejemplares || ''}
|
||||||
|
disabled={loading} sx={{flex:1, minWidth: 150}}
|
||||||
|
inputProps={{min:1}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
<Typography variant="subtitle1" sx={{mt: 2, mb:1}}>Detalle de Secciones Impresas:</Typography>
|
||||||
|
{localErrors.seccionesArray && <Alert severity="error" sx={{mb:1}}>{localErrors.seccionesArray}</Alert>}
|
||||||
|
<Paper variant="outlined" sx={{p:1, mb:2, maxHeight: '250px', overflowY: 'auto'}}> {/* Permitir scroll en tabla de secciones */}
|
||||||
|
<TableContainer>
|
||||||
|
<Table size="small" stickyHeader> {/* stickyHeader para que cabecera quede fija */}
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell sx={{fontWeight:'bold', minWidth: 200}}>Sección</TableCell>
|
||||||
|
<TableCell sx={{fontWeight:'bold', width: '150px'}}>Cant. Páginas</TableCell>
|
||||||
|
<TableCell align="right" sx={{width: '50px'}}></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{seccionesDeTirada.map((sec, index) => (
|
||||||
|
<TableRow key={sec.idTemporal || index}> {/* Usar idTemporal para key */}
|
||||||
|
<TableCell sx={{py:0.5}}>
|
||||||
|
<FormControl fullWidth size="small" error={!!localErrors[`seccion_${index}_id`]}>
|
||||||
|
<Select value={sec.idSeccion} // Ahora idSeccion es number | ''
|
||||||
|
onChange={(e) => handleSeccionChange(index, 'idSeccion', e.target.value as number | '')}
|
||||||
|
disabled={loading || loadingDropdowns || seccionesPublicacion.length === 0}
|
||||||
|
displayEmpty
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccionar</em></MenuItem>
|
||||||
|
{seccionesPublicacion.map(s => <MenuItem key={s.idSeccion} value={s.idSeccion}>{s.nombre}</MenuItem>)}
|
||||||
|
</Select>
|
||||||
|
{localErrors[`seccion_${index}_id`] && <Typography color="error" variant="caption">{localErrors[`seccion_${index}_id`]}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell sx={{py:0.5}}>
|
||||||
|
<TextField type="number" size="small" fullWidth value={sec.cantPag}
|
||||||
|
onChange={(e) => handleSeccionChange(index, 'cantPag', e.target.value)}
|
||||||
|
error={!!localErrors[`seccion_${index}_pag`]}
|
||||||
|
helperText={localErrors[`seccion_${index}_pag`] || ''}
|
||||||
|
disabled={loading}
|
||||||
|
inputProps={{min:1}}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right" sx={{py:0.5}}>
|
||||||
|
<IconButton onClick={() => handleRemoveSeccion(index)} size="small" color="error" disabled={loading}>
|
||||||
|
<DeleteOutlineIcon />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
{seccionesDeTirada.length === 0 && (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={3} align="center">
|
||||||
|
<Typography variant="caption" color="textSecondary">
|
||||||
|
Agregue secciones a la tirada.
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
<Button startIcon={<AddCircleOutlineIcon />} onClick={handleAddSeccion} sx={{mt:1}} size="small" disabled={loading || !idPublicacion}>
|
||||||
|
Agregar Sección
|
||||||
|
</Button>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||||
|
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : 'Registrar Tirada'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TiradaFormModal;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export interface CreateEntradaSalidaDistDto {
|
||||||
|
idPublicacion: number;
|
||||||
|
idDistribuidor: number;
|
||||||
|
fecha: string; // "yyyy-MM-dd"
|
||||||
|
tipoMovimiento: 'Salida' | 'Entrada';
|
||||||
|
cantidad: number;
|
||||||
|
remito: number;
|
||||||
|
observacion?: string | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface CreatePorcMonCanillaDto {
|
||||||
|
idPublicacion: number;
|
||||||
|
idCanilla: number;
|
||||||
|
vigenciaD: string; // "yyyy-MM-dd"
|
||||||
|
porcMon: number;
|
||||||
|
esPorcentaje: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface CreatePorcPagoDto {
|
||||||
|
idPublicacion: number;
|
||||||
|
idDistribuidor: number;
|
||||||
|
vigenciaD: string; // "yyyy-MM-dd"
|
||||||
|
porcentaje: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface CreatePubliSeccionDto {
|
||||||
|
idPublicacion: number;
|
||||||
|
nombre: string;
|
||||||
|
estado?: boolean; // Default true en backend
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface CreateRecargoZonaDto {
|
||||||
|
idPublicacion: number;
|
||||||
|
idZona: number;
|
||||||
|
vigenciaD: string; // "yyyy-MM-dd"
|
||||||
|
valor: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface CreateSalidaOtroDestinoDto {
|
||||||
|
idPublicacion: number;
|
||||||
|
idDestino: number;
|
||||||
|
fecha: string; // "yyyy-MM-dd"
|
||||||
|
cantidad: number;
|
||||||
|
observacion?: string | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export interface EntradaSalidaDistDto {
|
||||||
|
idParte: number;
|
||||||
|
idPublicacion: number;
|
||||||
|
nombrePublicacion: string;
|
||||||
|
nombreEmpresaPublicacion: string;
|
||||||
|
idEmpresaPublicacion: number;
|
||||||
|
idDistribuidor: number;
|
||||||
|
nombreDistribuidor: string;
|
||||||
|
fecha: string; // "yyyy-MM-dd"
|
||||||
|
tipoMovimiento: 'Salida' | 'Entrada';
|
||||||
|
cantidad: number;
|
||||||
|
remito: number;
|
||||||
|
observacion?: string | null;
|
||||||
|
montoCalculado: number;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user