Ya perdí el hilo de los cambios pero ahi van.
This commit is contained in:
@@ -0,0 +1,130 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Contables;
|
||||||
|
using GestionIntegral.Api.Services.Contables;
|
||||||
|
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.Contables
|
||||||
|
{
|
||||||
|
[Route("api/notascreditodebito")] // Ruta base
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class NotasCreditoDebitoController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly INotaCreditoDebitoService _notaService;
|
||||||
|
private readonly ILogger<NotasCreditoDebitoController> _logger;
|
||||||
|
|
||||||
|
// Permisos para Notas C/D (CN001 a CN004)
|
||||||
|
private const string PermisoVer = "CN001";
|
||||||
|
private const string PermisoCrear = "CN002";
|
||||||
|
private const string PermisoModificar = "CN003";
|
||||||
|
private const string PermisoEliminar = "CN004";
|
||||||
|
|
||||||
|
public NotasCreditoDebitoController(INotaCreditoDebitoService notaService, ILogger<NotasCreditoDebitoController> logger)
|
||||||
|
{
|
||||||
|
_notaService = notaService;
|
||||||
|
_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 NotasCreditoDebitoController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/notascreditodebito
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<NotaCreditoDebitoDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetAll(
|
||||||
|
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
|
||||||
|
[FromQuery] string? destino, [FromQuery] int? idDestino,
|
||||||
|
[FromQuery] int? idEmpresa, [FromQuery] string? tipoNota)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
var notas = await _notaService.ObtenerTodosAsync(fechaDesde, fechaHasta, destino, idDestino, idEmpresa, tipoNota);
|
||||||
|
return Ok(notas);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/notascreditodebito/{idNota}
|
||||||
|
[HttpGet("{idNota:int}", Name = "GetNotaCreditoDebitoById")]
|
||||||
|
[ProducesResponseType(typeof(NotaCreditoDebitoDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetById(int idNota)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
var nota = await _notaService.ObtenerPorIdAsync(idNota);
|
||||||
|
if (nota == null) return NotFound(new { message = $"Nota con ID {idNota} no encontrada." });
|
||||||
|
return Ok(nota);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/notascreditodebito
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(NotaCreditoDebitoDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> Create([FromBody] CreateNotaDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoCrear)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _notaService.CrearAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al registrar la nota.");
|
||||||
|
|
||||||
|
return CreatedAtRoute("GetNotaCreditoDebitoById", new { idNota = dto.IdNota }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/notascreditodebito/{idNota}
|
||||||
|
[HttpPut("{idNota:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Update(int idNota, [FromBody] UpdateNotaDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoModificar)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _notaService.ActualizarAsync(idNota, updateDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Nota no encontrada.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/notascreditodebito/{idNota}
|
||||||
|
[HttpDelete("{idNota:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Delete(int idNota)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoEliminar)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _notaService.EliminarAsync(idNota, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Nota no encontrada.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Contables;
|
||||||
|
using GestionIntegral.Api.Services.Contables;
|
||||||
|
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.Contables
|
||||||
|
{
|
||||||
|
[Route("api/pagosdistribuidor")] // Ruta base
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class PagosDistribuidorController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IPagoDistribuidorService _pagoService;
|
||||||
|
private readonly ILogger<PagosDistribuidorController> _logger;
|
||||||
|
|
||||||
|
// Permisos para Pagos Distribuidores (CP001 a CP004)
|
||||||
|
private const string PermisoVer = "CP001";
|
||||||
|
private const string PermisoCrear = "CP002";
|
||||||
|
private const string PermisoModificar = "CP003";
|
||||||
|
private const string PermisoEliminar = "CP004";
|
||||||
|
|
||||||
|
public PagosDistribuidorController(IPagoDistribuidorService pagoService, ILogger<PagosDistribuidorController> logger)
|
||||||
|
{
|
||||||
|
_pagoService = pagoService;
|
||||||
|
_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 PagosDistribuidorController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/pagosdistribuidor
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<PagoDistribuidorDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetAll(
|
||||||
|
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
|
||||||
|
[FromQuery] int? idDistribuidor, [FromQuery] int? idEmpresa,
|
||||||
|
[FromQuery] string? tipoMovimiento)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
var pagos = await _pagoService.ObtenerTodosAsync(fechaDesde, fechaHasta, idDistribuidor, idEmpresa, tipoMovimiento);
|
||||||
|
return Ok(pagos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/pagosdistribuidor/{idPago}
|
||||||
|
[HttpGet("{idPago:int}", Name = "GetPagoDistribuidorById")]
|
||||||
|
[ProducesResponseType(typeof(PagoDistribuidorDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetById(int idPago)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
var pago = await _pagoService.ObtenerPorIdAsync(idPago);
|
||||||
|
if (pago == null) return NotFound(new { message = $"Pago con ID {idPago} no encontrado." });
|
||||||
|
return Ok(pago);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/pagosdistribuidor
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(PagoDistribuidorDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> Create([FromBody] CreatePagoDistribuidorDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoCrear)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _pagoService.CrearAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al registrar el pago.");
|
||||||
|
|
||||||
|
return CreatedAtRoute("GetPagoDistribuidorById", new { idPago = dto.IdPago }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/pagosdistribuidor/{idPago}
|
||||||
|
[HttpPut("{idPago:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Update(int idPago, [FromBody] UpdatePagoDistribuidorDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoModificar)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _pagoService.ActualizarAsync(idPago, updateDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Pago no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/pagosdistribuidor/{idPago}
|
||||||
|
[HttpDelete("{idPago:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Delete(int idPago)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoEliminar)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _pagoService.EliminarAsync(idPago, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Pago no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
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/controldevoluciones")] // Ruta base
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class ControlDevolucionesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IControlDevolucionesService _controlDevService;
|
||||||
|
private readonly ILogger<ControlDevolucionesController> _logger;
|
||||||
|
|
||||||
|
// Permisos para Control Devoluciones (CD001 a CD003)
|
||||||
|
private const string PermisoVer = "CD001";
|
||||||
|
private const string PermisoCrear = "CD002";
|
||||||
|
private const string PermisoModificar = "CD003";
|
||||||
|
// Asumo que no hay un permiso específico para eliminar, se podría usar CD003 o crear CD004
|
||||||
|
|
||||||
|
public ControlDevolucionesController(IControlDevolucionesService controlDevService, ILogger<ControlDevolucionesController> logger)
|
||||||
|
{
|
||||||
|
_controlDevService = controlDevService;
|
||||||
|
_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 ControlDevolucionesController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/controldevoluciones
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<ControlDevolucionesDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetAll(
|
||||||
|
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
|
||||||
|
[FromQuery] int? idEmpresa)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
var controles = await _controlDevService.ObtenerTodosAsync(fechaDesde, fechaHasta, idEmpresa);
|
||||||
|
return Ok(controles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/controldevoluciones/{idControl}
|
||||||
|
[HttpGet("{idControl:int}", Name = "GetControlDevolucionesById")]
|
||||||
|
[ProducesResponseType(typeof(ControlDevolucionesDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetById(int idControl)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||||
|
var control = await _controlDevService.ObtenerPorIdAsync(idControl);
|
||||||
|
if (control == null) return NotFound(new { message = $"Control de devoluciones con ID {idControl} no encontrado." });
|
||||||
|
return Ok(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/controldevoluciones
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(ControlDevolucionesDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> Create([FromBody] CreateControlDevolucionesDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoCrear)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _controlDevService.CrearAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al registrar el control.");
|
||||||
|
|
||||||
|
return CreatedAtRoute("GetControlDevolucionesById", new { idControl = dto.IdControl }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/controldevoluciones/{idControl}
|
||||||
|
[HttpPut("{idControl:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Update(int idControl, [FromBody] UpdateControlDevolucionesDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoModificar)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _controlDevService.ActualizarAsync(idControl, updateDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Control de devoluciones no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/controldevoluciones/{idControl}
|
||||||
|
[HttpDelete("{idControl:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)] // Podría usar PermisoModificar o un permiso de borrado específico
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Delete(int idControl)
|
||||||
|
{
|
||||||
|
// Asumimos que CD003 (Modificar) también permite eliminar, o se define un permiso específico
|
||||||
|
if (!TienePermiso(PermisoModificar)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _controlDevService.EliminarAsync(idControl, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Control de devoluciones no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
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/entradassalidascanilla")] // Ruta base
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class EntradasSalidasCanillaController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IEntradaSalidaCanillaService _esCanillaService;
|
||||||
|
private readonly ILogger<EntradasSalidasCanillaController> _logger;
|
||||||
|
|
||||||
|
// Permisos para E/S Canillitas (MC001 a MC005)
|
||||||
|
private const string PermisoVerMovimientos = "MC001";
|
||||||
|
private const string PermisoCrearMovimiento = "MC002";
|
||||||
|
private const string PermisoModificarMovimiento = "MC003";
|
||||||
|
private const string PermisoEliminarMovimiento = "MC004"; // (si no está liquidado)
|
||||||
|
private const string PermisoLiquidar = "MC005"; // Asumo que ver comprobante implica poder liquidar.
|
||||||
|
|
||||||
|
public EntradasSalidasCanillaController(IEntradaSalidaCanillaService esCanillaService, ILogger<EntradasSalidasCanillaController> logger)
|
||||||
|
{
|
||||||
|
_esCanillaService = esCanillaService;
|
||||||
|
_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 EntradasSalidasCanillaController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/entradassalidascanilla
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<EntradaSalidaCanillaDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetAll(
|
||||||
|
[FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta,
|
||||||
|
[FromQuery] int? idPublicacion, [FromQuery] int? idCanilla,
|
||||||
|
[FromQuery] bool? liquidados, [FromQuery] bool? incluirNoLiquidados = true) // incluirNoLiquidados por defecto true para ver todo
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerMovimientos)) return Forbid();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var movimientos = await _esCanillaService.ObtenerTodosAsync(fechaDesde, fechaHasta, idPublicacion, idCanilla, liquidados, incluirNoLiquidados);
|
||||||
|
return Ok(movimientos);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener listado de E/S Canillitas.");
|
||||||
|
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/entradassalidascanilla/{idParte}
|
||||||
|
[HttpGet("{idParte:int}", Name = "GetEntradaSalidaCanillaById")]
|
||||||
|
[ProducesResponseType(typeof(EntradaSalidaCanillaDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetById(int idParte)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerMovimientos)) return Forbid();
|
||||||
|
var movimiento = await _esCanillaService.ObtenerPorIdAsync(idParte);
|
||||||
|
if (movimiento == null) return NotFound(new { message = $"Movimiento con ID {idParte} no encontrado." });
|
||||||
|
return Ok(movimiento);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/entradassalidascanilla/{idParte}
|
||||||
|
[HttpPut("{idParte:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> UpdateMovimiento(int idParte, [FromBody] UpdateEntradaSalidaCanillaDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoModificarMovimiento)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _esCanillaService.ActualizarMovimientoAsync(idParte, updateDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Movimiento no encontrado." || error == "No se puede modificar un movimiento ya liquidado.")
|
||||||
|
return NotFound(new { message = error }); // Podría ser 404 o 400 dependiendo del error
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/entradassalidascanilla/{idParte}
|
||||||
|
[HttpDelete("{idParte:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> DeleteMovimiento(int idParte)
|
||||||
|
{
|
||||||
|
// Permiso base MC004
|
||||||
|
// El servicio ahora usa User (ClaimsPrincipal) para verificar MC006 si es necesario
|
||||||
|
string permisoBaseRequerido = "MC004"; // Asumiendo que esta constante existe o la defines
|
||||||
|
|
||||||
|
if (!TienePermiso(permisoBaseRequerido))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Acceso denegado a DeleteMovimiento (IDParte: {IdParte}) para Usuario ID {UserId}. Permiso {Permiso} requerido.", idParte, GetCurrentUserId() ?? 0, permisoBaseRequerido);
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
|
||||||
|
|
||||||
|
var (exito, error) = await _esCanillaService.EliminarMovimientoAsync(idParte, userId.Value, User); // Pasar ClaimsPrincipal (User)
|
||||||
|
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Movimiento no encontrado.") return NotFound(new { message = error });
|
||||||
|
// Otros errores como "no tiene permiso para eliminar liquidados" o errores internos.
|
||||||
|
// El servicio podría devolver un código de estado más específico o el controlador interpretarlo.
|
||||||
|
// Por ahora, un BadRequest es genérico para fallos de lógica de negocio o permisos no cumplidos en el servicio.
|
||||||
|
if (error != null && error.Contains("No tiene permiso"))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Intento fallido de eliminar movimiento ID {IdParte} por Usuario ID {UserId}. Razón: {Error}", idParte, userId, error);
|
||||||
|
return StatusCode(StatusCodes.Status403Forbidden, new { message = error });
|
||||||
|
}
|
||||||
|
return BadRequest(new { message = error ?? "Error desconocido al eliminar." });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/entradassalidascanilla/liquidar
|
||||||
|
[HttpPost("liquidar")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> LiquidarMovimientos([FromBody] LiquidarMovimientosCanillaRequestDto liquidarDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoLiquidar)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _esCanillaService.LiquidarMovimientosAsync(liquidarDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("bulk")] // Nueva ruta o ajustar la existente y diferenciar por DTO
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<EntradaSalidaCanillaDto>), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> CreateBulkMovimientos([FromBody] CreateBulkEntradaSalidaCanillaDto createBulkDto)
|
||||||
|
{
|
||||||
|
// Mantener PermisoCrearMovimiento = "MC002"
|
||||||
|
if (!TienePermiso(PermisoCrearMovimiento)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dtos, error) = await _esCanillaService.CrearMovimientosEnLoteAsync(createBulkDto, userId.Value);
|
||||||
|
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dtos == null || !dtos.Any()) return StatusCode(StatusCodes.Status500InternalServerError, "No se pudo registrar ningún movimiento o hubo un error.");
|
||||||
|
|
||||||
|
// Podrías devolver solo un 201 Created si la lista de DTOs es muy grande
|
||||||
|
// o si no necesitas los detalles de cada uno inmediatamente.
|
||||||
|
// O devolver la lista como se hace aquí.
|
||||||
|
return StatusCode(StatusCodes.Status201Created, dtos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Radios;
|
||||||
|
using GestionIntegral.Api.Services.Radios;
|
||||||
|
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.Radios
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")] // Ruta base: /api/canciones
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class CancionesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ICancionService _cancionService;
|
||||||
|
private readonly ILogger<CancionesController> _logger;
|
||||||
|
|
||||||
|
// Asumir permisos para Canciones (ej. RC001-RC004 o usar SS005)
|
||||||
|
private const string PermisoVerCanciones = "SS005";
|
||||||
|
private const string PermisoGestionarCanciones = "SS005";
|
||||||
|
|
||||||
|
public CancionesController(ICancionService cancionService, ILogger<CancionesController> logger)
|
||||||
|
{
|
||||||
|
_cancionService = cancionService;
|
||||||
|
_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 CancionesController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/canciones
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<CancionDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetAll([FromQuery] string? tema, [FromQuery] string? interprete, [FromQuery] int? idRitmo)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerCanciones)) return Forbid();
|
||||||
|
var canciones = await _cancionService.ObtenerTodasAsync(tema, interprete, idRitmo);
|
||||||
|
return Ok(canciones);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/canciones/{id}
|
||||||
|
[HttpGet("{id:int}", Name = "GetCancionById")]
|
||||||
|
[ProducesResponseType(typeof(CancionDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetById(int id)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerCanciones)) return Forbid();
|
||||||
|
var cancion = await _cancionService.ObtenerPorIdAsync(id);
|
||||||
|
if (cancion == null) return NotFound(new { message = $"Canción con ID {id} no encontrada." });
|
||||||
|
return Ok(cancion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/canciones
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(CancionDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> Create([FromBody] CreateCancionDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarCanciones)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _cancionService.CrearAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear la canción.");
|
||||||
|
|
||||||
|
return CreatedAtRoute("GetCancionById", new { id = dto.Id }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/canciones/{id}
|
||||||
|
[HttpPut("{id:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Update(int id, [FromBody] UpdateCancionDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarCanciones)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _cancionService.ActualizarAsync(id, updateDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Canción no encontrada.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/canciones/{id}
|
||||||
|
[HttpDelete("{id:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Delete(int id)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarCanciones)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _cancionService.EliminarAsync(id, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Canción no encontrada.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Radios;
|
||||||
|
using GestionIntegral.Api.Services.Radios;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Controllers.Radios
|
||||||
|
{
|
||||||
|
[Route("api/radios/listas")] // Ruta base, ej: /api/radios/listas
|
||||||
|
[ApiController]
|
||||||
|
[Authorize]
|
||||||
|
public class RadioListasController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IRadioListaService _radioListaService;
|
||||||
|
private readonly ILogger<RadioListasController> _logger;
|
||||||
|
|
||||||
|
// Asumir permiso general de Radios o uno específico para generar listas
|
||||||
|
private const string PermisoGenerarListas = "SS005"; // Usando el permiso general de la sección Radios
|
||||||
|
|
||||||
|
public RadioListasController(IRadioListaService radioListaService, ILogger<RadioListasController> logger)
|
||||||
|
{
|
||||||
|
_radioListaService = radioListaService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
// GetCurrentUserId no es estrictamente necesario aquí si la acción no modifica datos persistentes auditables por usuario.
|
||||||
|
|
||||||
|
// POST: api/radios/listas/generar
|
||||||
|
[HttpPost("generar")]
|
||||||
|
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] // Devuelve un archivo
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async Task<IActionResult> GenerarListaRadio([FromBody] GenerarListaRadioRequestDto requestDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGenerarListas)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
|
||||||
|
_logger.LogInformation("Solicitud de generación de lista de radio recibida: {@RequestDto}", requestDto);
|
||||||
|
|
||||||
|
var (fileContents, contentType, fileName, error) = await _radioListaService.GenerarListaRadioAsync(requestDto);
|
||||||
|
|
||||||
|
if (error != null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Error al generar lista de radio: {Error}", error);
|
||||||
|
// Devolver un JSON con el error podría ser más útil para el frontend que un simple BadRequest
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileContents == null || fileContents.Length == 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("La generación de la lista de radio no produjo contenido.");
|
||||||
|
// Similar al anterior, un JSON con mensaje puede ser mejor
|
||||||
|
return NotFound(new { message = "No se pudo generar la lista, o no hay datos suficientes." });
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Lista de radio generada exitosamente: {FileName}", fileName);
|
||||||
|
// Devuelve el archivo ZIP para descarga
|
||||||
|
return File(fileContents, contentType, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Radios;
|
||||||
|
using GestionIntegral.Api.Services.Radios;
|
||||||
|
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.Radios
|
||||||
|
{
|
||||||
|
[Route("api/[controller]")] // Ruta base: /api/ritmos
|
||||||
|
[ApiController]
|
||||||
|
[Authorize] // Proteger todos los endpoints
|
||||||
|
public class RitmosController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IRitmoService _ritmoService;
|
||||||
|
private readonly ILogger<RitmosController> _logger;
|
||||||
|
|
||||||
|
// Asumir códigos de permiso para Ritmos (ej. RR001-RR004)
|
||||||
|
// O usar permisos más genéricos de "Gestión Radios" si no hay específicos
|
||||||
|
private const string PermisoVerRitmos = "SS005"; // Usando el de acceso a la sección radios por ahora
|
||||||
|
private const string PermisoGestionarRitmos = "SS005"; // Idem para crear/mod/elim
|
||||||
|
|
||||||
|
public RitmosController(IRitmoService ritmoService, ILogger<RitmosController> logger)
|
||||||
|
{
|
||||||
|
_ritmoService = ritmoService;
|
||||||
|
_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 RitmosController.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/ritmos
|
||||||
|
[HttpGet]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<RitmoDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetAll([FromQuery] string? nombre)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerRitmos)) return Forbid();
|
||||||
|
var ritmos = await _ritmoService.ObtenerTodosAsync(nombre);
|
||||||
|
return Ok(ritmos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/ritmos/{id}
|
||||||
|
[HttpGet("{id:int}", Name = "GetRitmoById")]
|
||||||
|
[ProducesResponseType(typeof(RitmoDto), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetById(int id)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoVerRitmos)) return Forbid();
|
||||||
|
var ritmo = await _ritmoService.ObtenerPorIdAsync(id);
|
||||||
|
if (ritmo == null) return NotFound(new { message = $"Ritmo con ID {id} no encontrado." });
|
||||||
|
return Ok(ritmo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: api/ritmos
|
||||||
|
[HttpPost]
|
||||||
|
[ProducesResponseType(typeof(RitmoDto), StatusCodes.Status201Created)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> Create([FromBody] CreateRitmoDto createDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarRitmos)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId(); // Aunque no se use en el repo sin historial, es bueno tenerlo
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (dto, error) = await _ritmoService.CrearAsync(createDto, userId.Value);
|
||||||
|
if (error != null) return BadRequest(new { message = error });
|
||||||
|
if (dto == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear el ritmo.");
|
||||||
|
|
||||||
|
return CreatedAtRoute("GetRitmoById", new { id = dto.Id }, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT: api/ritmos/{id}
|
||||||
|
[HttpPut("{id:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Update(int id, [FromBody] UpdateRitmoDto updateDto)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarRitmos)) return Forbid();
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _ritmoService.ActualizarAsync(id, updateDto, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Ritmo no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error });
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/ritmos/{id}
|
||||||
|
[HttpDelete("{id:int}")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)] // Si está en uso
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> Delete(int id)
|
||||||
|
{
|
||||||
|
if (!TienePermiso(PermisoGestionarRitmos)) return Forbid();
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null) return Unauthorized();
|
||||||
|
|
||||||
|
var (exito, error) = await _ritmoService.EliminarAsync(id, userId.Value);
|
||||||
|
if (!exito)
|
||||||
|
{
|
||||||
|
if (error == "Ritmo no encontrado.") return NotFound(new { message = error });
|
||||||
|
return BadRequest(new { message = error }); // Ej: "En uso"
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using GestionIntegral.Api.Dtos.Usuarios;
|
using GestionIntegral.Api.Dtos.Usuarios;
|
||||||
|
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
|
||||||
using GestionIntegral.Api.Services.Usuarios;
|
using GestionIntegral.Api.Services.Usuarios;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -141,18 +142,63 @@ namespace GestionIntegral.Api.Controllers.Usuarios
|
|||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<IActionResult> ToggleHabilitado(int id, [FromBody] bool habilitar)
|
public async Task<IActionResult> ToggleHabilitado(int id, [FromBody] bool habilitar)
|
||||||
{
|
{
|
||||||
if (!TienePermiso(PermisoModificarUsuarios)) return Forbid();
|
if (!TienePermiso(PermisoModificarUsuarios)) return Forbid();
|
||||||
|
|
||||||
var idAdmin = GetCurrentUserId();
|
var idAdmin = GetCurrentUserId();
|
||||||
if (idAdmin == null) return Unauthorized("Token inválido.");
|
if (idAdmin == null) return Unauthorized("Token inválido.");
|
||||||
|
|
||||||
var (exito, error) = await _usuarioService.CambiarEstadoHabilitadoAsync(id, habilitar, idAdmin.Value);
|
var (exito, error) = await _usuarioService.CambiarEstadoHabilitadoAsync(id, habilitar, idAdmin.Value);
|
||||||
if (!exito)
|
if (!exito)
|
||||||
{
|
{
|
||||||
if (error == "Usuario no encontrado.") return NotFound(new { message = error });
|
if (error == "Usuario no encontrado.") return NotFound(new { message = error });
|
||||||
return BadRequest(new { message = error });
|
return BadRequest(new { message = error });
|
||||||
}
|
}
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: api/usuarios/{idUsuarioAfectado}/historial
|
||||||
|
[HttpGet("{idUsuarioAfectado:int}/historial")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<UsuarioHistorialDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
public async Task<IActionResult> GetHistorialDeUsuario(int idUsuarioAfectado, [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta)
|
||||||
|
{
|
||||||
|
// Necesitas un permiso para ver historial, ej: "AU001" o uno más granular "AU_USR_VIEW_SINGLE"
|
||||||
|
// O si solo SuperAdmin puede ver historiales específicos.
|
||||||
|
if (!TienePermiso("AU001")) // Asumiendo AU001 = Ver Historial de Auditoría (General o Usuarios)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Acceso denegado a GetHistorialDeUsuario para Usuario ID {UserId}", GetCurrentUserId() ?? 0);
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
var usuarioExiste = await _usuarioService.ObtenerPorIdAsync(idUsuarioAfectado);
|
||||||
|
if (usuarioExiste == null)
|
||||||
|
{
|
||||||
|
return NotFound(new { message = $"Usuario con ID {idUsuarioAfectado} no encontrado." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var historial = await _usuarioService.ObtenerHistorialPorUsuarioIdAsync(idUsuarioAfectado, fechaDesde, fechaHasta);
|
||||||
|
return Ok(historial);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET: api/usuarios/historial (Para todos los usuarios, con filtros)
|
||||||
|
[HttpGet("historial")]
|
||||||
|
[ProducesResponseType(typeof(IEnumerable<UsuarioHistorialDto>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
|
public async Task<IActionResult> GetTodoElHistorialDeUsuarios(
|
||||||
|
[FromQuery] DateTime? fechaDesde,
|
||||||
|
[FromQuery] DateTime? fechaHasta,
|
||||||
|
[FromQuery] int? idUsuarioModifico,
|
||||||
|
[FromQuery] string? tipoModificacion)
|
||||||
|
{
|
||||||
|
if (!TienePermiso("AU001")) // Mismo permiso general de auditoría
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Acceso denegado a GetTodoElHistorialDeUsuarios para Usuario ID {UserId}", GetCurrentUserId() ?? 0);
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
var historial = await _usuarioService.ObtenerTodoElHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion);
|
||||||
|
return Ok(historial);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using GestionIntegral.Api.Models.Contables;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Contables
|
||||||
|
{
|
||||||
|
public interface INotaCreditoDebitoRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<NotaCreditoDebito>> GetAllAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
string? destino, int? idDestino, int? idEmpresa, string? tipoNota);
|
||||||
|
|
||||||
|
Task<NotaCreditoDebito?> GetByIdAsync(int idNota);
|
||||||
|
Task<NotaCreditoDebito?> CreateAsync(NotaCreditoDebito nuevaNota, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(NotaCreditoDebito notaAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteAsync(int idNota, int idUsuario, IDbTransaction transaction);
|
||||||
|
// No se suele validar unicidad por referencia, ya que podría repetirse.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using GestionIntegral.Api.Models.Contables;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Contables
|
||||||
|
{
|
||||||
|
public interface IPagoDistribuidorRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<PagoDistribuidor>> GetAllAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
int? idDistribuidor, int? idEmpresa, string? tipoMovimiento);
|
||||||
|
|
||||||
|
Task<PagoDistribuidor?> GetByIdAsync(int idPago);
|
||||||
|
Task<PagoDistribuidor?> CreateAsync(PagoDistribuidor nuevoPago, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Contables;
|
||||||
|
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.Contables
|
||||||
|
{
|
||||||
|
public class NotaCreditoDebitoRepository : INotaCreditoDebitoRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<NotaCreditoDebitoRepository> _log;
|
||||||
|
|
||||||
|
public NotaCreditoDebitoRepository(DbConnectionFactory cf, ILogger<NotaCreditoDebitoRepository> log)
|
||||||
|
{
|
||||||
|
_cf = cf;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SelectQueryBase() => @"
|
||||||
|
SELECT
|
||||||
|
Id_Nota AS IdNota, Destino, Id_Destino AS IdDestino, Referencia, Tipo, Fecha,
|
||||||
|
Monto, Observaciones, Id_Empresa AS IdEmpresa
|
||||||
|
FROM dbo.cue_CreditosDebitos";
|
||||||
|
|
||||||
|
public async Task<IEnumerable<NotaCreditoDebito>> GetAllAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
string? destino, int? idDestino, int? idEmpresa, string? tipoNota)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(SelectQueryBase());
|
||||||
|
sqlBuilder.Append(" 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 (!string.IsNullOrWhiteSpace(destino)) { sqlBuilder.Append(" AND Destino = @DestinoParam"); parameters.Add("DestinoParam", destino); }
|
||||||
|
if (idDestino.HasValue) { sqlBuilder.Append(" AND Id_Destino = @IdDestinoParam"); parameters.Add("IdDestinoParam", idDestino.Value); }
|
||||||
|
if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
|
||||||
|
if (!string.IsNullOrWhiteSpace(tipoNota)) { sqlBuilder.Append(" AND Tipo = @TipoParam"); parameters.Add("TipoParam", tipoNota); }
|
||||||
|
|
||||||
|
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Nota DESC;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<NotaCreditoDebito>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener todas las Notas de Crédito/Débito.");
|
||||||
|
return Enumerable.Empty<NotaCreditoDebito>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NotaCreditoDebito?> GetByIdAsync(int idNota)
|
||||||
|
{
|
||||||
|
var sql = SelectQueryBase() + " WHERE Id_Nota = @IdNotaParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<NotaCreditoDebito>(sql, new { IdNotaParam = idNota });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Nota C/D por ID: {IdNota}", idNota);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NotaCreditoDebito?> CreateAsync(NotaCreditoDebito nuevaNota, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.cue_CreditosDebitos (Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa)
|
||||||
|
OUTPUT INSERTED.Id_Nota AS IdNota, INSERTED.Destino, INSERTED.Id_Destino AS IdDestino, INSERTED.Referencia,
|
||||||
|
INSERTED.Tipo, INSERTED.Fecha, INSERTED.Monto, INSERTED.Observaciones, INSERTED.Id_Empresa AS IdEmpresa
|
||||||
|
VALUES (@Destino, @IdDestino, @Referencia, @Tipo, @Fecha, @Monto, @Observaciones, @IdEmpresa);";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.cue_CreditosDebitos_H
|
||||||
|
(Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<NotaCreditoDebito>(sqlInsert, nuevaNota, transaction);
|
||||||
|
if (inserted == null || inserted.IdNota == 0) throw new DataException("Error al crear la nota o ID no generado.");
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdNotaHist = inserted.IdNota, DestinoHist = inserted.Destino, IdDestinoHist = inserted.IdDestino,
|
||||||
|
ReferenciaHist = inserted.Referencia, TipoHist = inserted.Tipo, FechaHist = inserted.Fecha, MontoHist = inserted.Monto,
|
||||||
|
ObservacionesHist = inserted.Observaciones, IdEmpresaHist = inserted.IdEmpresa,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creada"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(NotaCreditoDebito notaAActualizar, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<NotaCreditoDebito>(
|
||||||
|
SelectQueryBase() + " WHERE Id_Nota = @IdNotaParam",
|
||||||
|
new { IdNotaParam = notaAActualizar.IdNota }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Nota de Crédito/Débito no encontrada.");
|
||||||
|
|
||||||
|
// Solo se permite actualizar Monto y Observaciones
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.cue_CreditosDebitos SET
|
||||||
|
Monto = @Monto, Observaciones = @Observaciones
|
||||||
|
WHERE Id_Nota = @IdNota;";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.cue_CreditosDebitos_H
|
||||||
|
(Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdNotaHist = actual.IdNota, DestinoHist = actual.Destino, IdDestinoHist = actual.IdDestino, ReferenciaHist = actual.Referencia,
|
||||||
|
TipoHist = actual.Tipo, FechaHist = actual.Fecha, MontoHist = actual.Monto, // Valor ANTERIOR
|
||||||
|
ObservacionesHist = actual.Observaciones, IdEmpresaHist = actual.IdEmpresa, // Valor ANTERIOR
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizada"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new {
|
||||||
|
notaAActualizar.Monto,
|
||||||
|
notaAActualizar.Observaciones,
|
||||||
|
notaAActualizar.IdNota
|
||||||
|
} , transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idNota, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<NotaCreditoDebito>(
|
||||||
|
SelectQueryBase() + " WHERE Id_Nota = @IdNotaParam",
|
||||||
|
new { IdNotaParam = idNota }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Nota de Crédito/Débito no encontrada para eliminar.");
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.cue_CreditosDebitos WHERE Id_Nota = @IdNotaParam";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.cue_CreditosDebitos_H
|
||||||
|
(Id_Nota, Destino, Id_Destino, Referencia, Tipo, Fecha, Monto, Observaciones, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdNotaHist, @DestinoHist, @IdDestinoHist, @ReferenciaHist, @TipoHist, @FechaHist, @MontoHist, @ObservacionesHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdNotaHist = actual.IdNota, DestinoHist = actual.Destino, IdDestinoHist = actual.IdDestino, ReferenciaHist = actual.Referencia,
|
||||||
|
TipoHist = actual.Tipo, FechaHist = actual.Fecha, MontoHist = actual.Monto, ObservacionesHist = actual.Observaciones, IdEmpresaHist = actual.IdEmpresa,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminada"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdNotaParam = idNota }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Contables;
|
||||||
|
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.Contables
|
||||||
|
{
|
||||||
|
public class PagoDistribuidorRepository : IPagoDistribuidorRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<PagoDistribuidorRepository> _log;
|
||||||
|
|
||||||
|
public PagoDistribuidorRepository(DbConnectionFactory cf, ILogger<PagoDistribuidorRepository> log)
|
||||||
|
{
|
||||||
|
_cf = cf;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SelectQueryBase() => @"
|
||||||
|
SELECT
|
||||||
|
Id_Pago AS IdPago, Id_Distribuidor AS IdDistribuidor, Fecha, TipoMovimiento, Recibo, Monto,
|
||||||
|
Id_TipoPago AS IdTipoPago, Detalle, Id_Empresa AS IdEmpresa
|
||||||
|
FROM dbo.cue_PagosDistribuidor";
|
||||||
|
|
||||||
|
public async Task<IEnumerable<PagoDistribuidor>> GetAllAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
int? idDistribuidor, int? idEmpresa, string? tipoMovimiento)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(SelectQueryBase());
|
||||||
|
sqlBuilder.Append(" 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 (idDistribuidor.HasValue) { sqlBuilder.Append(" AND Id_Distribuidor = @IdDistribuidorParam"); parameters.Add("IdDistribuidorParam", idDistribuidor.Value); }
|
||||||
|
if (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
|
||||||
|
if (!string.IsNullOrWhiteSpace(tipoMovimiento)) { sqlBuilder.Append(" AND TipoMovimiento = @TipoMovParam"); parameters.Add("TipoMovParam", tipoMovimiento); }
|
||||||
|
|
||||||
|
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Pago DESC;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<PagoDistribuidor>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener todos los Pagos de Distribuidores.");
|
||||||
|
return Enumerable.Empty<PagoDistribuidor>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PagoDistribuidor?> GetByIdAsync(int idPago)
|
||||||
|
{
|
||||||
|
var sql = SelectQueryBase() + " WHERE Id_Pago = @IdPagoParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<PagoDistribuidor>(sql, new { IdPagoParam = idPago });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener PagoDistribuidor por ID: {IdPago}", idPago);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExistsByReciboAndTipoMovimientoAsync(int recibo, string tipoMovimiento, int? excludeIdPago = null)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.cue_PagosDistribuidor WHERE Recibo = @ReciboParam AND TipoMovimiento = @TipoMovParam");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("ReciboParam", recibo);
|
||||||
|
parameters.Add("TipoMovParam", tipoMovimiento);
|
||||||
|
|
||||||
|
if (excludeIdPago.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Id_Pago != @ExcludeIdPagoParam");
|
||||||
|
parameters.Add("ExcludeIdPagoParam", excludeIdPago.Value);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en ExistsByReciboAndTipoMovimientoAsync. Recibo: {Recibo}, Tipo: {Tipo}", recibo, tipoMovimiento);
|
||||||
|
return true; // Asumir que existe en caso de error para prevenir duplicados
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PagoDistribuidor?> CreateAsync(PagoDistribuidor nuevoPago, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.cue_PagosDistribuidor (Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa)
|
||||||
|
OUTPUT INSERTED.Id_Pago AS IdPago, INSERTED.Id_Distribuidor AS IdDistribuidor, INSERTED.Fecha, INSERTED.TipoMovimiento,
|
||||||
|
INSERTED.Recibo, INSERTED.Monto, INSERTED.Id_TipoPago AS IdTipoPago, INSERTED.Detalle, INSERTED.Id_Empresa AS IdEmpresa
|
||||||
|
VALUES (@IdDistribuidor, @Fecha, @TipoMovimiento, @Recibo, @Monto, @IdTipoPago, @Detalle, @IdEmpresa);";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.cue_PagosDistribuidor_H
|
||||||
|
(Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<PagoDistribuidor>(sqlInsert, nuevoPago, transaction);
|
||||||
|
if (inserted == null || inserted.IdPago == 0) throw new DataException("Error al crear pago o ID no generado.");
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdPagoHist = inserted.IdPago, IdDistribuidorHist = inserted.IdDistribuidor, FechaHist = inserted.Fecha,
|
||||||
|
TipoMovimientoHist = inserted.TipoMovimiento, ReciboHist = inserted.Recibo, MontoHist = inserted.Monto,
|
||||||
|
IdTipoPagoHist = inserted.IdTipoPago, DetalleHist = inserted.Detalle, IdEmpresaHist = inserted.IdEmpresa,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Creado"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(PagoDistribuidor pagoAActualizar, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PagoDistribuidor>(
|
||||||
|
SelectQueryBase() + " WHERE Id_Pago = @IdPagoParam",
|
||||||
|
new { IdPagoParam = pagoAActualizar.IdPago }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Pago no encontrado.");
|
||||||
|
|
||||||
|
// Campos que se permiten actualizar: Monto, Id_TipoPago, Detalle
|
||||||
|
// Otros campos como IdDistribuidor, Fecha, TipoMovimiento, Recibo, IdEmpresa no deberían cambiar
|
||||||
|
// para un pago ya registrado. Si necesitan cambiar, se debería anular/eliminar y crear uno nuevo.
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.cue_PagosDistribuidor SET
|
||||||
|
Monto = @Monto, Id_TipoPago = @IdTipoPago, Detalle = @Detalle
|
||||||
|
WHERE Id_Pago = @IdPago;";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.cue_PagosDistribuidor_H
|
||||||
|
(Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdPagoHist = actual.IdPago, IdDistribuidorHist = actual.IdDistribuidor, FechaHist = actual.Fecha,
|
||||||
|
TipoMovimientoHist = actual.TipoMovimiento, ReciboHist = actual.Recibo, MontoHist = actual.Monto, // Valor ANTERIOR
|
||||||
|
IdTipoPagoHist = actual.IdTipoPago, DetalleHist = actual.Detalle, IdEmpresaHist = actual.IdEmpresa, // Valores ANTERIORES
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Actualizado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, pagoAActualizar, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idPago, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<PagoDistribuidor>(
|
||||||
|
SelectQueryBase() + " WHERE Id_Pago = @IdPagoParam",
|
||||||
|
new { IdPagoParam = idPago }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Pago no encontrado para eliminar.");
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.cue_PagosDistribuidor WHERE Id_Pago = @IdPagoParam";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.cue_PagosDistribuidor_H
|
||||||
|
(Id_Pago, Id_Distribuidor, Fecha, TipoMovimiento, Recibo, Monto, Id_TipoPago, Detalle, Id_Empresa, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdPagoHist, @IdDistribuidorHist, @FechaHist, @TipoMovimientoHist, @ReciboHist, @MontoHist, @IdTipoPagoHist, @DetalleHist, @IdEmpresaHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdPagoHist = actual.IdPago, IdDistribuidorHist = actual.IdDistribuidor, FechaHist = actual.Fecha,
|
||||||
|
TipoMovimientoHist = actual.TipoMovimiento, ReciboHist = actual.Recibo, MontoHist = actual.Monto,
|
||||||
|
IdTipoPagoHist = actual.IdTipoPago, DetalleHist = actual.Detalle, IdEmpresaHist = actual.IdEmpresa,
|
||||||
|
IdUsuarioHist = idUsuario, FechaModHist = DateTime.Now, TipoModHist = "Eliminado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdPagoParam = idPago }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
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 ControlDevolucionesRepository : IControlDevolucionesRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<ControlDevolucionesRepository> _log;
|
||||||
|
|
||||||
|
public ControlDevolucionesRepository(DbConnectionFactory cf, ILogger<ControlDevolucionesRepository> log)
|
||||||
|
{
|
||||||
|
_cf = cf;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SelectQueryBase() => @"
|
||||||
|
SELECT
|
||||||
|
Id_Control AS IdControl, Id_Empresa AS IdEmpresa, Fecha,
|
||||||
|
Entrada, Sobrantes, Detalle, SinCargo
|
||||||
|
FROM dbo.dist_dtCtrlDevoluciones";
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ControlDevoluciones>> GetAllAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idEmpresa)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(SelectQueryBase());
|
||||||
|
sqlBuilder.Append(" 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 (idEmpresa.HasValue) { sqlBuilder.Append(" AND Id_Empresa = @IdEmpresaParam"); parameters.Add("IdEmpresaParam", idEmpresa.Value); }
|
||||||
|
|
||||||
|
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Empresa;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<ControlDevoluciones>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener todos los Controles de Devoluciones.");
|
||||||
|
return Enumerable.Empty<ControlDevoluciones>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ControlDevoluciones?> GetByIdAsync(int idControl)
|
||||||
|
{
|
||||||
|
var sql = SelectQueryBase() + " WHERE Id_Control = @IdControlParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<ControlDevoluciones>(sql, new { IdControlParam = idControl });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener ControlDevoluciones por ID: {IdControl}", idControl);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ControlDevoluciones?> GetByEmpresaAndFechaAsync(int idEmpresa, DateTime fecha, IDbTransaction? transaction = null)
|
||||||
|
{
|
||||||
|
var sql = SelectQueryBase() + " WHERE Id_Empresa = @IdEmpresaParam AND Fecha = @FechaParam";
|
||||||
|
var cn = transaction?.Connection ?? _cf.CreateConnection();
|
||||||
|
bool ownConnection = transaction == null;
|
||||||
|
ControlDevoluciones? result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
|
||||||
|
result = await cn.QuerySingleOrDefaultAsync<ControlDevoluciones>(sql, new { IdEmpresaParam = idEmpresa, 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<ControlDevoluciones?> CreateAsync(ControlDevoluciones nuevoControl, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.dist_dtCtrlDevoluciones (Id_Empresa, Fecha, Entrada, Sobrantes, Detalle, SinCargo)
|
||||||
|
OUTPUT INSERTED.Id_Control AS IdControl, INSERTED.Id_Empresa AS IdEmpresa, INSERTED.Fecha,
|
||||||
|
INSERTED.Entrada, INSERTED.Sobrantes, INSERTED.Detalle, INSERTED.SinCargo
|
||||||
|
VALUES (@IdEmpresa, @Fecha, @Entrada, @Sobrantes, @Detalle, @SinCargo);";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_dtCtrlDevoluciones_H
|
||||||
|
(Id_Control, Id_Empresa, Fecha, Entrada, Sobrantes, Detalle, SinCargo, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdControlParam, @IdEmpresaParam, @FechaParam, @EntradaParam, @SobrantesParam, @DetalleParam, @SinCargoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<ControlDevoluciones>(sqlInsert, nuevoControl, transaction);
|
||||||
|
if (inserted == null || inserted.IdControl == 0) throw new DataException("Error al crear control de devoluciones o ID no generado.");
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdControlParam = inserted.IdControl, IdEmpresaParam = inserted.IdEmpresa, FechaParam = inserted.Fecha,
|
||||||
|
EntradaParam = inserted.Entrada, SobrantesParam = inserted.Sobrantes, DetalleParam = inserted.Detalle, SinCargoParam = inserted.SinCargo,
|
||||||
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(ControlDevoluciones controlAActualizar, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<ControlDevoluciones>(
|
||||||
|
SelectQueryBase() + " WHERE Id_Control = @IdControlParam",
|
||||||
|
new { IdControlParam = controlAActualizar.IdControl }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Control de devoluciones no encontrado.");
|
||||||
|
|
||||||
|
// En este caso, Id_Empresa y Fecha no deberían cambiar para un registro existente
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.dist_dtCtrlDevoluciones SET
|
||||||
|
Entrada = @Entrada, Sobrantes = @Sobrantes, Detalle = @Detalle, SinCargo = @SinCargo
|
||||||
|
WHERE Id_Control = @IdControl;";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_dtCtrlDevoluciones_H
|
||||||
|
(Id_Control, Id_Empresa, Fecha, Entrada, Sobrantes, Detalle, SinCargo, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdControlParam, @IdEmpresaParam, @FechaParam, @EntradaParam, @SobrantesParam, @DetalleParam, @SinCargoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdControlParam = actual.IdControl, IdEmpresaParam = actual.IdEmpresa, FechaParam = actual.Fecha, // Datos originales
|
||||||
|
EntradaParam = actual.Entrada, SobrantesParam = actual.Sobrantes, DetalleParam = actual.Detalle, SinCargoParam = actual.SinCargo, // Valores ANTERIORES
|
||||||
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, controlAActualizar, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idControl, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<ControlDevoluciones>(
|
||||||
|
SelectQueryBase() + " WHERE Id_Control = @IdControlParam",
|
||||||
|
new { IdControlParam = idControl }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Control de devoluciones no encontrado para eliminar.");
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.dist_dtCtrlDevoluciones WHERE Id_Control = @IdControlParam";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_dtCtrlDevoluciones_H
|
||||||
|
(Id_Control, Id_Empresa, Fecha, Entrada, Sobrantes, Detalle, SinCargo, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdControlParam, @IdEmpresaParam, @FechaParam, @EntradaParam, @SobrantesParam, @DetalleParam, @SinCargoParam, @IdUsuarioParam, @FechaModParam, @TipoModParam);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new {
|
||||||
|
IdControlParam = actual.IdControl, IdEmpresaParam = actual.IdEmpresa, FechaParam = actual.Fecha,
|
||||||
|
EntradaParam = actual.Entrada, SobrantesParam = actual.Sobrantes, DetalleParam = actual.Detalle, SinCargoParam = actual.SinCargo,
|
||||||
|
IdUsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Eliminado"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlDelete, new { IdControlParam = idControl }, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
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 EntradaSalidaCanillaRepository : IEntradaSalidaCanillaRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<EntradaSalidaCanillaRepository> _log;
|
||||||
|
|
||||||
|
public EntradaSalidaCanillaRepository(DbConnectionFactory cf, ILogger<EntradaSalidaCanillaRepository> log)
|
||||||
|
{
|
||||||
|
_cf = cf;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SelectQueryBase() => @"
|
||||||
|
SELECT
|
||||||
|
Id_Parte AS IdParte, Id_Publicacion AS IdPublicacion, Id_Canilla AS IdCanilla,
|
||||||
|
Fecha, CantSalida, CantEntrada, Id_Precio AS IdPrecio, Id_Recargo AS IdRecargo,
|
||||||
|
Id_PorcMon AS IdPorcMon, Observacion, Liquidado, FechaLiquidado, UserLiq
|
||||||
|
FROM dbo.dist_EntradasSalidasCanillas";
|
||||||
|
|
||||||
|
public async Task<IEnumerable<EntradaSalidaCanilla>> GetAllAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
int? idPublicacion, int? idCanilla, bool? liquidados)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(SelectQueryBase());
|
||||||
|
sqlBuilder.Append(" 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 (idCanilla.HasValue) { sqlBuilder.Append(" AND Id_Canilla = @IdCanillaParam"); parameters.Add("IdCanillaParam", idCanilla.Value); }
|
||||||
|
if (liquidados.HasValue) { sqlBuilder.Append(" AND Liquidado = @LiquidadoParam"); parameters.Add("LiquidadoParam", liquidados.Value); }
|
||||||
|
|
||||||
|
sqlBuilder.Append(" ORDER BY Fecha DESC, Id_Canilla, Id_Publicacion, Id_Parte DESC;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<EntradaSalidaCanilla>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Entradas/Salidas de Canillitas.");
|
||||||
|
return Enumerable.Empty<EntradaSalidaCanilla>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EntradaSalidaCanilla?> GetByIdAsync(int idParte)
|
||||||
|
{
|
||||||
|
var sql = SelectQueryBase() + " WHERE Id_Parte = @IdParteParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<EntradaSalidaCanilla>(sql, new { IdParteParam = idParte });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener EntradaSalidaCanilla por ID: {IdParte}", idParte);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<EntradaSalidaCanilla>> GetByIdsAsync(IEnumerable<int> idsPartes, IDbTransaction? transaction = null)
|
||||||
|
{
|
||||||
|
if (idsPartes == null || !idsPartes.Any()) return Enumerable.Empty<EntradaSalidaCanilla>();
|
||||||
|
var sql = SelectQueryBase() + " WHERE Id_Parte IN @IdsPartesParam";
|
||||||
|
|
||||||
|
var cn = transaction?.Connection ?? _cf.CreateConnection();
|
||||||
|
bool ownConnection = transaction == null;
|
||||||
|
IEnumerable<EntradaSalidaCanilla> result = Enumerable.Empty<EntradaSalidaCanilla>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ownConnection && cn.State == ConnectionState.Closed && cn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
|
||||||
|
result = await cn.QueryAsync<EntradaSalidaCanilla>(sql, new { IdsPartesParam = idsPartes }, 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<bool> ExistsByPublicacionCanillaFechaAsync(int idPublicacion, int idCanilla, DateTime fecha, IDbTransaction? transaction = null, int? excludeIdParte = null)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_EntradasSalidasCanillas WHERE Id_Publicacion = @IdPubParam AND Id_Canilla = @IdCanParam AND Fecha = @FechaParam");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("IdPubParam", idPublicacion);
|
||||||
|
parameters.Add("IdCanParam", idCanilla);
|
||||||
|
parameters.Add("FechaParam", fecha.Date);
|
||||||
|
|
||||||
|
if (excludeIdParte.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Id_Parte != @ExcludeIdParteParam");
|
||||||
|
parameters.Add("ExcludeIdParteParam", excludeIdParte.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var connection = transaction?.Connection ?? _cf.CreateConnection();
|
||||||
|
bool ownConnection = transaction == null;
|
||||||
|
bool result = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ownConnection && connection.State == ConnectionState.Closed && connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync();
|
||||||
|
|
||||||
|
result = await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters, transaction);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en ExistsByPublicacionCanillaFechaAsync.");
|
||||||
|
return true; // Asumir que existe en caso de error
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (ownConnection && connection.State == ConnectionState.Open && connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync();
|
||||||
|
if (ownConnection) (connection as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<EntradaSalidaCanilla?> CreateAsync(EntradaSalidaCanilla nuevoES, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.dist_EntradasSalidasCanillas
|
||||||
|
(Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Liquidado, FechaLiquidado, UserLiq)
|
||||||
|
OUTPUT INSERTED.Id_Parte AS IdParte, INSERTED.Id_Publicacion AS IdPublicacion, INSERTED.Id_Canilla AS IdCanilla, INSERTED.Fecha,
|
||||||
|
INSERTED.CantSalida, INSERTED.CantEntrada, INSERTED.Id_Precio AS IdPrecio, INSERTED.Id_Recargo AS IdRecargo,
|
||||||
|
INSERTED.Id_PorcMon AS IdPorcMon, INSERTED.Observacion, INSERTED.Liquidado, INSERTED.FechaLiquidado, INSERTED.UserLiq
|
||||||
|
VALUES (@IdPublicacion, @IdCanilla, @Fecha, @CantSalida, @CantEntrada, @IdPrecio, @IdRecargo, @IdPorcMon, @Observacion, @Liquidado, @FechaLiquidado, @UserLiq);";
|
||||||
|
|
||||||
|
var inserted = await transaction.Connection!.QuerySingleAsync<EntradaSalidaCanilla>(sqlInsert, nuevoES, transaction);
|
||||||
|
if (inserted == null || inserted.IdParte == 0) throw new DataException("Error al crear E/S Canilla o ID no generado.");
|
||||||
|
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
|
||||||
|
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
// Liquidado, FechaLiquidado, UserLiq no van al historial de creación, sino al de liquidación.
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
|
||||||
|
{
|
||||||
|
IdParteHist = inserted.IdParte,
|
||||||
|
IdPubHist = inserted.IdPublicacion,
|
||||||
|
IdCanillaHist = inserted.IdCanilla,
|
||||||
|
FechaHist = inserted.Fecha,
|
||||||
|
CantSalidaHist = inserted.CantSalida,
|
||||||
|
CantEntradaHist = inserted.CantEntrada,
|
||||||
|
IdPrecioHist = inserted.IdPrecio,
|
||||||
|
IdRecargoHist = inserted.IdRecargo,
|
||||||
|
IdPorcMonHist = inserted.IdPorcMon,
|
||||||
|
ObsHist = inserted.Observacion,
|
||||||
|
IdUsuarioHist = idUsuario,
|
||||||
|
FechaModHist = DateTime.Now,
|
||||||
|
TipoModHist = "Creada"
|
||||||
|
}, transaction);
|
||||||
|
return inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(EntradaSalidaCanilla esAActualizar, int idUsuario, IDbTransaction transaction, string tipoMod = "Actualizada")
|
||||||
|
{
|
||||||
|
var actual = await transaction.Connection!.QuerySingleOrDefaultAsync<EntradaSalidaCanilla>(SelectQueryBase() + " WHERE Id_Parte = @IdParteParam",
|
||||||
|
new { IdParteParam = esAActualizar.IdParte }, transaction);
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Registro E/S Canilla no encontrado.");
|
||||||
|
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.dist_EntradasSalidasCanillas SET
|
||||||
|
CantSalida = @CantSalida, CantEntrada = @CantEntrada, Observacion = @Observacion,
|
||||||
|
Liquidado = @Liquidado, FechaLiquidado = @FechaLiquidado, UserLiq = @UserLiq,
|
||||||
|
Id_Precio = @IdPrecio, Id_Recargo = @IdRecargo, Id_PorcMon = @IdPorcMon
|
||||||
|
-- No se permite cambiar Publicacion, Canilla, Fecha directamente aquí.
|
||||||
|
WHERE Id_Parte = @IdParte;";
|
||||||
|
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
|
||||||
|
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
|
||||||
|
{
|
||||||
|
IdParteHist = actual.IdParte,
|
||||||
|
IdPubHist = actual.IdPublicacion,
|
||||||
|
IdCanillaHist = actual.IdCanilla,
|
||||||
|
FechaHist = actual.Fecha,
|
||||||
|
CantSalidaHist = actual.CantSalida,
|
||||||
|
CantEntradaHist = actual.CantEntrada,
|
||||||
|
IdPrecioHist = actual.IdPrecio,
|
||||||
|
IdRecargoHist = actual.IdRecargo,
|
||||||
|
IdPorcMonHist = actual.IdPorcMon,
|
||||||
|
ObsHist = actual.Observacion, // Valores ANTERIORES
|
||||||
|
IdUsuarioHist = idUsuario,
|
||||||
|
FechaModHist = DateTime.Now,
|
||||||
|
TipoModHist = tipoMod
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, esAActualizar, transaction);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> LiquidarAsync(IEnumerable<int> idsPartes, DateTime fechaLiquidacion, int idUsuarioLiquidador, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
// Primero, obtener los registros actuales para el historial
|
||||||
|
var movimientosALiquidar = await GetByIdsAsync(idsPartes, transaction);
|
||||||
|
if (!movimientosALiquidar.Any() || movimientosALiquidar.Count() != idsPartes.Distinct().Count())
|
||||||
|
{
|
||||||
|
_log.LogWarning("Intento de liquidar IdsPartes no encontrados o inconsistentes.");
|
||||||
|
return false; // O lanzar excepción
|
||||||
|
}
|
||||||
|
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.dist_EntradasSalidasCanillas SET
|
||||||
|
Liquidado = 1, FechaLiquidado = @FechaLiquidacionParam, UserLiq = @UserLiqParam
|
||||||
|
WHERE Id_Parte = @IdParteParam AND Liquidado = 0;"; // Solo liquidar los no liquidados
|
||||||
|
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
|
||||||
|
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
int totalRowsAffected = 0;
|
||||||
|
foreach (var mov in movimientosALiquidar)
|
||||||
|
{
|
||||||
|
if (mov.Liquidado) continue; // Ya estaba liquidado, no hacer nada ni registrar historial
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
|
||||||
|
{
|
||||||
|
IdParteHist = mov.IdParte,
|
||||||
|
IdPubHist = mov.IdPublicacion,
|
||||||
|
IdCanillaHist = mov.IdCanilla,
|
||||||
|
FechaHist = mov.Fecha,
|
||||||
|
CantSalidaHist = mov.CantSalida,
|
||||||
|
CantEntradaHist = mov.CantEntrada,
|
||||||
|
IdPrecioHist = mov.IdPrecio,
|
||||||
|
IdRecargoHist = mov.IdRecargo,
|
||||||
|
IdPorcMonHist = mov.IdPorcMon,
|
||||||
|
ObsHist = mov.Observacion,
|
||||||
|
IdUsuarioHist = idUsuarioLiquidador,
|
||||||
|
FechaModHist = DateTime.Now,
|
||||||
|
TipoModHist = "Liquidada"
|
||||||
|
}, transaction);
|
||||||
|
|
||||||
|
var rows = await transaction.Connection!.ExecuteAsync(sqlUpdate,
|
||||||
|
new { FechaLiquidacionParam = fechaLiquidacion.Date, UserLiqParam = idUsuarioLiquidador, IdParteParam = mov.IdParte },
|
||||||
|
transaction);
|
||||||
|
totalRowsAffected += rows;
|
||||||
|
}
|
||||||
|
// Se considera éxito si al menos una fila fue afectada (o si todas ya estaban liquidadas y no hubo errores)
|
||||||
|
// Si se pasaron IDs que no existen, GetByIdsAsync ya devolvería menos elementos.
|
||||||
|
return totalRowsAffected >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
var actual = await GetByIdAsync(idParte); // No necesita TX, solo para el historial
|
||||||
|
if (actual == null) throw new KeyNotFoundException("Registro E/S Canilla no encontrado para eliminar.");
|
||||||
|
if (actual.Liquidado) throw new InvalidOperationException("No se puede eliminar un movimiento liquidado.");
|
||||||
|
|
||||||
|
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.dist_EntradasSalidasCanillas WHERE Id_Parte = @IdParteParam";
|
||||||
|
const string sqlHistorico = @"
|
||||||
|
INSERT INTO dbo.dist_EntradasSalidasCanillas_H
|
||||||
|
(Id_Parte, Id_Publicacion, Id_Canilla, Fecha, CantSalida, CantEntrada, Id_Precio, Id_Recargo, Id_PorcMon, Observacion, Id_Usuario, FechaMod, TipoMod)
|
||||||
|
VALUES (@IdParteHist, @IdPubHist, @IdCanillaHist, @FechaHist, @CantSalidaHist, @CantEntradaHist, @IdPrecioHist, @IdRecargoHist, @IdPorcMonHist, @ObsHist, @IdUsuarioHist, @FechaModHist, @TipoModHist);";
|
||||||
|
|
||||||
|
await transaction.Connection!.ExecuteAsync(sqlHistorico, new
|
||||||
|
{
|
||||||
|
IdParteHist = actual.IdParte,
|
||||||
|
IdPubHist = actual.IdPublicacion,
|
||||||
|
IdCanillaHist = actual.IdCanilla,
|
||||||
|
FechaHist = actual.Fecha,
|
||||||
|
CantSalidaHist = actual.CantSalida,
|
||||||
|
CantEntradaHist = actual.CantEntrada,
|
||||||
|
IdPrecioHist = actual.IdPrecio,
|
||||||
|
IdRecargoHist = actual.IdRecargo,
|
||||||
|
IdPorcMonHist = actual.IdPorcMon,
|
||||||
|
ObsHist = 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,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 IControlDevolucionesRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<ControlDevoluciones>> GetAllAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idEmpresa);
|
||||||
|
Task<ControlDevoluciones?> GetByIdAsync(int idControl);
|
||||||
|
Task<ControlDevoluciones?> GetByEmpresaAndFechaAsync(int idEmpresa, DateTime fecha, IDbTransaction? transaction = null); // Para validar unicidad
|
||||||
|
Task<ControlDevoluciones?> CreateAsync(ControlDevoluciones nuevoControl, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(ControlDevoluciones controlAActualizar, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> DeleteAsync(int idControl, int idUsuario, IDbTransaction transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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 IEntradaSalidaCanillaRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<EntradaSalidaCanilla>> GetAllAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
int? idPublicacion, int? idCanilla, bool? liquidados);
|
||||||
|
|
||||||
|
Task<EntradaSalidaCanilla?> GetByIdAsync(int idParte);
|
||||||
|
Task<IEnumerable<EntradaSalidaCanilla>> GetByIdsAsync(IEnumerable<int> idsPartes, IDbTransaction? transaction = null); // Para liquidación masiva
|
||||||
|
Task<EntradaSalidaCanilla?> CreateAsync(EntradaSalidaCanilla nuevoES, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> UpdateAsync(EntradaSalidaCanilla esAActualizar, int idUsuario, IDbTransaction transaction, string tipoMod = "Actualizada");
|
||||||
|
Task<bool> DeleteAsync(int idParte, int idUsuario, IDbTransaction transaction);
|
||||||
|
Task<bool> LiquidarAsync(IEnumerable<int> idsPartes, DateTime fechaLiquidacion, int idUsuarioLiquidador, IDbTransaction transaction);
|
||||||
|
Task<bool> ExistsByPublicacionCanillaFechaAsync(int idPublicacion, int idCanilla, DateTime fecha, IDbTransaction? transaction = null, int? excludeIdParte = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Radios;
|
||||||
|
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.Radios
|
||||||
|
{
|
||||||
|
public class CancionRepository : ICancionRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<CancionRepository> _log;
|
||||||
|
|
||||||
|
public CancionRepository(DbConnectionFactory cf, ILogger<CancionRepository> log)
|
||||||
|
{
|
||||||
|
_cf = cf;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SelectWithAlias() => @"
|
||||||
|
SELECT
|
||||||
|
Id, Tema, CompositorAutor, Interprete, Sello, Placa, Pista, Introduccion,
|
||||||
|
Ritmo AS IdRitmo, -- Mapear columna Ritmo a propiedad IdRitmo del modelo Cancion
|
||||||
|
Formato, Album
|
||||||
|
FROM dbo.rad_dtCanciones";
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Cancion>> GetAllAsync(string? temaFilter, string? interpreteFilter, int? idRitmoFilter)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(SelectWithAlias());
|
||||||
|
sqlBuilder.Append(" WHERE 1=1");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(temaFilter))
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Tema LIKE @TemaParam");
|
||||||
|
parameters.Add("TemaParam", $"%{temaFilter}%");
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(interpreteFilter))
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Interprete LIKE @InterpreteParam");
|
||||||
|
parameters.Add("InterpreteParam", $"%{interpreteFilter}%");
|
||||||
|
}
|
||||||
|
if (idRitmoFilter.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Ritmo = @IdRitmoParam"); // La columna en DB es "Ritmo"
|
||||||
|
parameters.Add("IdRitmoParam", idRitmoFilter.Value);
|
||||||
|
}
|
||||||
|
sqlBuilder.Append(" ORDER BY Tema, Interprete;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<Cancion>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener todas las Canciones.");
|
||||||
|
return Enumerable.Empty<Cancion>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Cancion?> GetByIdAsync(int id)
|
||||||
|
{
|
||||||
|
var sql = SelectWithAlias() + " WHERE Id = @IdParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<Cancion>(sql, new { IdParam = id });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Canción por ID: {IdCancion}", id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExistsByTemaAndInterpreteAsync(string tema, string interprete, int? excludeId = null)
|
||||||
|
{
|
||||||
|
// La unicidad puede ser más compleja si se consideran otros campos como Álbum.
|
||||||
|
// Por ahora, un ejemplo simple.
|
||||||
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.rad_dtCanciones WHERE Tema = @TemaParam AND Interprete = @InterpreteParam");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("TemaParam", tema);
|
||||||
|
parameters.Add("InterpreteParam", interprete);
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Id != @ExcludeIdParam");
|
||||||
|
parameters.Add("ExcludeIdParam", excludeId.Value);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en ExistsByTemaAndInterpreteAsync. Tema: {Tema}, Interprete: {Interprete}", tema, interprete);
|
||||||
|
return true; // Asumir que existe en caso de error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<Cancion?> CreateAsync(Cancion nuevaCancion /*, int idUsuario, IDbTransaction transaction */)
|
||||||
|
{
|
||||||
|
// El modelo Cancion usa IdRitmo, pero la tabla rad_dtCanciones usa la columna "Ritmo" para el FK a rad_dtRitmos.Id
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.rad_dtCanciones (Tema, CompositorAutor, Interprete, Sello, Placa, Pista, Introduccion, Ritmo, Formato, Album)
|
||||||
|
OUTPUT INSERTED.Id, INSERTED.Tema, INSERTED.CompositorAutor, INSERTED.Interprete, INSERTED.Sello, INSERTED.Placa,
|
||||||
|
INSERTED.Pista, INSERTED.Introduccion, INSERTED.Ritmo AS IdRitmo, INSERTED.Formato, INSERTED.Album
|
||||||
|
VALUES (@Tema, @CompositorAutor, @Interprete, @Sello, @Placa, @Pista, @Introduccion, @IdRitmo, @Formato, @Album);";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
// El objeto nuevaCancion tiene la propiedad IdRitmo, que Dapper mapeará al parámetro @IdRitmo
|
||||||
|
return await connection.QuerySingleAsync<Cancion>(sqlInsert, nuevaCancion);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al crear Canción: {Tema} por {Interprete}", nuevaCancion.Tema, nuevaCancion.Interprete);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(Cancion cancionAActualizar /*, int idUsuario, IDbTransaction transaction */)
|
||||||
|
{
|
||||||
|
// Similar a Create, la columna en DB es "Ritmo"
|
||||||
|
const string sqlUpdate = @"
|
||||||
|
UPDATE dbo.rad_dtCanciones SET
|
||||||
|
Tema = @Tema, CompositorAutor = @CompositorAutor, Interprete = @Interprete, Sello = @Sello,
|
||||||
|
Placa = @Placa, Pista = @Pista, Introduccion = @Introduccion, Ritmo = @IdRitmo,
|
||||||
|
Formato = @Formato, Album = @Album
|
||||||
|
WHERE Id = @Id;";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, cancionAActualizar);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al actualizar Canción ID: {IdCancion}", cancionAActualizar.Id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int id /*, int idUsuario, IDbTransaction transaction */)
|
||||||
|
{
|
||||||
|
// IsInUseAsync no se implementó porque no hay dependencias directas obvias que impidan borrar una canción
|
||||||
|
// (a menos que las listas generadas se guarden y referencien IDs de canciones).
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.rad_dtCanciones WHERE Id = @IdParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id });
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al eliminar Canción ID: {IdCancion}", id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// IsInUseAsync podría ser relevante si las listas generadas referencian el Id de la canción
|
||||||
|
// Por ahora, lo omitimos.
|
||||||
|
public Task<bool> IsInUseAsync(int id)
|
||||||
|
{
|
||||||
|
_log.LogWarning("IsInUseAsync no implementado para CancionRepository.");
|
||||||
|
return Task.FromResult(false); // Asumir que no está en uso por ahora
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Cancion>> GetRandomCancionesAsync(int count)
|
||||||
|
{
|
||||||
|
// El modelo Cancion tiene la propiedad "Ritmo" que es el IdRitmo.
|
||||||
|
// El SelectWithAlias no es necesario aquí si solo tomamos las columnas que ya tiene Cancion.
|
||||||
|
// Si el modelo Cancion no tuviera la propiedad Ritmo (IdRitmo), entonces necesitarías el alias.
|
||||||
|
// Por simplicidad, y dado que el modelo Cancion sí tiene Ritmo (que es IdRitmo), no necesitamos un alias específico aquí.
|
||||||
|
var sql = $"SELECT TOP ({count}) Id, Tema, CompositorAutor, Interprete, Sello, Placa, Pista, Introduccion, Ritmo, Formato, Album FROM dbo.rad_dtCanciones ORDER BY NEWID()";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
// Dapper mapeará la columna "Ritmo" de la BD a la propiedad "Ritmo" del modelo Cancion.
|
||||||
|
return await connection.QueryAsync<Cancion>(sql);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener {Count} canciones aleatorias.", count);
|
||||||
|
return Enumerable.Empty<Cancion>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using GestionIntegral.Api.Models.Radios;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
// using System.Data; // Solo si se usa transacción para historial
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Radios
|
||||||
|
{
|
||||||
|
public interface ICancionRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<Cancion>> GetAllAsync(string? temaFilter, string? interpreteFilter, int? idRitmoFilter);
|
||||||
|
Task<Cancion?> GetByIdAsync(int id);
|
||||||
|
Task<Cancion?> CreateAsync(Cancion nuevaCancion /*, int idUsuario, IDbTransaction transaction - si hay historial */);
|
||||||
|
Task<bool> UpdateAsync(Cancion cancionAActualizar /*, int idUsuario, IDbTransaction transaction */);
|
||||||
|
Task<bool> DeleteAsync(int id /*, int idUsuario, IDbTransaction transaction */);
|
||||||
|
Task<bool> ExistsByTemaAndInterpreteAsync(string tema, string interprete, int? excludeId = null); // Ejemplo de unicidad
|
||||||
|
Task<IEnumerable<Cancion>> GetRandomCancionesAsync(int count);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using GestionIntegral.Api.Models.Radios;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
// using System.Data; // Solo si se usa transacción para historial
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Data.Repositories.Radios
|
||||||
|
{
|
||||||
|
public interface IRitmoRepository
|
||||||
|
{
|
||||||
|
Task<IEnumerable<Ritmo>> GetAllAsync(string? nombreFilter);
|
||||||
|
Task<Ritmo?> GetByIdAsync(int id);
|
||||||
|
Task<Ritmo?> CreateAsync(Ritmo nuevoRitmo /*, int idUsuario, IDbTransaction transaction - si hay historial */);
|
||||||
|
Task<bool> UpdateAsync(Ritmo ritmoAActualizar /*, int idUsuario, IDbTransaction transaction */);
|
||||||
|
Task<bool> DeleteAsync(int id /*, int idUsuario, IDbTransaction transaction */);
|
||||||
|
Task<bool> ExistsByNameAsync(string nombreRitmo, int? excludeId = null);
|
||||||
|
Task<bool> IsInUseAsync(int id); // Verificar si se usa en rad_dtCanciones
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
using Dapper;
|
||||||
|
using GestionIntegral.Api.Models.Radios;
|
||||||
|
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.Radios
|
||||||
|
{
|
||||||
|
public class RitmoRepository : IRitmoRepository
|
||||||
|
{
|
||||||
|
private readonly DbConnectionFactory _cf;
|
||||||
|
private readonly ILogger<RitmoRepository> _log;
|
||||||
|
|
||||||
|
public RitmoRepository(DbConnectionFactory cf, ILogger<RitmoRepository> log)
|
||||||
|
{
|
||||||
|
_cf = cf;
|
||||||
|
_log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Ritmo>> GetAllAsync(string? nombreFilter)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder("SELECT Id, Ritmo AS NombreRitmo FROM dbo.rad_dtRitmos WHERE 1=1");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
if (!string.IsNullOrWhiteSpace(nombreFilter))
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Ritmo LIKE @NombreFilterParam");
|
||||||
|
parameters.Add("NombreFilterParam", $"%{nombreFilter}%");
|
||||||
|
}
|
||||||
|
sqlBuilder.Append(" ORDER BY Ritmo;");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QueryAsync<Ritmo>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener todos los Ritmos.");
|
||||||
|
return Enumerable.Empty<Ritmo>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Ritmo?> GetByIdAsync(int id)
|
||||||
|
{
|
||||||
|
const string sql = "SELECT Id, Ritmo AS NombreRitmo FROM dbo.rad_dtRitmos WHERE Id = @IdParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleOrDefaultAsync<Ritmo>(sql, new { IdParam = id });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al obtener Ritmo por ID: {IdRitmo}", id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExistsByNameAsync(string nombreRitmo, int? excludeId = null)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.rad_dtRitmos WHERE Ritmo = @NombreRitmoParam");
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("NombreRitmoParam", nombreRitmo);
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND Id != @ExcludeIdParam");
|
||||||
|
parameters.Add("ExcludeIdParam", excludeId.Value);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.ExecuteScalarAsync<bool>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en ExistsByNameAsync para Ritmo: {NombreRitmo}", nombreRitmo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsInUseAsync(int id)
|
||||||
|
{
|
||||||
|
const string sql = "SELECT TOP 1 1 FROM dbo.rad_dtCanciones WHERE Ritmo = @IdParam"; // Columna 'Ritmo' en Canciones es FK a 'Id' en Ritmos
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.ExecuteScalarAsync<bool>(sql, new { IdParam = id });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error en IsInUseAsync para Ritmo ID: {IdRitmo}", id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Ritmo?> CreateAsync(Ritmo nuevoRitmo /*, int idUsuario, IDbTransaction transaction */)
|
||||||
|
{
|
||||||
|
// Sin historial, no se necesita transacción aquí para una sola inserción.
|
||||||
|
const string sqlInsert = @"
|
||||||
|
INSERT INTO dbo.rad_dtRitmos (Ritmo)
|
||||||
|
OUTPUT INSERTED.Id, INSERTED.Ritmo AS NombreRitmo
|
||||||
|
VALUES (@NombreRitmo);";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
return await connection.QuerySingleAsync<Ritmo>(sqlInsert, nuevoRitmo);
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al crear Ritmo: {NombreRitmo}", nuevoRitmo.NombreRitmo);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateAsync(Ritmo ritmoAActualizar /*, int idUsuario, IDbTransaction transaction */)
|
||||||
|
{
|
||||||
|
const string sqlUpdate = "UPDATE dbo.rad_dtRitmos SET Ritmo = @NombreRitmo WHERE Id = @Id;";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, ritmoAActualizar);
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al actualizar Ritmo ID: {IdRitmo}", ritmoAActualizar.Id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int id /*, int idUsuario, IDbTransaction transaction */)
|
||||||
|
{
|
||||||
|
const string sqlDelete = "DELETE FROM dbo.rad_dtRitmos WHERE Id = @IdParam";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _cf.CreateConnection();
|
||||||
|
var rowsAffected = await connection.ExecuteAsync(sqlDelete, new { IdParam = id });
|
||||||
|
return rowsAffected == 1;
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Error al eliminar Ritmo ID: {IdRitmo}", id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using GestionIntegral.Api.Models.Usuarios; // Para Usuario
|
using GestionIntegral.Api.Models.Usuarios; // Para Usuario
|
||||||
|
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
@@ -16,10 +17,10 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
// Task<bool> DeleteAsync(int id, int idUsuarioModificador, IDbTransaction transaction);
|
// Task<bool> DeleteAsync(int id, int idUsuarioModificador, IDbTransaction transaction);
|
||||||
Task<bool> SetPasswordAsync(int userId, string newHash, string newSalt, bool debeCambiarClave, int idUsuarioModificador, IDbTransaction transaction);
|
Task<bool> SetPasswordAsync(int userId, string newHash, string newSalt, bool debeCambiarClave, int idUsuarioModificador, IDbTransaction transaction);
|
||||||
Task<bool> UserExistsAsync(string username, int? excludeId = null);
|
Task<bool> UserExistsAsync(string username, int? excludeId = null);
|
||||||
|
|
||||||
// Para el DTO de listado
|
// Para el DTO de listado
|
||||||
Task<IEnumerable<(Usuario Usuario, string NombrePerfil)>> GetAllWithProfileNameAsync(string? userFilter, string? nombreFilter);
|
Task<IEnumerable<(Usuario Usuario, string NombrePerfil)>> GetAllWithProfileNameAsync(string? userFilter, string? nombreFilter);
|
||||||
Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id);
|
Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id);
|
||||||
|
Task<IEnumerable<UsuarioHistorialDto>> GetHistorialByUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta);
|
||||||
|
Task<IEnumerable<UsuarioHistorialDto>> GetAllHistorialAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModificoFilter, string? tipoModFilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using GestionIntegral.Api.Models.Usuarios;
|
using GestionIntegral.Api.Models.Usuarios;
|
||||||
|
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
@@ -102,7 +103,7 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public async Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id)
|
public async Task<(Usuario? Usuario, string? NombrePerfil)> GetByIdWithProfileNameAsync(int id)
|
||||||
{
|
{
|
||||||
const string sql = @"
|
const string sql = @"
|
||||||
SELECT u.*, p.perfil AS NombrePerfil
|
SELECT u.*, p.perfil AS NombrePerfil
|
||||||
@@ -162,7 +163,7 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error en UserExistsAsync para username: {Username}", username);
|
_logger.LogError(ex, "Error en UserExistsAsync para username: {Username}", username);
|
||||||
return true; // Asumir que existe para prevenir duplicados
|
return true; // Asumir que existe para prevenir duplicados
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,13 +230,20 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||||
{
|
{
|
||||||
IdUsuarioHist = usuarioActual.Id,
|
IdUsuarioHist = usuarioActual.Id,
|
||||||
UserAntHist = usuarioActual.User, UserNvoHist = usuarioAActualizar.User, // Aunque no cambiemos User, lo registramos
|
UserAntHist = usuarioActual.User,
|
||||||
HabilitadaAntHist = usuarioActual.Habilitada, HabilitadaNvaHist = usuarioAActualizar.Habilitada,
|
UserNvoHist = usuarioAActualizar.User, // Aunque no cambiemos User, lo registramos
|
||||||
SupAdminAntHist = usuarioActual.SupAdmin, SupAdminNvoHist = usuarioAActualizar.SupAdmin,
|
HabilitadaAntHist = usuarioActual.Habilitada,
|
||||||
NombreAntHist = usuarioActual.Nombre, NombreNvoHist = usuarioAActualizar.Nombre,
|
HabilitadaNvaHist = usuarioAActualizar.Habilitada,
|
||||||
ApellidoAntHist = usuarioActual.Apellido, ApellidoNvoHist = usuarioAActualizar.Apellido,
|
SupAdminAntHist = usuarioActual.SupAdmin,
|
||||||
IdPerfilAntHist = usuarioActual.IdPerfil, IdPerfilNvoHist = usuarioAActualizar.IdPerfil,
|
SupAdminNvoHist = usuarioAActualizar.SupAdmin,
|
||||||
DebeCambiarClaveAntHist = usuarioActual.DebeCambiarClave, DebeCambiarClaveNvaHist = usuarioAActualizar.DebeCambiarClave,
|
NombreAntHist = usuarioActual.Nombre,
|
||||||
|
NombreNvoHist = usuarioAActualizar.Nombre,
|
||||||
|
ApellidoAntHist = usuarioActual.Apellido,
|
||||||
|
ApellidoNvoHist = usuarioAActualizar.Apellido,
|
||||||
|
IdPerfilAntHist = usuarioActual.IdPerfil,
|
||||||
|
IdPerfilNvoHist = usuarioAActualizar.IdPerfil,
|
||||||
|
DebeCambiarClaveAntHist = usuarioActual.DebeCambiarClave,
|
||||||
|
DebeCambiarClaveNvaHist = usuarioAActualizar.DebeCambiarClave,
|
||||||
IdUsuarioModHist = idUsuarioModificador,
|
IdUsuarioModHist = idUsuarioModificador,
|
||||||
FechaModHist = DateTime.Now,
|
FechaModHist = DateTime.Now,
|
||||||
TipoModHist = "Actualizado"
|
TipoModHist = "Actualizado"
|
||||||
@@ -247,9 +255,9 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
|
|
||||||
public async Task<bool> SetPasswordAsync(int userId, string newHash, string newSalt, bool debeCambiarClave, int idUsuarioModificador, IDbTransaction transaction)
|
public async Task<bool> SetPasswordAsync(int userId, string newHash, string newSalt, bool debeCambiarClave, int idUsuarioModificador, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
var connection = transaction.Connection!;
|
var connection = transaction.Connection!;
|
||||||
var usuarioActual = await connection.QuerySingleOrDefaultAsync<Usuario>(
|
var usuarioActual = await connection.QuerySingleOrDefaultAsync<Usuario>(
|
||||||
"SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id", new { Id = userId }, transaction);
|
"SELECT * FROM dbo.gral_Usuarios WHERE Id = @Id", new { Id = userId }, transaction);
|
||||||
|
|
||||||
if (usuarioActual == null) throw new KeyNotFoundException("Usuario no encontrado para cambiar contraseña.");
|
if (usuarioActual == null) throw new KeyNotFoundException("Usuario no encontrado para cambiar contraseña.");
|
||||||
|
|
||||||
@@ -289,5 +297,122 @@ namespace GestionIntegral.Api.Data.Repositories.Usuarios
|
|||||||
}, transaction);
|
}, transaction);
|
||||||
return rowsAffected == 1;
|
return rowsAffected == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<UsuarioHistorialDto>> GetHistorialByUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(@"
|
||||||
|
SELECT
|
||||||
|
h.IdHist,
|
||||||
|
h.IdUsuario AS IdUsuarioAfectado,
|
||||||
|
uPrincipal.[User] AS UserAfectado, -- DELIMITADO CON []
|
||||||
|
h.UserAnt, h.UserNvo,
|
||||||
|
h.HabilitadaAnt, h.HabilitadaNva,
|
||||||
|
h.SupAdminAnt, h.SupAdminNvo,
|
||||||
|
h.NombreAnt, h.NombreNvo,
|
||||||
|
h.ApellidoAnt, h.ApellidoNvo,
|
||||||
|
h.IdPerfilAnt, h.IdPerfilNvo,
|
||||||
|
pAnt.perfil AS NombrePerfilAnt,
|
||||||
|
pNvo.perfil AS NombrePerfilNvo,
|
||||||
|
h.DebeCambiarClaveAnt, h.DebeCambiarClaveNva,
|
||||||
|
h.Id_UsuarioMod AS IdUsuarioModifico,
|
||||||
|
ISNULL(uMod.Nombre + ' ' + uMod.Apellido, 'Sistema') AS NombreUsuarioModifico,
|
||||||
|
h.FechaMod AS FechaModificacion,
|
||||||
|
h.TipoMod AS TipoModificacion
|
||||||
|
FROM dbo.gral_Usuarios_H h
|
||||||
|
INNER JOIN dbo.gral_Usuarios uPrincipal ON h.IdUsuario = uPrincipal.Id -- No necesita delimitador aquí si 'Id' es el nombre de la columna PK en gral_Usuarios
|
||||||
|
LEFT JOIN dbo.gral_Usuarios uMod ON h.Id_UsuarioMod = uMod.Id
|
||||||
|
LEFT JOIN dbo.gral_Perfiles pAnt ON h.IdPerfilAnt = pAnt.id
|
||||||
|
LEFT JOIN dbo.gral_Perfiles pNvo ON h.IdPerfilNvo = pNvo.id
|
||||||
|
WHERE h.IdUsuario = @IdUsuarioAfectadoParam");
|
||||||
|
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
parameters.Add("IdUsuarioAfectadoParam", idUsuarioAfectado);
|
||||||
|
|
||||||
|
if (fechaDesde.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam");
|
||||||
|
parameters.Add("FechaDesdeParam", fechaDesde.Value.Date);
|
||||||
|
}
|
||||||
|
if (fechaHasta.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam");
|
||||||
|
parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1)); // Hasta el final del día
|
||||||
|
}
|
||||||
|
sqlBuilder.Append(" ORDER BY h.FechaMod DESC, h.IdHist DESC;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<UsuarioHistorialDto>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener historial para Usuario ID: {IdUsuarioAfectado}", idUsuarioAfectado);
|
||||||
|
return Enumerable.Empty<UsuarioHistorialDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<UsuarioHistorialDto>> GetAllHistorialAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModificoFilter, string? tipoModFilter)
|
||||||
|
{
|
||||||
|
var sqlBuilder = new StringBuilder(@"
|
||||||
|
SELECT
|
||||||
|
h.IdHist,
|
||||||
|
h.IdUsuario AS IdUsuarioAfectado,
|
||||||
|
uPrincipal.[User] AS UserAfectado, -- DELIMITADO CON []
|
||||||
|
h.UserAnt, h.UserNvo,
|
||||||
|
h.HabilitadaAnt, h.HabilitadaNva,
|
||||||
|
h.SupAdminAnt, h.SupAdminNvo,
|
||||||
|
h.NombreAnt, h.NombreNvo,
|
||||||
|
h.ApellidoAnt, h.ApellidoNvo,
|
||||||
|
h.IdPerfilAnt, h.IdPerfilNvo,
|
||||||
|
pAnt.perfil AS NombrePerfilAnt,
|
||||||
|
pNvo.perfil AS NombrePerfilNvo,
|
||||||
|
h.DebeCambiarClaveAnt, h.DebeCambiarClaveNva,
|
||||||
|
h.Id_UsuarioMod AS IdUsuarioModifico,
|
||||||
|
ISNULL(uMod.Nombre + ' ' + uMod.Apellido, 'Sistema') AS NombreUsuarioModifico,
|
||||||
|
h.FechaMod AS FechaModificacion,
|
||||||
|
h.TipoMod AS TipoModificacion
|
||||||
|
FROM dbo.gral_Usuarios_H h
|
||||||
|
INNER JOIN dbo.gral_Usuarios uPrincipal ON h.IdUsuario = uPrincipal.Id
|
||||||
|
LEFT JOIN dbo.gral_Usuarios uMod ON h.Id_UsuarioMod = uMod.Id
|
||||||
|
LEFT JOIN dbo.gral_Perfiles pAnt ON h.IdPerfilAnt = pAnt.id
|
||||||
|
LEFT JOIN dbo.gral_Perfiles pNvo ON h.IdPerfilNvo = pNvo.id
|
||||||
|
WHERE 1=1");
|
||||||
|
|
||||||
|
var parameters = new DynamicParameters();
|
||||||
|
|
||||||
|
if (fechaDesde.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND h.FechaMod >= @FechaDesdeParam");
|
||||||
|
parameters.Add("FechaDesdeParam", fechaDesde.Value.Date);
|
||||||
|
}
|
||||||
|
if (fechaHasta.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND h.FechaMod <= @FechaHastaParam");
|
||||||
|
parameters.Add("FechaHastaParam", fechaHasta.Value.Date.AddDays(1).AddTicks(-1));
|
||||||
|
}
|
||||||
|
if (idUsuarioModificoFilter.HasValue)
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND h.Id_UsuarioMod = @IdUsuarioModFilterParam");
|
||||||
|
parameters.Add("IdUsuarioModFilterParam", idUsuarioModificoFilter.Value);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(tipoModFilter))
|
||||||
|
{
|
||||||
|
sqlBuilder.Append(" AND h.TipoMod LIKE @TipoModFilterParam");
|
||||||
|
parameters.Add("TipoModFilterParam", $"%{tipoModFilter}%");
|
||||||
|
}
|
||||||
|
sqlBuilder.Append(" ORDER BY h.FechaMod DESC, h.IdHist DESC;");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
return await connection.QueryAsync<UsuarioHistorialDto>(sqlBuilder.ToString(), parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al obtener todo el historial de Usuarios.");
|
||||||
|
return Enumerable.Empty<UsuarioHistorialDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
|
||||||
|
<PackageReference Include="NPOI" Version="2.7.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.9.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.9.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
namespace GestionIntegral.Api.Models.Contables
|
||||||
|
{
|
||||||
|
public class NotaCreditoDebito // Corresponde a cue_CreditosDebitos
|
||||||
|
{
|
||||||
|
public int IdNota { get; set; } // Id_Nota (PK, Identity)
|
||||||
|
public string Destino { get; set; } = string.Empty; // "Distribuidores" o "Canillas"
|
||||||
|
public int IdDestino { get; set; } // Id del distribuidor o canillita
|
||||||
|
public string? Referencia { get; set; } // varchar(50), NULL
|
||||||
|
public string Tipo { get; set; } = string.Empty; // "Debito" o "Credito"
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
public string? Observaciones { get; set; } // varchar(250), NULL
|
||||||
|
public int IdEmpresa { get; set; } // Empresa a la que corresponde el saldo afectado
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
namespace GestionIntegral.Api.Models.Contables
|
||||||
|
{
|
||||||
|
public class NotaCreditoDebitoHistorico // Corresponde a cue_CreditosDebitos_H
|
||||||
|
{
|
||||||
|
// No hay PK explícita en _H
|
||||||
|
public int Id_Nota { get; set; } // Coincide con columna en _H
|
||||||
|
public string Destino { get; set; } = string.Empty;
|
||||||
|
public int Id_Destino { get; set; }
|
||||||
|
public string? Referencia { get; set; }
|
||||||
|
public string Tipo { get; set; } = string.Empty;
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
public string? Observaciones { get; set; }
|
||||||
|
public int Id_Empresa { get; set; }
|
||||||
|
public int Id_Usuario { get; set; }
|
||||||
|
public DateTime FechaMod { get; set; }
|
||||||
|
public string TipoMod { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
namespace GestionIntegral.Api.Models.Contables
|
||||||
|
{
|
||||||
|
public class PagoDistribuidor // Corresponde a cue_PagosDistribuidor
|
||||||
|
{
|
||||||
|
public int IdPago { get; set; } // Id_Pago (PK, Identity)
|
||||||
|
public int IdDistribuidor { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public string TipoMovimiento { get; set; } = string.Empty; // "Recibido" o "Realizado"
|
||||||
|
public int Recibo { get; set; } // Nro de recibo
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
public int IdTipoPago { get; set; } // FK a cue_dtTipopago
|
||||||
|
public string? Detalle { get; set; }
|
||||||
|
public int IdEmpresa { get; set; } // Empresa a la que corresponde el saldo afectado
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
namespace GestionIntegral.Api.Models.Contables
|
||||||
|
{
|
||||||
|
public class PagoDistribuidorHistorico // Corresponde a cue_PagosDistribuidor_H
|
||||||
|
{
|
||||||
|
// No hay PK explícita en _H
|
||||||
|
public int Id_Pago { get; set; } // Coincide con columna en _H
|
||||||
|
public int Id_Distribuidor { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public string TipoMovimiento { get; set; } = string.Empty;
|
||||||
|
public int Recibo { get; set; }
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
public int Id_TipoPago { get; set; }
|
||||||
|
public string? Detalle { get; set; }
|
||||||
|
public int Id_Empresa { 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 ControlDevoluciones // Corresponde a dist_dtCtrlDevoluciones
|
||||||
|
{
|
||||||
|
public int IdControl { get; set; } // Id_Control (PK, Identity)
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public int Entrada { get; set; } // Cantidad total de ejemplares que ingresaron como devolución
|
||||||
|
public int Sobrantes { get; set; }
|
||||||
|
public string? Detalle { get; set; }
|
||||||
|
public int SinCargo { get; set; } // DEFAULT 0 NOT NULL
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
namespace GestionIntegral.Api.Models.Distribucion
|
||||||
|
{
|
||||||
|
public class ControlDevolucionesHistorico // Corresponde a dist_dtCtrlDevoluciones_H
|
||||||
|
{
|
||||||
|
// No hay PK explícita en _H
|
||||||
|
public int Id_Control { get; set; } // Coincide con columna en _H
|
||||||
|
public int Id_Empresa { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public int Entrada { get; set; }
|
||||||
|
public int Sobrantes { get; set; }
|
||||||
|
public string? Detalle { get; set; }
|
||||||
|
public int SinCargo { get; set; }
|
||||||
|
public int Id_Usuario { get; set; }
|
||||||
|
public DateTime FechaMod { get; set; }
|
||||||
|
public string TipoMod { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
namespace GestionIntegral.Api.Models.Distribucion
|
||||||
|
{
|
||||||
|
public class EntradaSalidaCanilla // Corresponde a dist_EntradasSalidasCanillas
|
||||||
|
{
|
||||||
|
public int IdParte { get; set; }
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public int IdCanilla { get; set; }
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
public int CantSalida { get; set; }
|
||||||
|
public int CantEntrada { get; set; }
|
||||||
|
public int IdPrecio { get; set; } // FK a dist_Precios
|
||||||
|
public int IdRecargo { get; set; } // FK a dist_RecargoZona (0 si no aplica)
|
||||||
|
public int IdPorcMon { get; set; } // FK a dist_PorcMonPagoCanilla (0 si no aplica)
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
public bool Liquidado { get; set; }
|
||||||
|
public DateTime? FechaLiquidado { get; set; }
|
||||||
|
public int? UserLiq { get; set; } // Usuario que liquidó
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
|
{
|
||||||
|
public class CreateNotaDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "El tipo de destino es obligatorio ('Distribuidores' o 'Canillas').")]
|
||||||
|
[RegularExpression("^(Distribuidores|Canillas)$", ErrorMessage = "Destino debe ser 'Distribuidores' o 'Canillas'.")]
|
||||||
|
public string Destino { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El destinatario es obligatorio.")]
|
||||||
|
[Range(1, int.MaxValue)]
|
||||||
|
public int IdDestino { get; set; }
|
||||||
|
|
||||||
|
[StringLength(50)]
|
||||||
|
public string? Referencia { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El tipo de nota es obligatorio ('Debito' o 'Credito').")]
|
||||||
|
[RegularExpression("^(Debito|Credito)$", ErrorMessage = "Tipo debe ser 'Debito' o 'Credito'.")]
|
||||||
|
public string Tipo { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
|
||||||
|
[Required, Range(0.01, (double)decimal.MaxValue, ErrorMessage = "El monto debe ser mayor a cero.")]
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
|
||||||
|
[StringLength(250)]
|
||||||
|
public string? Observaciones { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
|
{
|
||||||
|
public class CreatePagoDistribuidorDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdDistribuidor { get; set; }
|
||||||
|
[Required]
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
[Required]
|
||||||
|
[RegularExpression("^(Recibido|Realizado)$", ErrorMessage = "Tipo de movimiento debe ser 'Recibido' o 'Realizado'.")]
|
||||||
|
public string TipoMovimiento { get; set; } = string.Empty;
|
||||||
|
[Required, Range(1, int.MaxValue)]
|
||||||
|
public int Recibo { get; set; } // Nro de Recibo
|
||||||
|
[Required, Range(0.01, (double)decimal.MaxValue, ErrorMessage = "El monto debe ser mayor a cero.")]
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int IdTipoPago { get; set; }
|
||||||
|
[StringLength(150)]
|
||||||
|
public string? Detalle { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int IdEmpresa { get; set; } // Empresa cuyo saldo se verá afectado
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
|
{
|
||||||
|
public class NotaCreditoDebitoDto
|
||||||
|
{
|
||||||
|
public int IdNota { get; set; }
|
||||||
|
public string Destino { get; set; } = string.Empty; // "Distribuidores", "Canillas"
|
||||||
|
public int IdDestino { get; set; }
|
||||||
|
public string NombreDestinatario { get; set; } = string.Empty; // Nombre del Distribuidor o Canillita
|
||||||
|
public string? Referencia { get; set; }
|
||||||
|
public string Tipo { get; set; } = string.Empty; // "Debito", "Credito"
|
||||||
|
public string Fecha { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
public string? Observaciones { get; set; }
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
public string NombreEmpresa { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
|
{
|
||||||
|
public class PagoDistribuidorDto
|
||||||
|
{
|
||||||
|
public int IdPago { get; set; }
|
||||||
|
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; // "Recibido" / "Realizado"
|
||||||
|
public int Recibo { get; set; }
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
public int IdTipoPago { get; set; }
|
||||||
|
public string NombreTipoPago { get; set; } = string.Empty;
|
||||||
|
public string? Detalle { get; set; }
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
public string NombreEmpresa { get; set; } = string.Empty; // Empresa del saldo afectado
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Para las notas, la edición es limitada. Si se comete un error grave, se anula y se crea una nueva.
|
||||||
|
// Podríamos permitir cambiar Monto y Observaciones.
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
|
{
|
||||||
|
public class UpdateNotaDto
|
||||||
|
{
|
||||||
|
[Required, Range(0.01, (double)decimal.MaxValue, ErrorMessage = "El monto debe ser mayor a cero.")]
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
|
||||||
|
[StringLength(250)]
|
||||||
|
public string? Observaciones { get; set; }
|
||||||
|
// No se permite cambiar Destino, IdDestino, Tipo, Fecha, Referencia, IdEmpresa de una nota existente.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// La edición de un pago puede ser delicada por la afectación de saldos.
|
||||||
|
// Podríamos permitir cambiar Monto, TipoPago, Detalle.
|
||||||
|
// Cambiar Fecha, Distribuidor, Empresa, TipoMovimiento, Recibo podría requerir anular y recrear.
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Contables
|
||||||
|
{
|
||||||
|
public class UpdatePagoDistribuidorDto
|
||||||
|
{
|
||||||
|
[Required, Range(0.01, (double)decimal.MaxValue, ErrorMessage = "El monto debe ser mayor a cero.")]
|
||||||
|
public decimal Monto { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int IdTipoPago { get; set; }
|
||||||
|
[StringLength(150)]
|
||||||
|
public string? Detalle { get; set; }
|
||||||
|
// Los campos IdDistribuidor, Fecha, TipoMovimiento, Recibo, IdEmpresa no se cambian aquí.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class ControlDevolucionesDto
|
||||||
|
{
|
||||||
|
public int IdControl { get; set; }
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
public string NombreEmpresa { get; set; } = string.Empty;
|
||||||
|
public string Fecha { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public int Entrada { get; set; }
|
||||||
|
public int Sobrantes { get; set; }
|
||||||
|
public string? Detalle { get; set; }
|
||||||
|
public int SinCargo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class CreateBulkEntradaSalidaCanillaDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "El ID del canillita es obligatorio.")]
|
||||||
|
public int IdCanilla { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La fecha del movimiento es obligatoria.")]
|
||||||
|
public DateTime Fecha { get; set; } // Fecha común para todos los ítems
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "Debe haber al menos un ítem de movimiento.")]
|
||||||
|
[MinLength(1, ErrorMessage = "Debe agregar al menos una publicación.")]
|
||||||
|
public List<EntradaSalidaCanillaItemDto> Items { get; set; } = new List<EntradaSalidaCanillaItemDto>();
|
||||||
|
|
||||||
|
// Validar que no haya publicaciones duplicadas en la lista de items
|
||||||
|
[CustomValidation(typeof(CreateBulkEntradaSalidaCanillaDto), nameof(ValidateNoDuplicatePublications))]
|
||||||
|
public string? DuplicateError { get; set; }
|
||||||
|
|
||||||
|
public static ValidationResult? ValidateNoDuplicatePublications(CreateBulkEntradaSalidaCanillaDto dto, ValidationContext context)
|
||||||
|
{
|
||||||
|
if (dto.Items != null)
|
||||||
|
{
|
||||||
|
var duplicatePublications = dto.Items
|
||||||
|
.GroupBy(item => item.IdPublicacion)
|
||||||
|
.Where(group => group.Count() > 1)
|
||||||
|
.Select(group => group.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (duplicatePublications.Any())
|
||||||
|
{
|
||||||
|
return new ValidationResult($"No puede agregar la misma publicación varias veces. Publicaciones duplicadas: {string.Join(", ", duplicatePublications)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ValidationResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class CreateControlDevolucionesDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdEmpresa { get; set; }
|
||||||
|
[Required]
|
||||||
|
public DateTime Fecha { get; set; }
|
||||||
|
[Required, Range(0, int.MaxValue)] // Entrada puede ser 0 si solo hay sobrantes/sin cargo
|
||||||
|
public int Entrada { get; set; }
|
||||||
|
[Required, Range(0, int.MaxValue)]
|
||||||
|
public int Sobrantes { get; set; }
|
||||||
|
[StringLength(250)]
|
||||||
|
public string? Detalle { get; set; }
|
||||||
|
[Required, Range(0, int.MaxValue)]
|
||||||
|
public int SinCargo { get; set; } = 0; // Default 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class CreateEntradaSalidaCanillaDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int IdCanilla { get; set; }
|
||||||
|
[Required]
|
||||||
|
public DateTime Fecha { get; set; } // Fecha del movimiento (retiro/devolución)
|
||||||
|
[Required, Range(0, int.MaxValue)] // Puede retirar 0 si es solo para registrar devolución
|
||||||
|
public int CantSalida { get; set; }
|
||||||
|
[Required, Range(0, int.MaxValue)]
|
||||||
|
public int CantEntrada { get; set; }
|
||||||
|
[StringLength(150)]
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
// Liquidado, FechaLiquidado, UserLiq se manejan por una acción de "Liquidar" separada.
|
||||||
|
// IdPrecio, IdRecargo, IdPorcMon se determinan en el backend.
|
||||||
|
|
||||||
|
[CustomValidation(typeof(CreateEntradaSalidaCanillaDto), nameof(ValidateCantidades))]
|
||||||
|
public string? CantidadesError { get; set; } // Dummy para validación
|
||||||
|
|
||||||
|
public static ValidationResult? ValidateCantidades(CreateEntradaSalidaCanillaDto dto, ValidationContext context)
|
||||||
|
{
|
||||||
|
if (dto.CantEntrada > dto.CantSalida)
|
||||||
|
{
|
||||||
|
return new ValidationResult("La cantidad de entrada (devolución) no puede ser mayor a la cantidad de salida (retiro).");
|
||||||
|
}
|
||||||
|
return ValidationResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class EntradaSalidaCanillaDto
|
||||||
|
{
|
||||||
|
public int IdParte { get; set; }
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
public string NombrePublicacion { get; set; } = string.Empty;
|
||||||
|
public int IdCanilla { get; set; }
|
||||||
|
public string NomApeCanilla { get; set; } = string.Empty;
|
||||||
|
public bool CanillaEsAccionista { get; set; }
|
||||||
|
public string Fecha { get; set; } = string.Empty; // yyyy-MM-dd
|
||||||
|
public int CantSalida { get; set; }
|
||||||
|
public int CantEntrada { get; set; }
|
||||||
|
public int Vendidos { get; set; } // Calculado
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
public bool Liquidado { get; set; }
|
||||||
|
public string? FechaLiquidado { get; set; } // yyyy-MM-dd
|
||||||
|
public int? UserLiq { get; set; }
|
||||||
|
public string? NombreUserLiq { get; set; } // Para mostrar en UI
|
||||||
|
public decimal MontoARendir { get; set; } // Calculado por el backend
|
||||||
|
public decimal PrecioUnitarioAplicado { get; set; } // Info para UI
|
||||||
|
public decimal RecargoAplicado { get; set; } // Info para UI
|
||||||
|
public decimal PorcentajeOMontoCanillaAplicado { get; set; } // Info para UI
|
||||||
|
public bool EsPorcentajeCanilla { get; set; } // Info para UI
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class EntradaSalidaCanillaItemDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "El ID de la publicación es obligatorio.")]
|
||||||
|
public int IdPublicacion { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La cantidad de salida es obligatoria.")]
|
||||||
|
[Range(0, int.MaxValue, ErrorMessage = "La cantidad de salida debe ser un número positivo o cero.")]
|
||||||
|
public int CantSalida { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La cantidad de entrada es obligatoria.")]
|
||||||
|
[Range(0, int.MaxValue, ErrorMessage = "La cantidad de entrada debe ser un número positivo o cero.")]
|
||||||
|
public int CantEntrada { get; set; }
|
||||||
|
|
||||||
|
[StringLength(150)]
|
||||||
|
public string? Observacion { get; set; } // Observación por línea
|
||||||
|
|
||||||
|
// Validar que CantEntrada no sea mayor que CantSalida
|
||||||
|
[CustomValidation(typeof(EntradaSalidaCanillaItemDto), nameof(ValidateCantidades))]
|
||||||
|
public string? CantidadesError { get; set; }
|
||||||
|
|
||||||
|
public static ValidationResult? ValidateCantidades(EntradaSalidaCanillaItemDto item, ValidationContext context)
|
||||||
|
{
|
||||||
|
if (item.CantEntrada > item.CantSalida)
|
||||||
|
{
|
||||||
|
return new ValidationResult("La cantidad de entrada (devolución) no puede ser mayor a la cantidad de salida (retiro) para esta publicación.", new[] { nameof(CantEntrada) });
|
||||||
|
}
|
||||||
|
return ValidationResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class LiquidarMovimientosCanillaRequestDto // Para liquidar uno o más movimientos
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
[MinLength(1)]
|
||||||
|
public List<int> IdsPartesALiquidar { get; set; } = new List<int>();
|
||||||
|
[Required]
|
||||||
|
public DateTime FechaLiquidacion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class UpdateControlDevolucionesDto
|
||||||
|
{
|
||||||
|
// IdEmpresa y Fecha no deberían cambiar para un registro existente. Si cambian, es un nuevo registro.
|
||||||
|
[Required, Range(0, int.MaxValue)]
|
||||||
|
public int Entrada { get; set; }
|
||||||
|
[Required, Range(0, int.MaxValue)]
|
||||||
|
public int Sobrantes { get; set; }
|
||||||
|
[StringLength(250)]
|
||||||
|
public string? Detalle { get; set; }
|
||||||
|
[Required, Range(0, int.MaxValue)]
|
||||||
|
public int SinCargo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Similar a E/S Distribuidores, la edición es limitada para no afectar cálculos complejos ya hechos.
|
||||||
|
// Principalmente para corregir cantidades si aún no está liquidado.
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||||
|
{
|
||||||
|
public class UpdateEntradaSalidaCanillaDto
|
||||||
|
{
|
||||||
|
[Required, Range(0, int.MaxValue)]
|
||||||
|
public int CantSalida { get; set; }
|
||||||
|
[Required, Range(0, int.MaxValue)]
|
||||||
|
public int CantEntrada { get; set; }
|
||||||
|
[StringLength(150)]
|
||||||
|
public string? Observacion { get; set; }
|
||||||
|
|
||||||
|
[CustomValidation(typeof(UpdateEntradaSalidaCanillaDto), nameof(ValidateCantidades))]
|
||||||
|
public string? CantidadesError { get; set; } // Dummy para validación
|
||||||
|
|
||||||
|
public static ValidationResult? ValidateCantidades(UpdateEntradaSalidaCanillaDto dto, ValidationContext context)
|
||||||
|
{
|
||||||
|
if (dto.CantEntrada > dto.CantSalida)
|
||||||
|
{
|
||||||
|
return new ValidationResult("La cantidad de entrada no puede ser mayor a la de salida.");
|
||||||
|
}
|
||||||
|
return ValidationResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Backend/GestionIntegral.Api/Models/Dtos/Radios/CancionDto.cs
Normal file
18
Backend/GestionIntegral.Api/Models/Dtos/Radios/CancionDto.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Radios
|
||||||
|
{
|
||||||
|
public class CancionDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Tema { get; set; }
|
||||||
|
public string? CompositorAutor { get; set; }
|
||||||
|
public string? Interprete { get; set; }
|
||||||
|
public string? Sello { get; set; }
|
||||||
|
public string? Placa { get; set; }
|
||||||
|
public int? Pista { get; set; }
|
||||||
|
public string? Introduccion { get; set; }
|
||||||
|
public int? IdRitmo { get; set; } // Renombrado de Ritmo a IdRitmo para claridad en DTO
|
||||||
|
public string? NombreRitmo { get; set; } // Para mostrar en UI
|
||||||
|
public string? Formato { get; set; }
|
||||||
|
public string? Album { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Radios
|
||||||
|
{
|
||||||
|
public class CancionEnListaDto // Lo que se va a escribir en cada fila del Excel
|
||||||
|
{
|
||||||
|
public string? Tema { get; set; }
|
||||||
|
public string? Interprete { get; set; }
|
||||||
|
public string? CompositorAutor { get; set; }
|
||||||
|
public string? Album { get; set; }
|
||||||
|
public string? Sello { get; set; }
|
||||||
|
public string? Placa { get; set; } // Nro de Catálogo / Identificador
|
||||||
|
public int? Pista { get; set; }
|
||||||
|
public string? Ritmo { get; set; } // Nombre del ritmo
|
||||||
|
public string? Formato { get; set; }
|
||||||
|
public string? Introduccion { get; set; } // Para saber duración o cues
|
||||||
|
// Podrías añadir campos como "HoraProgramada" si la generación es por franjas
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Radios
|
||||||
|
{
|
||||||
|
public class CreateCancionDto
|
||||||
|
{
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Tema { get; set; } // Tema podría ser el campo más importante
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? CompositorAutor { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Interprete { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Sello { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Placa { get; set; }
|
||||||
|
|
||||||
|
public int? Pista { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Introduccion { get; set; }
|
||||||
|
|
||||||
|
public int? IdRitmo { get; set; } // FK
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Formato { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Album { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Radios
|
||||||
|
{
|
||||||
|
public class CreateRitmoDto
|
||||||
|
{
|
||||||
|
[StringLength(255, ErrorMessage = "El nombre del ritmo no puede exceder los 255 caracteres.")]
|
||||||
|
// Aunque la BD permite NULL, para crear usualmente se requeriría un nombre.
|
||||||
|
// Si se permite crear ritmos sin nombre, quitar [Required]
|
||||||
|
[Required(ErrorMessage = "El nombre del ritmo es obligatorio.")]
|
||||||
|
public string NombreRitmo { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Radios
|
||||||
|
{
|
||||||
|
public class GenerarListaRadioRequestDto
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "El mes es obligatorio.")]
|
||||||
|
[Range(1, 12, ErrorMessage = "El mes debe estar entre 1 y 12.")]
|
||||||
|
public int Mes { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "El año es obligatorio.")]
|
||||||
|
[Range(2000, 2999, ErrorMessage = "El año debe ser un valor válido (ej. 2024).")] // Ajusta el rango si es necesario
|
||||||
|
public int Anio { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La institución es obligatoria.")]
|
||||||
|
[RegularExpression("^(AADI|SADAIC)$", ErrorMessage = "La institución debe ser 'AADI' o 'SADAIC'.")]
|
||||||
|
public string Institucion { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "La radio es obligatoria.")]
|
||||||
|
[RegularExpression("^(FM 99.1|FM 100.3)$", ErrorMessage = "La radio debe ser 'FM 99.1' o 'FM 100.3'.")]
|
||||||
|
public string Radio { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Radios
|
||||||
|
{
|
||||||
|
public class ProgramacionHorariaExcelDto
|
||||||
|
{
|
||||||
|
public int Dia { get; set; }
|
||||||
|
public int Hora { get; set; }
|
||||||
|
public string? TituloObra { get; set; } // Mapeado desde Cancion.Tema
|
||||||
|
public string? CompositorAutor { get; set; }
|
||||||
|
public string? Interprete { get; set; }
|
||||||
|
public string? Sello { get; set; }
|
||||||
|
public string? Album { get; set; }
|
||||||
|
// No se incluyen Pista, Introducción, Formato, Placa, NombreRitmo
|
||||||
|
// porque el Excel original de VB.NET no los tenía.
|
||||||
|
// Si se decide mantenerlos en el futuro, se podrían añadir aquí.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace GestionIntegral.Api.Dtos.Radios
|
||||||
|
{
|
||||||
|
public class RitmoDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? NombreRitmo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Radios
|
||||||
|
{
|
||||||
|
public class UpdateCancionDto
|
||||||
|
{
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Tema { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? CompositorAutor { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Interprete { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Sello { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Placa { get; set; }
|
||||||
|
|
||||||
|
public int? Pista { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Introduccion { get; set; }
|
||||||
|
|
||||||
|
public int? IdRitmo { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Formato { get; set; }
|
||||||
|
|
||||||
|
[StringLength(255)]
|
||||||
|
public string? Album { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
namespace GestionIntegral.Api.Dtos.Radios
|
||||||
|
{
|
||||||
|
public class UpdateRitmoDto
|
||||||
|
{
|
||||||
|
[StringLength(255, ErrorMessage = "El nombre del ritmo no puede exceder los 255 caracteres.")]
|
||||||
|
[Required(ErrorMessage = "El nombre del ritmo es obligatorio.")]
|
||||||
|
public string NombreRitmo { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Dtos.Usuarios.Auditoria
|
||||||
|
{
|
||||||
|
public class UsuarioHistorialDto
|
||||||
|
{
|
||||||
|
public int IdHist { get; set; } // PK de la tabla gral_Usuarios_H
|
||||||
|
public int IdUsuarioAfectado { get; set; } // IdUsuario (el usuario cuyos datos cambiaron)
|
||||||
|
public string UserAfectado { get; set; } = string.Empty; // UserNvo (username del usuario afectado)
|
||||||
|
|
||||||
|
// Campos del historial
|
||||||
|
public string? UserAnt { get; set; }
|
||||||
|
public string UserNvo { get; set; } = string.Empty;
|
||||||
|
public bool? HabilitadaAnt { get; set; }
|
||||||
|
public bool HabilitadaNva { get; set; }
|
||||||
|
public bool? SupAdminAnt { get; set; }
|
||||||
|
public bool SupAdminNvo { get; set; }
|
||||||
|
public string? NombreAnt { get; set; }
|
||||||
|
public string NombreNvo { get; set; } = string.Empty;
|
||||||
|
public string? ApellidoAnt { get; set; }
|
||||||
|
public string ApellidoNvo { get; set; } = string.Empty;
|
||||||
|
public int? IdPerfilAnt { get; set; }
|
||||||
|
public int IdPerfilNvo { get; set; }
|
||||||
|
public string? NombrePerfilAnt { get; set; } // Join con gral_Perfiles
|
||||||
|
public string NombrePerfilNvo { get; set; } = string.Empty; // Join con gral_Perfiles
|
||||||
|
public bool? DebeCambiarClaveAnt { get; set; }
|
||||||
|
public bool DebeCambiarClaveNva { get; set; }
|
||||||
|
|
||||||
|
// Auditoría del registro de historial
|
||||||
|
public int IdUsuarioModifico { get; set; } // Id_UsuarioMod
|
||||||
|
public string NombreUsuarioModifico { get; set; } = string.Empty; // Nombre del usuario que hizo el cambio
|
||||||
|
public DateTime FechaModificacion { get; set; } // FechaMod
|
||||||
|
public string TipoModificacion { get; set; } = string.Empty; // TipoMod
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Backend/GestionIntegral.Api/Models/Radios/Cancion.cs
Normal file
17
Backend/GestionIntegral.Api/Models/Radios/Cancion.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace GestionIntegral.Api.Models.Radios
|
||||||
|
{
|
||||||
|
public class Cancion // Corresponde a rad_dtCanciones
|
||||||
|
{
|
||||||
|
public int Id { get; set; } // Id (PK, Identity)
|
||||||
|
public string? Tema { get; set; }
|
||||||
|
public string? CompositorAutor { get; set; }
|
||||||
|
public string? Interprete { get; set; }
|
||||||
|
public string? Sello { get; set; }
|
||||||
|
public string? Placa { get; set; }
|
||||||
|
public int? Pista { get; set; }
|
||||||
|
public string? Introduccion { get; set; }
|
||||||
|
public int? Ritmo { get; set; } // FK a rad_dtRitmos.Id
|
||||||
|
public string? Formato { get; set; }
|
||||||
|
public string? Album { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Backend/GestionIntegral.Api/Models/Radios/Ritmo.cs
Normal file
8
Backend/GestionIntegral.Api/Models/Radios/Ritmo.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace GestionIntegral.Api.Models.Radios
|
||||||
|
{
|
||||||
|
public class Ritmo // Corresponde a rad_dtRitmos
|
||||||
|
{
|
||||||
|
public int Id { get; set; } // Id (PK, Identity)
|
||||||
|
public string? NombreRitmo { get; set; } // Columna "Ritmo" (nvarchar(255), NULL) - Renombrado para claridad
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,11 @@ using System.Text;
|
|||||||
using GestionIntegral.Api.Data;
|
using GestionIntegral.Api.Data;
|
||||||
using GestionIntegral.Api.Services.Contables;
|
using GestionIntegral.Api.Services.Contables;
|
||||||
using GestionIntegral.Api.Services.Distribucion;
|
using GestionIntegral.Api.Services.Distribucion;
|
||||||
|
using GestionIntegral.Api.Services.Radios;
|
||||||
using GestionIntegral.Api.Data.Repositories.Contables;
|
using GestionIntegral.Api.Data.Repositories.Contables;
|
||||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||||
using GestionIntegral.Api.Data.Repositories.Impresion;
|
using GestionIntegral.Api.Data.Repositories.Impresion;
|
||||||
|
using GestionIntegral.Api.Data.Repositories.Radios;
|
||||||
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;
|
||||||
@@ -65,6 +67,19 @@ builder.Services.AddScoped<ISalidaOtroDestinoRepository, SalidaOtroDestinoReposi
|
|||||||
builder.Services.AddScoped<ISalidaOtroDestinoService, SalidaOtroDestinoService>();
|
builder.Services.AddScoped<ISalidaOtroDestinoService, SalidaOtroDestinoService>();
|
||||||
builder.Services.AddScoped<IEntradaSalidaDistRepository, EntradaSalidaDistRepository>();
|
builder.Services.AddScoped<IEntradaSalidaDistRepository, EntradaSalidaDistRepository>();
|
||||||
builder.Services.AddScoped<IEntradaSalidaDistService, EntradaSalidaDistService>();
|
builder.Services.AddScoped<IEntradaSalidaDistService, EntradaSalidaDistService>();
|
||||||
|
builder.Services.AddScoped<IEntradaSalidaCanillaRepository, EntradaSalidaCanillaRepository>();
|
||||||
|
builder.Services.AddScoped<IEntradaSalidaCanillaService, EntradaSalidaCanillaService>();
|
||||||
|
builder.Services.AddScoped<IControlDevolucionesRepository, ControlDevolucionesRepository>();
|
||||||
|
builder.Services.AddScoped<IControlDevolucionesService, ControlDevolucionesService>();
|
||||||
|
builder.Services.AddScoped<IPagoDistribuidorRepository, PagoDistribuidorRepository>();
|
||||||
|
builder.Services.AddScoped<IPagoDistribuidorService, PagoDistribuidorService>();
|
||||||
|
builder.Services.AddScoped<INotaCreditoDebitoRepository, NotaCreditoDebitoRepository>();
|
||||||
|
builder.Services.AddScoped<INotaCreditoDebitoService, NotaCreditoDebitoService>();
|
||||||
|
builder.Services.AddScoped<IRitmoRepository, RitmoRepository>();
|
||||||
|
builder.Services.AddScoped<IRitmoService, RitmoService>();
|
||||||
|
builder.Services.AddScoped<ICancionRepository, CancionRepository>();
|
||||||
|
builder.Services.AddScoped<ICancionService, CancionService>();
|
||||||
|
builder.Services.AddScoped<IRadioListaService, RadioListaService>();
|
||||||
|
|
||||||
// --- Configuración de Autenticación JWT ---
|
// --- Configuración de Autenticación JWT ---
|
||||||
var jwtSettings = builder.Configuration.GetSection("Jwt");
|
var jwtSettings = builder.Configuration.GetSection("Jwt");
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Contables;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Contables
|
||||||
|
{
|
||||||
|
public interface INotaCreditoDebitoService
|
||||||
|
{
|
||||||
|
Task<IEnumerable<NotaCreditoDebitoDto>> ObtenerTodosAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
string? destino, int? idDestino, int? idEmpresa, string? tipoNota);
|
||||||
|
|
||||||
|
Task<NotaCreditoDebitoDto?> ObtenerPorIdAsync(int idNota);
|
||||||
|
Task<(NotaCreditoDebitoDto? Nota, string? Error)> CrearAsync(CreateNotaDto createDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> ActualizarAsync(int idNota, UpdateNotaDto updateDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> EliminarAsync(int idNota, int idUsuario);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Contables;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Contables
|
||||||
|
{
|
||||||
|
public interface IPagoDistribuidorService
|
||||||
|
{
|
||||||
|
Task<IEnumerable<PagoDistribuidorDto>> ObtenerTodosAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
int? idDistribuidor, int? idEmpresa, string? tipoMovimiento);
|
||||||
|
|
||||||
|
Task<PagoDistribuidorDto?> ObtenerPorIdAsync(int idPago);
|
||||||
|
Task<(PagoDistribuidorDto? Pago, string? Error)> CrearAsync(CreatePagoDistribuidorDto createDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> ActualizarAsync(int idPago, UpdatePagoDistribuidorDto updateDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> EliminarAsync(int idPago, int idUsuario);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
using GestionIntegral.Api.Data;
|
||||||
|
using GestionIntegral.Api.Data.Repositories.Contables;
|
||||||
|
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||||
|
using GestionIntegral.Api.Dtos.Contables;
|
||||||
|
using GestionIntegral.Api.Models.Contables;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Contables
|
||||||
|
{
|
||||||
|
public class NotaCreditoDebitoService : INotaCreditoDebitoService
|
||||||
|
{
|
||||||
|
private readonly INotaCreditoDebitoRepository _notaRepo;
|
||||||
|
private readonly IDistribuidorRepository _distribuidorRepo;
|
||||||
|
private readonly ICanillaRepository _canillaRepo;
|
||||||
|
private readonly IEmpresaRepository _empresaRepo;
|
||||||
|
private readonly ISaldoRepository _saldoRepo;
|
||||||
|
private readonly DbConnectionFactory _connectionFactory;
|
||||||
|
private readonly ILogger<NotaCreditoDebitoService> _logger;
|
||||||
|
|
||||||
|
public NotaCreditoDebitoService(
|
||||||
|
INotaCreditoDebitoRepository notaRepo,
|
||||||
|
IDistribuidorRepository distribuidorRepo,
|
||||||
|
ICanillaRepository canillaRepo,
|
||||||
|
IEmpresaRepository empresaRepo,
|
||||||
|
ISaldoRepository saldoRepo,
|
||||||
|
DbConnectionFactory connectionFactory,
|
||||||
|
ILogger<NotaCreditoDebitoService> logger)
|
||||||
|
{
|
||||||
|
_notaRepo = notaRepo;
|
||||||
|
_distribuidorRepo = distribuidorRepo;
|
||||||
|
_canillaRepo = canillaRepo;
|
||||||
|
_empresaRepo = empresaRepo;
|
||||||
|
_saldoRepo = saldoRepo;
|
||||||
|
_connectionFactory = connectionFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<NotaCreditoDebitoDto> MapToDto(NotaCreditoDebito nota)
|
||||||
|
{
|
||||||
|
if (nota == null) return null!;
|
||||||
|
|
||||||
|
string nombreDestinatario = "N/A";
|
||||||
|
if (nota.Destino == "Distribuidores")
|
||||||
|
{
|
||||||
|
var distData = await _distribuidorRepo.GetByIdAsync(nota.IdDestino);
|
||||||
|
nombreDestinatario = distData.Distribuidor?.Nombre ?? "Distribuidor Desconocido";
|
||||||
|
}
|
||||||
|
else if (nota.Destino == "Canillas")
|
||||||
|
{
|
||||||
|
var canData = await _canillaRepo.GetByIdAsync(nota.IdDestino); // Asumiendo que GetByIdAsync devuelve una tupla
|
||||||
|
nombreDestinatario = canData.Canilla?.NomApe ?? "Canillita Desconocido";
|
||||||
|
}
|
||||||
|
|
||||||
|
var empresa = await _empresaRepo.GetByIdAsync(nota.IdEmpresa);
|
||||||
|
|
||||||
|
return new NotaCreditoDebitoDto
|
||||||
|
{
|
||||||
|
IdNota = nota.IdNota,
|
||||||
|
Destino = nota.Destino,
|
||||||
|
IdDestino = nota.IdDestino,
|
||||||
|
NombreDestinatario = nombreDestinatario,
|
||||||
|
Referencia = nota.Referencia,
|
||||||
|
Tipo = nota.Tipo,
|
||||||
|
Fecha = nota.Fecha.ToString("yyyy-MM-dd"),
|
||||||
|
Monto = nota.Monto,
|
||||||
|
Observaciones = nota.Observaciones,
|
||||||
|
IdEmpresa = nota.IdEmpresa,
|
||||||
|
NombreEmpresa = empresa?.Nombre ?? "Empresa Desconocida"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<NotaCreditoDebitoDto>> ObtenerTodosAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
string? destino, int? idDestino, int? idEmpresa, string? tipoNota)
|
||||||
|
{
|
||||||
|
var notas = await _notaRepo.GetAllAsync(fechaDesde, fechaHasta, destino, idDestino, idEmpresa, tipoNota);
|
||||||
|
var dtos = new List<NotaCreditoDebitoDto>();
|
||||||
|
foreach (var nota in notas)
|
||||||
|
{
|
||||||
|
dtos.Add(await MapToDto(nota));
|
||||||
|
}
|
||||||
|
return dtos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NotaCreditoDebitoDto?> ObtenerPorIdAsync(int idNota)
|
||||||
|
{
|
||||||
|
var nota = await _notaRepo.GetByIdAsync(idNota);
|
||||||
|
return nota == null ? null : await MapToDto(nota);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(NotaCreditoDebitoDto? Nota, string? Error)> CrearAsync(CreateNotaDto createDto, int idUsuario)
|
||||||
|
{
|
||||||
|
// Validar Destinatario
|
||||||
|
if (createDto.Destino == "Distribuidores")
|
||||||
|
{
|
||||||
|
if (await _distribuidorRepo.GetByIdSimpleAsync(createDto.IdDestino) == null)
|
||||||
|
return (null, "El distribuidor especificado no existe.");
|
||||||
|
}
|
||||||
|
else if (createDto.Destino == "Canillas")
|
||||||
|
{
|
||||||
|
if (await _canillaRepo.GetByIdSimpleAsync(createDto.IdDestino) == null) // Asumiendo GetByIdSimpleAsync en ICanillaRepository
|
||||||
|
return (null, "El canillita especificado no existe.");
|
||||||
|
}
|
||||||
|
else { return (null, "Tipo de destino inválido."); }
|
||||||
|
|
||||||
|
if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null)
|
||||||
|
return (null, "La empresa especificada no existe.");
|
||||||
|
|
||||||
|
var nuevaNota = new NotaCreditoDebito
|
||||||
|
{
|
||||||
|
Destino = createDto.Destino,
|
||||||
|
IdDestino = createDto.IdDestino,
|
||||||
|
Referencia = createDto.Referencia,
|
||||||
|
Tipo = createDto.Tipo,
|
||||||
|
Fecha = createDto.Fecha.Date,
|
||||||
|
Monto = createDto.Monto,
|
||||||
|
Observaciones = createDto.Observaciones,
|
||||||
|
IdEmpresa = createDto.IdEmpresa
|
||||||
|
};
|
||||||
|
|
||||||
|
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 notaCreada = await _notaRepo.CreateAsync(nuevaNota, idUsuario, transaction);
|
||||||
|
if (notaCreada == null) throw new DataException("Error al registrar la nota.");
|
||||||
|
|
||||||
|
// Afectar Saldo
|
||||||
|
// Nota de Crédito: Disminuye la deuda del destinatario (monto positivo para el servicio de saldo)
|
||||||
|
// Nota de Débito: Aumenta la deuda del destinatario (monto negativo para el servicio de saldo)
|
||||||
|
decimal montoAjusteSaldo = createDto.Tipo == "Credito" ? createDto.Monto : -createDto.Monto;
|
||||||
|
|
||||||
|
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaCreada.Destino, notaCreada.IdDestino, notaCreada.IdEmpresa, montoAjusteSaldo, transaction);
|
||||||
|
if (!saldoActualizado) throw new DataException($"Error al actualizar el saldo para {notaCreada.Destino} ID {notaCreada.IdDestino}.");
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("NotaC/D ID {Id} creada y saldo afectado por Usuario ID {UserId}.", notaCreada.IdNota, idUsuario);
|
||||||
|
return (await MapToDto(notaCreada), null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
try { transaction.Rollback(); } catch { }
|
||||||
|
_logger.LogError(ex, "Error CrearAsync NotaCreditoDebito.");
|
||||||
|
return (null, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idNota, UpdateNotaDto 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 notaExistente = await _notaRepo.GetByIdAsync(idNota);
|
||||||
|
if (notaExistente == null) return (false, "Nota no encontrada.");
|
||||||
|
|
||||||
|
// Calcular diferencia de monto para ajustar saldo
|
||||||
|
decimal montoOriginal = notaExistente.Tipo == "Credito" ? notaExistente.Monto : -notaExistente.Monto;
|
||||||
|
decimal montoNuevo = notaExistente.Tipo == "Credito" ? updateDto.Monto : -updateDto.Monto; // Tipo no cambia
|
||||||
|
decimal diferenciaAjusteSaldo = montoNuevo - montoOriginal;
|
||||||
|
|
||||||
|
notaExistente.Monto = updateDto.Monto;
|
||||||
|
notaExistente.Observaciones = updateDto.Observaciones;
|
||||||
|
|
||||||
|
var actualizado = await _notaRepo.UpdateAsync(notaExistente, idUsuario, transaction);
|
||||||
|
if (!actualizado) throw new DataException("Error al actualizar la nota.");
|
||||||
|
|
||||||
|
if (diferenciaAjusteSaldo != 0)
|
||||||
|
{
|
||||||
|
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaExistente.Destino, notaExistente.IdDestino, notaExistente.IdEmpresa, diferenciaAjusteSaldo, transaction);
|
||||||
|
if (!saldoActualizado) throw new DataException("Error al ajustar el saldo tras la actualización de la nota.");
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("NotaC/D ID {Id} actualizada por Usuario ID {UserId}.", idNota, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Nota no encontrada."); }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
try { transaction.Rollback(); } catch { }
|
||||||
|
_logger.LogError(ex, "Error ActualizarAsync NotaC/D ID: {Id}", idNota);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> EliminarAsync(int idNota, 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 notaExistente = await _notaRepo.GetByIdAsync(idNota);
|
||||||
|
if (notaExistente == null) return (false, "Nota no encontrada.");
|
||||||
|
|
||||||
|
// Revertir el efecto en el saldo
|
||||||
|
decimal montoReversion = notaExistente.Tipo == "Credito" ? -notaExistente.Monto : notaExistente.Monto;
|
||||||
|
|
||||||
|
var eliminado = await _notaRepo.DeleteAsync(idNota, idUsuario, transaction);
|
||||||
|
if (!eliminado) throw new DataException("Error al eliminar la nota.");
|
||||||
|
|
||||||
|
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaExistente.Destino, notaExistente.IdDestino, notaExistente.IdEmpresa, montoReversion, transaction);
|
||||||
|
if (!saldoActualizado) throw new DataException("Error al revertir el saldo tras la eliminación de la nota.");
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("NotaC/D ID {Id} eliminada y saldo revertido por Usuario ID {UserId}.", idNota, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Nota no encontrada."); }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
try { transaction.Rollback(); } catch { }
|
||||||
|
_logger.LogError(ex, "Error EliminarAsync NotaC/D ID: {Id}", idNota);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
using GestionIntegral.Api.Data;
|
||||||
|
using GestionIntegral.Api.Data.Repositories.Contables;
|
||||||
|
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||||
|
using GestionIntegral.Api.Dtos.Contables;
|
||||||
|
using GestionIntegral.Api.Models.Contables;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Contables
|
||||||
|
{
|
||||||
|
public class PagoDistribuidorService : IPagoDistribuidorService
|
||||||
|
{
|
||||||
|
private readonly IPagoDistribuidorRepository _pagoRepo;
|
||||||
|
private readonly IDistribuidorRepository _distribuidorRepo;
|
||||||
|
private readonly ITipoPagoRepository _tipoPagoRepo;
|
||||||
|
private readonly IEmpresaRepository _empresaRepo;
|
||||||
|
private readonly ISaldoRepository _saldoRepo;
|
||||||
|
private readonly DbConnectionFactory _connectionFactory;
|
||||||
|
private readonly ILogger<PagoDistribuidorService> _logger;
|
||||||
|
|
||||||
|
public PagoDistribuidorService(
|
||||||
|
IPagoDistribuidorRepository pagoRepo,
|
||||||
|
IDistribuidorRepository distribuidorRepo,
|
||||||
|
ITipoPagoRepository tipoPagoRepo,
|
||||||
|
IEmpresaRepository empresaRepo,
|
||||||
|
ISaldoRepository saldoRepo,
|
||||||
|
DbConnectionFactory connectionFactory,
|
||||||
|
ILogger<PagoDistribuidorService> logger)
|
||||||
|
{
|
||||||
|
_pagoRepo = pagoRepo;
|
||||||
|
_distribuidorRepo = distribuidorRepo;
|
||||||
|
_tipoPagoRepo = tipoPagoRepo;
|
||||||
|
_empresaRepo = empresaRepo;
|
||||||
|
_saldoRepo = saldoRepo;
|
||||||
|
_connectionFactory = connectionFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<PagoDistribuidorDto> MapToDto(PagoDistribuidor pago)
|
||||||
|
{
|
||||||
|
if (pago == null) return null!;
|
||||||
|
|
||||||
|
var distribuidorData = await _distribuidorRepo.GetByIdAsync(pago.IdDistribuidor);
|
||||||
|
var tipoPago = await _tipoPagoRepo.GetByIdAsync(pago.IdTipoPago);
|
||||||
|
var empresa = await _empresaRepo.GetByIdAsync(pago.IdEmpresa);
|
||||||
|
|
||||||
|
return new PagoDistribuidorDto
|
||||||
|
{
|
||||||
|
IdPago = pago.IdPago,
|
||||||
|
IdDistribuidor = pago.IdDistribuidor,
|
||||||
|
NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "N/A",
|
||||||
|
Fecha = pago.Fecha.ToString("yyyy-MM-dd"),
|
||||||
|
TipoMovimiento = pago.TipoMovimiento,
|
||||||
|
Recibo = pago.Recibo,
|
||||||
|
Monto = pago.Monto,
|
||||||
|
IdTipoPago = pago.IdTipoPago,
|
||||||
|
NombreTipoPago = tipoPago?.Nombre ?? "N/A",
|
||||||
|
Detalle = pago.Detalle,
|
||||||
|
IdEmpresa = pago.IdEmpresa,
|
||||||
|
NombreEmpresa = empresa?.Nombre ?? "N/A"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<PagoDistribuidorDto>> ObtenerTodosAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
int? idDistribuidor, int? idEmpresa, string? tipoMovimiento)
|
||||||
|
{
|
||||||
|
var pagos = await _pagoRepo.GetAllAsync(fechaDesde, fechaHasta, idDistribuidor, idEmpresa, tipoMovimiento);
|
||||||
|
var dtos = new List<PagoDistribuidorDto>();
|
||||||
|
foreach (var pago in pagos)
|
||||||
|
{
|
||||||
|
dtos.Add(await MapToDto(pago));
|
||||||
|
}
|
||||||
|
return dtos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PagoDistribuidorDto?> ObtenerPorIdAsync(int idPago)
|
||||||
|
{
|
||||||
|
var pago = await _pagoRepo.GetByIdAsync(idPago);
|
||||||
|
return pago == null ? null : await MapToDto(pago);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(PagoDistribuidorDto? Pago, string? Error)> CrearAsync(CreatePagoDistribuidorDto createDto, int idUsuario)
|
||||||
|
{
|
||||||
|
if (await _distribuidorRepo.GetByIdSimpleAsync(createDto.IdDistribuidor) == null)
|
||||||
|
return (null, "Distribuidor no válido.");
|
||||||
|
if (await _tipoPagoRepo.GetByIdAsync(createDto.IdTipoPago) == null)
|
||||||
|
return (null, "Tipo de pago no válido.");
|
||||||
|
if (await _empresaRepo.GetByIdAsync(createDto.IdEmpresa) == null)
|
||||||
|
return (null, "Empresa no válida.");
|
||||||
|
if (await _pagoRepo.ExistsByReciboAndTipoMovimientoAsync(createDto.Recibo, createDto.TipoMovimiento))
|
||||||
|
return (null, $"Ya existe un pago '{createDto.TipoMovimiento}' con el número de recibo '{createDto.Recibo}'.");
|
||||||
|
|
||||||
|
|
||||||
|
var nuevoPago = new PagoDistribuidor
|
||||||
|
{
|
||||||
|
IdDistribuidor = createDto.IdDistribuidor,
|
||||||
|
Fecha = createDto.Fecha.Date,
|
||||||
|
TipoMovimiento = createDto.TipoMovimiento,
|
||||||
|
Recibo = createDto.Recibo,
|
||||||
|
Monto = createDto.Monto,
|
||||||
|
IdTipoPago = createDto.IdTipoPago,
|
||||||
|
Detalle = createDto.Detalle,
|
||||||
|
IdEmpresa = createDto.IdEmpresa
|
||||||
|
};
|
||||||
|
|
||||||
|
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 pagoCreado = await _pagoRepo.CreateAsync(nuevoPago, idUsuario, transaction);
|
||||||
|
if (pagoCreado == null) throw new DataException("Error al registrar el pago.");
|
||||||
|
|
||||||
|
// Afectar Saldo
|
||||||
|
// Si TipoMovimiento es "Recibido", el monto DISMINUYE la deuda del distribuidor (monto positivo para el servicio de saldo).
|
||||||
|
// Si TipoMovimiento es "Realizado" (empresa paga a distribuidor), el monto AUMENTA la deuda (monto negativo para el servicio de saldo).
|
||||||
|
decimal montoAjusteSaldo = createDto.TipoMovimiento == "Recibido" ? createDto.Monto : -createDto.Monto;
|
||||||
|
|
||||||
|
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoCreado.IdDistribuidor, pagoCreado.IdEmpresa, montoAjusteSaldo, transaction);
|
||||||
|
if (!saldoActualizado) throw new DataException("Error al actualizar el saldo del distribuidor.");
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("PagoDistribuidor ID {Id} creado y saldo afectado por Usuario ID {UserId}.", pagoCreado.IdPago, idUsuario);
|
||||||
|
return (await MapToDto(pagoCreado), null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
try { transaction.Rollback(); } catch { }
|
||||||
|
_logger.LogError(ex, "Error CrearAsync PagoDistribuidor.");
|
||||||
|
return (null, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPago, UpdatePagoDistribuidorDto 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 pagoExistente = await _pagoRepo.GetByIdAsync(idPago);
|
||||||
|
if (pagoExistente == null) return (false, "Pago no encontrado.");
|
||||||
|
|
||||||
|
if (await _tipoPagoRepo.GetByIdAsync(updateDto.IdTipoPago) == null)
|
||||||
|
return (false, "Tipo de pago no válido.");
|
||||||
|
|
||||||
|
// Calcular la diferencia de monto para ajustar el saldo
|
||||||
|
decimal montoOriginal = pagoExistente.TipoMovimiento == "Recibido" ? pagoExistente.Monto : -pagoExistente.Monto;
|
||||||
|
decimal montoNuevo = pagoExistente.TipoMovimiento == "Recibido" ? updateDto.Monto : -updateDto.Monto;
|
||||||
|
decimal diferenciaAjusteSaldo = montoNuevo - montoOriginal;
|
||||||
|
|
||||||
|
// Actualizar campos permitidos
|
||||||
|
pagoExistente.Monto = updateDto.Monto;
|
||||||
|
pagoExistente.IdTipoPago = updateDto.IdTipoPago;
|
||||||
|
pagoExistente.Detalle = updateDto.Detalle;
|
||||||
|
|
||||||
|
var actualizado = await _pagoRepo.UpdateAsync(pagoExistente, idUsuario, transaction);
|
||||||
|
if (!actualizado) throw new DataException("Error al actualizar el pago.");
|
||||||
|
|
||||||
|
if (diferenciaAjusteSaldo != 0)
|
||||||
|
{
|
||||||
|
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, diferenciaAjusteSaldo, transaction);
|
||||||
|
if (!saldoActualizado) throw new DataException("Error al ajustar el saldo del distribuidor tras la actualización del pago.");
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("PagoDistribuidor ID {Id} actualizado por Usuario ID {UserId}.", idPago, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Pago no encontrado."); }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
try { transaction.Rollback(); } catch { }
|
||||||
|
_logger.LogError(ex, "Error ActualizarAsync PagoDistribuidor ID: {Id}", idPago);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> EliminarAsync(int idPago, 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 pagoExistente = await _pagoRepo.GetByIdAsync(idPago);
|
||||||
|
if (pagoExistente == null) return (false, "Pago no encontrado.");
|
||||||
|
|
||||||
|
// Revertir el efecto en el saldo
|
||||||
|
// Si fue "Recibido", el saldo disminuyó (montoAjusteSaldo fue +Monto). Al eliminar, revertimos sumando -Monto (o restando +Monto).
|
||||||
|
// Si fue "Realizado", el saldo aumentó (montoAjusteSaldo fue -Monto). Al eliminar, revertimos sumando +Monto (o restando -Monto).
|
||||||
|
decimal montoReversion = pagoExistente.TipoMovimiento == "Recibido" ? -pagoExistente.Monto : pagoExistente.Monto;
|
||||||
|
|
||||||
|
var eliminado = await _pagoRepo.DeleteAsync(idPago, idUsuario, transaction);
|
||||||
|
if (!eliminado) throw new DataException("Error al eliminar el pago.");
|
||||||
|
|
||||||
|
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoExistente.IdDistribuidor, pagoExistente.IdEmpresa, montoReversion, transaction);
|
||||||
|
if (!saldoActualizado) throw new DataException("Error al revertir el saldo del distribuidor tras la eliminación del pago.");
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("PagoDistribuidor ID {Id} eliminado por Usuario ID {UserId}.", idPago, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Pago no encontrado."); }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
try { transaction.Rollback(); } catch { }
|
||||||
|
_logger.LogError(ex, "Error EliminarAsync PagoDistribuidor ID: {Id}", idPago);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
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 ControlDevolucionesService : IControlDevolucionesService
|
||||||
|
{
|
||||||
|
private readonly IControlDevolucionesRepository _controlDevRepo;
|
||||||
|
private readonly IEmpresaRepository _empresaRepository; // Para validar IdEmpresa y obtener nombre
|
||||||
|
private readonly DbConnectionFactory _connectionFactory;
|
||||||
|
private readonly ILogger<ControlDevolucionesService> _logger;
|
||||||
|
|
||||||
|
public ControlDevolucionesService(
|
||||||
|
IControlDevolucionesRepository controlDevRepo,
|
||||||
|
IEmpresaRepository empresaRepository,
|
||||||
|
DbConnectionFactory connectionFactory,
|
||||||
|
ILogger<ControlDevolucionesService> logger)
|
||||||
|
{
|
||||||
|
_controlDevRepo = controlDevRepo;
|
||||||
|
_empresaRepository = empresaRepository;
|
||||||
|
_connectionFactory = connectionFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ControlDevolucionesDto> MapToDto(ControlDevoluciones control)
|
||||||
|
{
|
||||||
|
if (control == null) return null!;
|
||||||
|
var empresa = await _empresaRepository.GetByIdAsync(control.IdEmpresa);
|
||||||
|
return new ControlDevolucionesDto
|
||||||
|
{
|
||||||
|
IdControl = control.IdControl,
|
||||||
|
IdEmpresa = control.IdEmpresa,
|
||||||
|
NombreEmpresa = empresa?.Nombre ?? "Empresa Desconocida",
|
||||||
|
Fecha = control.Fecha.ToString("yyyy-MM-dd"),
|
||||||
|
Entrada = control.Entrada,
|
||||||
|
Sobrantes = control.Sobrantes,
|
||||||
|
Detalle = control.Detalle,
|
||||||
|
SinCargo = control.SinCargo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ControlDevolucionesDto>> ObtenerTodosAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idEmpresa)
|
||||||
|
{
|
||||||
|
var controles = await _controlDevRepo.GetAllAsync(fechaDesde, fechaHasta, idEmpresa);
|
||||||
|
var dtos = new List<ControlDevolucionesDto>();
|
||||||
|
foreach (var control in controles)
|
||||||
|
{
|
||||||
|
dtos.Add(await MapToDto(control));
|
||||||
|
}
|
||||||
|
return dtos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ControlDevolucionesDto?> ObtenerPorIdAsync(int idControl)
|
||||||
|
{
|
||||||
|
var control = await _controlDevRepo.GetByIdAsync(idControl);
|
||||||
|
return control == null ? null : await MapToDto(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(ControlDevolucionesDto? Control, string? Error)> CrearAsync(CreateControlDevolucionesDto createDto, int idUsuario)
|
||||||
|
{
|
||||||
|
var empresa = await _empresaRepository.GetByIdAsync(createDto.IdEmpresa);
|
||||||
|
if (empresa == null)
|
||||||
|
return (null, "La empresa especificada no existe.");
|
||||||
|
|
||||||
|
// Validar que no exista ya un control para esa empresa y fecha
|
||||||
|
if (await _controlDevRepo.GetByEmpresaAndFechaAsync(createDto.IdEmpresa, createDto.Fecha.Date) != null)
|
||||||
|
{
|
||||||
|
return (null, $"Ya existe un control de devoluciones para la empresa '{empresa.Nombre}' en la fecha {createDto.Fecha:dd/MM/yyyy}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var nuevoControl = new ControlDevoluciones
|
||||||
|
{
|
||||||
|
IdEmpresa = createDto.IdEmpresa,
|
||||||
|
Fecha = createDto.Fecha.Date,
|
||||||
|
Entrada = createDto.Entrada,
|
||||||
|
Sobrantes = createDto.Sobrantes,
|
||||||
|
Detalle = createDto.Detalle,
|
||||||
|
SinCargo = createDto.SinCargo
|
||||||
|
};
|
||||||
|
|
||||||
|
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 controlCreado = await _controlDevRepo.CreateAsync(nuevoControl, idUsuario, transaction);
|
||||||
|
if (controlCreado == null) throw new DataException("Error al registrar el control de devoluciones.");
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("ControlDevoluciones ID {Id} creado por Usuario ID {UserId}.", controlCreado.IdControl, idUsuario);
|
||||||
|
return (await MapToDto(controlCreado), null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
try { transaction.Rollback(); } catch { }
|
||||||
|
_logger.LogError(ex, "Error CrearAsync ControlDevoluciones para Empresa ID {IdEmpresa}, Fecha {Fecha}", createDto.IdEmpresa, createDto.Fecha);
|
||||||
|
return (null, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idControl, UpdateControlDevolucionesDto 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 controlExistente = await _controlDevRepo.GetByIdAsync(idControl); // Obtener dentro de TX
|
||||||
|
if (controlExistente == null) return (false, "Control de devoluciones no encontrado.");
|
||||||
|
|
||||||
|
// IdEmpresa y Fecha no se modifican
|
||||||
|
controlExistente.Entrada = updateDto.Entrada;
|
||||||
|
controlExistente.Sobrantes = updateDto.Sobrantes;
|
||||||
|
controlExistente.Detalle = updateDto.Detalle;
|
||||||
|
controlExistente.SinCargo = updateDto.SinCargo;
|
||||||
|
|
||||||
|
var actualizado = await _controlDevRepo.UpdateAsync(controlExistente, idUsuario, transaction);
|
||||||
|
if (!actualizado) throw new DataException("Error al actualizar el control de devoluciones.");
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("ControlDevoluciones ID {Id} actualizado por Usuario ID {UserId}.", idControl, 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 ControlDevoluciones ID: {Id}", idControl);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> EliminarAsync(int idControl, 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 controlExistente = await _controlDevRepo.GetByIdAsync(idControl); // Obtener dentro de TX
|
||||||
|
if (controlExistente == null) return (false, "Control de devoluciones no encontrado.");
|
||||||
|
|
||||||
|
// Aquí no hay dependencias directas que afecten saldos, es un registro informativo.
|
||||||
|
// Se podría verificar si está "en uso" si alguna lógica de reporte o cierre depende de él,
|
||||||
|
// pero por ahora, la eliminación es directa.
|
||||||
|
|
||||||
|
var eliminado = await _controlDevRepo.DeleteAsync(idControl, idUsuario, transaction);
|
||||||
|
if (!eliminado) throw new DataException("Error al eliminar el control de devoluciones.");
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("ControlDevoluciones ID {Id} eliminado por Usuario ID {UserId}.", idControl, 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 ControlDevoluciones ID: {Id}", idControl);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,481 @@
|
|||||||
|
using GestionIntegral.Api.Data;
|
||||||
|
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||||
|
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
||||||
|
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;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Distribucion
|
||||||
|
{
|
||||||
|
public class EntradaSalidaCanillaService : IEntradaSalidaCanillaService
|
||||||
|
{
|
||||||
|
private readonly IEntradaSalidaCanillaRepository _esCanillaRepository;
|
||||||
|
private readonly IPublicacionRepository _publicacionRepository;
|
||||||
|
private readonly ICanillaRepository _canillaRepository;
|
||||||
|
private readonly IPrecioRepository _precioRepository;
|
||||||
|
private readonly IRecargoZonaRepository _recargoZonaRepository;
|
||||||
|
private readonly IPorcMonCanillaRepository _porcMonCanillaRepository;
|
||||||
|
private readonly IUsuarioRepository _usuarioRepository;
|
||||||
|
private readonly DbConnectionFactory _connectionFactory;
|
||||||
|
private readonly ILogger<EntradaSalidaCanillaService> _logger;
|
||||||
|
|
||||||
|
public EntradaSalidaCanillaService(
|
||||||
|
IEntradaSalidaCanillaRepository esCanillaRepository,
|
||||||
|
IPublicacionRepository publicacionRepository,
|
||||||
|
ICanillaRepository canillaRepository,
|
||||||
|
IPrecioRepository precioRepository,
|
||||||
|
IRecargoZonaRepository recargoZonaRepository,
|
||||||
|
IPorcMonCanillaRepository porcMonCanillaRepository,
|
||||||
|
IUsuarioRepository usuarioRepository,
|
||||||
|
DbConnectionFactory connectionFactory,
|
||||||
|
ILogger<EntradaSalidaCanillaService> logger)
|
||||||
|
{
|
||||||
|
_esCanillaRepository = esCanillaRepository;
|
||||||
|
_publicacionRepository = publicacionRepository;
|
||||||
|
_canillaRepository = canillaRepository;
|
||||||
|
_precioRepository = precioRepository;
|
||||||
|
_recargoZonaRepository = recargoZonaRepository;
|
||||||
|
_porcMonCanillaRepository = porcMonCanillaRepository;
|
||||||
|
_usuarioRepository = usuarioRepository;
|
||||||
|
_connectionFactory = connectionFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<EntradaSalidaCanillaDto?> MapToDto(EntradaSalidaCanilla? es)
|
||||||
|
{
|
||||||
|
if (es == null) return null;
|
||||||
|
|
||||||
|
var publicacionDataResult = await _publicacionRepository.GetByIdAsync(es.IdPublicacion);
|
||||||
|
var canillaDataResult = await _canillaRepository.GetByIdAsync(es.IdCanilla); // Devuelve tupla
|
||||||
|
|
||||||
|
Publicacion? publicacionEntity = publicacionDataResult.Publicacion; // Componente nullable de la tupla
|
||||||
|
Canilla? canillaEntity = canillaDataResult.Canilla; // Componente nullable de la tupla
|
||||||
|
|
||||||
|
var usuarioLiq = es.UserLiq.HasValue ? await _usuarioRepository.GetByIdAsync(es.UserLiq.Value) : null;
|
||||||
|
|
||||||
|
decimal montoARendir = await CalcularMontoARendir(es, canillaEntity, publicacionEntity);
|
||||||
|
|
||||||
|
return new EntradaSalidaCanillaDto
|
||||||
|
{
|
||||||
|
IdParte = es.IdParte,
|
||||||
|
IdPublicacion = es.IdPublicacion,
|
||||||
|
NombrePublicacion = publicacionEntity?.Nombre ?? "Pub. Desconocida",
|
||||||
|
IdCanilla = es.IdCanilla,
|
||||||
|
NomApeCanilla = canillaEntity?.NomApe ?? "Can. Desconocido",
|
||||||
|
CanillaEsAccionista = canillaEntity?.Accionista ?? false,
|
||||||
|
Fecha = es.Fecha.ToString("yyyy-MM-dd"),
|
||||||
|
CantSalida = es.CantSalida,
|
||||||
|
CantEntrada = es.CantEntrada,
|
||||||
|
Vendidos = es.CantSalida - es.CantEntrada,
|
||||||
|
Observacion = es.Observacion,
|
||||||
|
Liquidado = es.Liquidado,
|
||||||
|
FechaLiquidado = es.FechaLiquidado?.ToString("yyyy-MM-dd"),
|
||||||
|
UserLiq = es.UserLiq,
|
||||||
|
NombreUserLiq = usuarioLiq != null ? $"{usuarioLiq.Nombre} {usuarioLiq.Apellido}" : null,
|
||||||
|
MontoARendir = montoARendir,
|
||||||
|
PrecioUnitarioAplicado = (await _precioRepository.GetByIdAsync(es.IdPrecio))?.Lunes ?? 0,
|
||||||
|
RecargoAplicado = es.IdRecargo > 0 ? (await _recargoZonaRepository.GetByIdAsync(es.IdRecargo))?.Valor ?? 0 : 0,
|
||||||
|
PorcentajeOMontoCanillaAplicado = es.IdPorcMon > 0 ? (await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon))?.PorcMon ?? 0 : 0,
|
||||||
|
EsPorcentajeCanilla = es.IdPorcMon > 0 ? (await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon))?.EsPorcentaje ?? false : false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<decimal> CalcularMontoARendir(EntradaSalidaCanilla es, Canilla? canilla, Publicacion? publicacion) // Acepta nullable Canilla y Publicacion
|
||||||
|
{
|
||||||
|
if (es.CantSalida - es.CantEntrada <= 0) return 0;
|
||||||
|
|
||||||
|
var precioConfig = await _precioRepository.GetByIdAsync(es.IdPrecio);
|
||||||
|
if (precioConfig == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Configuración de precio ID {IdPrecio} no encontrada para movimiento ID {IdParte}.", es.IdPrecio, es.IdParte);
|
||||||
|
throw new InvalidOperationException($"Configuración de precio ID {es.IdPrecio} no encontrada para el movimiento.");
|
||||||
|
}
|
||||||
|
|
||||||
|
decimal precioDia = 0;
|
||||||
|
DayOfWeek diaSemana = es.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 (es.IdRecargo > 0)
|
||||||
|
{
|
||||||
|
var recargoConfig = await _recargoZonaRepository.GetByIdAsync(es.IdRecargo);
|
||||||
|
if (recargoConfig != null) valorRecargo = recargoConfig.Valor;
|
||||||
|
}
|
||||||
|
|
||||||
|
decimal precioFinalUnitario = precioDia + valorRecargo;
|
||||||
|
int cantidadVendida = es.CantSalida - es.CantEntrada;
|
||||||
|
|
||||||
|
if (canilla != null && canilla.Accionista && es.IdPorcMon > 0) // Check null para canilla
|
||||||
|
{
|
||||||
|
var porcMonConfig = await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon);
|
||||||
|
if (porcMonConfig != null)
|
||||||
|
{
|
||||||
|
if (porcMonConfig.EsPorcentaje)
|
||||||
|
{
|
||||||
|
return Math.Round((precioFinalUnitario * cantidadVendida * porcMonConfig.PorcMon) / 100, 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Math.Round(cantidadVendida * porcMonConfig.PorcMon, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Math.Round(precioFinalUnitario * cantidadVendida, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<EntradaSalidaCanillaDto>> ObtenerTodosAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
int? idPublicacion, int? idCanilla, bool? liquidados, bool? incluirNoLiquidados)
|
||||||
|
{
|
||||||
|
bool? filtroLiquidadoFinal = null;
|
||||||
|
if (liquidados.HasValue)
|
||||||
|
{
|
||||||
|
filtroLiquidadoFinal = liquidados.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (incluirNoLiquidados.HasValue && !incluirNoLiquidados.Value)
|
||||||
|
{
|
||||||
|
filtroLiquidadoFinal = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var movimientos = await _esCanillaRepository.GetAllAsync(fechaDesde, fechaHasta, idPublicacion, idCanilla, filtroLiquidadoFinal);
|
||||||
|
var dtos = new List<EntradaSalidaCanillaDto>();
|
||||||
|
foreach (var mov in movimientos)
|
||||||
|
{
|
||||||
|
var dto = await MapToDto(mov);
|
||||||
|
if (dto != null) dtos.Add(dto);
|
||||||
|
}
|
||||||
|
return dtos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EntradaSalidaCanillaDto?> ObtenerPorIdAsync(int idParte)
|
||||||
|
{
|
||||||
|
var movimiento = await _esCanillaRepository.GetByIdAsync(idParte);
|
||||||
|
return await MapToDto(movimiento);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaCanillaDto updateDto, int idUsuario)
|
||||||
|
{
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
|
||||||
|
using var transaction = connection.BeginTransaction();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte);
|
||||||
|
if (esExistente == null) return (false, "Movimiento no encontrado.");
|
||||||
|
if (esExistente.Liquidado) return (false, "No se puede modificar un movimiento ya liquidado.");
|
||||||
|
|
||||||
|
esExistente.CantSalida = updateDto.CantSalida;
|
||||||
|
esExistente.CantEntrada = updateDto.CantEntrada;
|
||||||
|
esExistente.Observacion = updateDto.Observacion;
|
||||||
|
|
||||||
|
var actualizado = await _esCanillaRepository.UpdateAsync(esExistente, idUsuario, transaction);
|
||||||
|
if (!actualizado) throw new DataException("Error al actualizar el movimiento.");
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
_logger.LogInformation("Movimiento Canillita ID {Id} actualizado por Usuario ID {UserId}.", idParte, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException) { if (transaction.Connection != null) try { transaction.Rollback(); } catch { } return (false, "Movimiento no encontrado."); }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (transaction.Connection != null) try { transaction.Rollback(); } catch { }
|
||||||
|
_logger.LogError(ex, "Error ActualizarMovimientoAsync Canillita ID: {Id}", idParte);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (connection?.State == ConnectionState.Open)
|
||||||
|
{
|
||||||
|
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario, ClaimsPrincipal userPrincipal)
|
||||||
|
{
|
||||||
|
// Helper interno para verificar permisos desde el ClaimsPrincipal proporcionado
|
||||||
|
bool TienePermisoEspecifico(string codAcc) => userPrincipal.IsInRole("SuperAdmin") || userPrincipal.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||||
|
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
IDbTransaction? transaction = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync();
|
||||||
|
else if (connection.State == ConnectionState.Closed) connection.Open();
|
||||||
|
|
||||||
|
transaction = connection.BeginTransaction();
|
||||||
|
|
||||||
|
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte);
|
||||||
|
if (esExistente == null)
|
||||||
|
{
|
||||||
|
if (transaction?.Connection != null) transaction.Rollback();
|
||||||
|
return (false, "Movimiento no encontrado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esExistente.Liquidado)
|
||||||
|
{
|
||||||
|
// Permiso MC006 es para "Eliminar Movimientos de Canillita Liquidados"
|
||||||
|
if (!TienePermisoEspecifico("MC006"))
|
||||||
|
{
|
||||||
|
if (transaction?.Connection != null) transaction.Rollback();
|
||||||
|
return (false, "No tiene permiso para eliminar movimientos ya liquidados. Se requiere permiso especial (MC006) o ser SuperAdmin.");
|
||||||
|
}
|
||||||
|
_logger.LogWarning("Usuario ID {IdUsuario} está eliminando un movimiento LIQUIDADO (IDParte: {IdParte}). Permiso MC006 verificado.", idUsuario, idParte);
|
||||||
|
}
|
||||||
|
// Si no está liquidado, el permiso MC004 ya fue verificado en el controlador.
|
||||||
|
|
||||||
|
var eliminado = await _esCanillaRepository.DeleteAsync(idParte, idUsuario, transaction);
|
||||||
|
if (!eliminado)
|
||||||
|
{
|
||||||
|
// No es necesario hacer rollback aquí si DeleteAsync lanza una excepción,
|
||||||
|
// ya que el bloque catch lo manejará. Si DeleteAsync devuelve false sin lanzar,
|
||||||
|
// entonces sí sería necesario un rollback.
|
||||||
|
if (transaction?.Connection != null) transaction.Rollback();
|
||||||
|
throw new DataException("Error al eliminar el movimiento desde el repositorio.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction?.Connection != null) transaction.Commit();
|
||||||
|
_logger.LogInformation("Movimiento Canillita ID {IdParte} eliminado por Usuario ID {IdUsuario}.", idParte, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
if (transaction?.Connection != null) try { transaction.Rollback(); } catch (Exception exR) { _logger.LogError(exR, "Rollback fallido KeyNotFoundException."); }
|
||||||
|
return (false, "Movimiento no encontrado.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (transaction?.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
|
||||||
|
_logger.LogError(ex, "Error EliminarMovimientoAsync Canillita ID: {IdParte}", idParte);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (transaction != null) transaction.Dispose();
|
||||||
|
if (connection?.State == ConnectionState.Open)
|
||||||
|
{
|
||||||
|
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> LiquidarMovimientosAsync(LiquidarMovimientosCanillaRequestDto liquidarDto, int idUsuarioLiquidador)
|
||||||
|
{
|
||||||
|
if (liquidarDto.IdsPartesALiquidar == null || !liquidarDto.IdsPartesALiquidar.Any())
|
||||||
|
return (false, "No se especificaron movimientos para liquidar.");
|
||||||
|
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
|
||||||
|
using var transaction = connection.BeginTransaction();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool liquidacionExitosa = await _esCanillaRepository.LiquidarAsync(liquidarDto.IdsPartesALiquidar, liquidarDto.FechaLiquidacion.Date, idUsuarioLiquidador, transaction);
|
||||||
|
|
||||||
|
if (!liquidacionExitosa)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Liquidación de movimientos de canillita pudo no haber afectado a todos los IDs solicitados. IDs: {Ids}", string.Join(",", liquidarDto.IdsPartesALiquidar));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction.Connection != null) transaction.Commit();
|
||||||
|
_logger.LogInformation("Movimientos de Canillita liquidados. Ids: {Ids} por Usuario ID {UserId}.", string.Join(",", liquidarDto.IdsPartesALiquidar), idUsuarioLiquidador);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (transaction.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
|
||||||
|
_logger.LogError(ex, "Error al liquidar movimientos de canillita.");
|
||||||
|
return (false, $"Error interno al liquidar movimientos: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (connection?.State == ConnectionState.Open)
|
||||||
|
{
|
||||||
|
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(IEnumerable<EntradaSalidaCanillaDto>? MovimientosCreados, string? Error)> CrearMovimientosEnLoteAsync(CreateBulkEntradaSalidaCanillaDto createBulkDto, int idUsuario)
|
||||||
|
{
|
||||||
|
var canillaDataResult = await _canillaRepository.GetByIdAsync(createBulkDto.IdCanilla);
|
||||||
|
Canilla? canillaActual = canillaDataResult.Canilla;
|
||||||
|
if (canillaActual == null) return (null, "Canillita no válido.");
|
||||||
|
if (canillaActual.Baja) return (null, "El canillita está dado de baja.");
|
||||||
|
|
||||||
|
List<EntradaSalidaCanilla> movimientosCreadosEntidades = new List<EntradaSalidaCanilla>();
|
||||||
|
List<string> erroresItems = new List<string>();
|
||||||
|
|
||||||
|
using var connection = _connectionFactory.CreateConnection();
|
||||||
|
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
|
||||||
|
using var transaction = connection.BeginTransaction();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var itemDto in createBulkDto.Items.Where(i => i.IdPublicacion > 0))
|
||||||
|
{
|
||||||
|
if (await _esCanillaRepository.ExistsByPublicacionCanillaFechaAsync(itemDto.IdPublicacion, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, transaction: transaction))
|
||||||
|
{
|
||||||
|
var pubInfo = await _publicacionRepository.GetByIdSimpleAsync(itemDto.IdPublicacion);
|
||||||
|
erroresItems.Add($"Ya existe un registro para la publicación '{pubInfo?.Nombre ?? itemDto.IdPublicacion.ToString()}' para {canillaActual.NomApe} en la fecha {createBulkDto.Fecha:dd/MM/yyyy}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (erroresItems.Any())
|
||||||
|
{
|
||||||
|
if (transaction.Connection != null) transaction.Rollback();
|
||||||
|
return (null, string.Join(" ", erroresItems));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var itemDto in createBulkDto.Items)
|
||||||
|
{
|
||||||
|
if (itemDto.IdPublicacion == 0 && itemDto.CantSalida == 0 && itemDto.CantEntrada == 0 && string.IsNullOrWhiteSpace(itemDto.Observacion)) continue;
|
||||||
|
if (itemDto.IdPublicacion == 0)
|
||||||
|
{
|
||||||
|
if (itemDto.CantSalida > 0 || itemDto.CantEntrada > 0 || !string.IsNullOrWhiteSpace(itemDto.Observacion))
|
||||||
|
{
|
||||||
|
erroresItems.Add($"Falta seleccionar la publicación para una de las líneas con cantidades/observación.");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var publicacionItem = await _publicacionRepository.GetByIdSimpleAsync(itemDto.IdPublicacion);
|
||||||
|
bool noEsValidaONoHabilitada = false;
|
||||||
|
if (publicacionItem == null)
|
||||||
|
{
|
||||||
|
noEsValidaONoHabilitada = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Si Habilitada es bool? y NULL significa que toma el DEFAULT 1 (true) de la BD
|
||||||
|
// entonces solo consideramos error si es explícitamente false.
|
||||||
|
if (publicacionItem.Habilitada.HasValue && publicacionItem.Habilitada.Value == false)
|
||||||
|
{
|
||||||
|
noEsValidaONoHabilitada = true;
|
||||||
|
}
|
||||||
|
// Si publicacionItem.Habilitada es null o true, noEsValidaONoHabilitada permanece false.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noEsValidaONoHabilitada)
|
||||||
|
{
|
||||||
|
erroresItems.Add($"Publicación ID {itemDto.IdPublicacion} no es válida o no está habilitada.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var precioActivo = await _precioRepository.GetActiveByPublicacionAndDateAsync(itemDto.IdPublicacion, createBulkDto.Fecha.Date, transaction);
|
||||||
|
if (precioActivo == null)
|
||||||
|
{
|
||||||
|
string nombrePubParaError = publicacionItem?.Nombre ?? $"ID {itemDto.IdPublicacion}";
|
||||||
|
erroresItems.Add($"No hay precio definido para '{nombrePubParaError}' en {createBulkDto.Fecha:dd/MM/yyyy}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RecargoZona? recargoActivo = null;
|
||||||
|
// Aquí usamos canillaActual! porque ya verificamos que no es null al inicio del método.
|
||||||
|
if (canillaActual!.IdZona > 0)
|
||||||
|
{
|
||||||
|
recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(itemDto.IdPublicacion, canillaActual.IdZona, createBulkDto.Fecha.Date, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
PorcMonCanilla? porcMonActivo = null;
|
||||||
|
// Aquí usamos canillaActual! porque ya verificamos que no es null al inicio del método.
|
||||||
|
if (canillaActual!.Accionista)
|
||||||
|
{
|
||||||
|
porcMonActivo = await _porcMonCanillaRepository.GetActiveByPublicacionCanillaAndDateAsync(itemDto.IdPublicacion, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, transaction);
|
||||||
|
if (porcMonActivo == null)
|
||||||
|
{
|
||||||
|
// Dentro de este bloque, canillaActual.NomApe es seguro porque Accionista era true.
|
||||||
|
string nombreCanParaError = canillaActual.NomApe;
|
||||||
|
string nombrePubParaError = publicacionItem?.Nombre ?? $"Publicación ID {itemDto.IdPublicacion}";
|
||||||
|
erroresItems.Add($"'{nombreCanParaError}' es accionista pero no tiene %/monto para '{nombrePubParaError}' en {createBulkDto.Fecha:dd/MM/yyyy}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nuevoES = new EntradaSalidaCanilla
|
||||||
|
{
|
||||||
|
IdPublicacion = itemDto.IdPublicacion,
|
||||||
|
IdCanilla = createBulkDto.IdCanilla,
|
||||||
|
Fecha = createBulkDto.Fecha.Date,
|
||||||
|
CantSalida = itemDto.CantSalida,
|
||||||
|
CantEntrada = itemDto.CantEntrada,
|
||||||
|
Observacion = itemDto.Observacion,
|
||||||
|
IdPrecio = precioActivo.IdPrecio,
|
||||||
|
IdRecargo = recargoActivo?.IdRecargo ?? 0,
|
||||||
|
IdPorcMon = porcMonActivo?.IdPorcMon ?? 0,
|
||||||
|
Liquidado = false,
|
||||||
|
FechaLiquidado = null,
|
||||||
|
UserLiq = null
|
||||||
|
};
|
||||||
|
|
||||||
|
var esCreada = await _esCanillaRepository.CreateAsync(nuevoES, idUsuario, transaction);
|
||||||
|
if (esCreada == null) throw new DataException($"Error al registrar movimiento para Publicación ID {itemDto.IdPublicacion}.");
|
||||||
|
movimientosCreadosEntidades.Add(esCreada);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (erroresItems.Any())
|
||||||
|
{
|
||||||
|
if (transaction.Connection != null) transaction.Rollback();
|
||||||
|
return (null, string.Join(" ", erroresItems));
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECCIÓN PARA CS0019 (línea 394 original):
|
||||||
|
bool tieneItemsSignificativos = false;
|
||||||
|
if (createBulkDto.Items != null) // Checkear si Items es null antes de llamar a Any()
|
||||||
|
{
|
||||||
|
tieneItemsSignificativos = createBulkDto.Items.Any(i => i.IdPublicacion > 0 &&
|
||||||
|
(i.CantSalida > 0 || i.CantEntrada > 0 || !string.IsNullOrWhiteSpace(i.Observacion)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!movimientosCreadosEntidades.Any() && tieneItemsSignificativos)
|
||||||
|
{
|
||||||
|
if (transaction.Connection != null) transaction.Rollback();
|
||||||
|
return (null, "No se pudo procesar ningún ítem válido con datos significativos.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction.Connection != null) transaction.Commit();
|
||||||
|
_logger.LogInformation("Lote de {Count} movimientos Canillita para Canilla ID {IdCanilla} en Fecha {Fecha} creados por Usuario ID {UserId}.",
|
||||||
|
movimientosCreadosEntidades.Count, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, idUsuario);
|
||||||
|
|
||||||
|
var dtosCreados = new List<EntradaSalidaCanillaDto>();
|
||||||
|
foreach (var entidad in movimientosCreadosEntidades)
|
||||||
|
{
|
||||||
|
var dto = await MapToDto(entidad);
|
||||||
|
if (dto != null) dtosCreados.Add(dto);
|
||||||
|
}
|
||||||
|
return (dtosCreados, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (transaction.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
|
||||||
|
_logger.LogError(ex, "Error CrearMovimientosEnLoteAsync para Canilla ID {IdCanilla}, Fecha {Fecha}", createBulkDto.IdCanilla, createBulkDto.Fecha);
|
||||||
|
return (null, $"Error interno al procesar el lote: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (connection?.State == ConnectionState.Open)
|
||||||
|
{
|
||||||
|
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,9 +57,29 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
var publicacionData = await _publicacionRepository.GetByIdAsync(es.IdPublicacion);
|
var publicacionData = await _publicacionRepository.GetByIdAsync(es.IdPublicacion);
|
||||||
var distribuidorData = await _distribuidorRepository.GetByIdAsync(es.IdDistribuidor);
|
var distribuidorData = await _distribuidorRepository.GetByIdAsync(es.IdDistribuidor);
|
||||||
|
|
||||||
decimal montoCalculado = await CalcularMontoMovimiento(es.IdPublicacion, es.IdDistribuidor, es.Fecha, es.Cantidad, es.TipoMovimiento,
|
// Obtener el valor bruto del movimiento
|
||||||
es.IdPrecio, es.IdRecargo, es.IdPorcentaje, distribuidorData.Distribuidor?.IdZona);
|
decimal valorBrutoMovimiento = await CalcularMontoMovimiento(
|
||||||
|
es.IdPublicacion, es.IdDistribuidor, es.Fecha, es.Cantidad,
|
||||||
|
es.TipoMovimiento, // Pasamos el tipo de movimiento original aquí
|
||||||
|
es.IdPrecio, es.IdRecargo, es.IdPorcentaje, distribuidorData.Distribuidor?.IdZona
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ajustar para el DTO: si es "Entrada", el monto calculado es un crédito (negativo o positivo según convención)
|
||||||
|
// Para consistencia con el ajuste de saldo, si es Entrada, el MontoCalculado para el DTO puede ser el valor
|
||||||
|
// que se le "acredita" al distribuidor (o sea, el valor de la mercadería devuelta).
|
||||||
|
// La lógica de +/- para el saldo ya está en Crear/Actualizar/Eliminar.
|
||||||
|
// Aquí solo mostramos el valor del movimiento. Si es entrada, es el valor de lo devuelto.
|
||||||
|
// Si es salida, es el valor de lo que se le factura.
|
||||||
|
// El método CalcularMonto ya devuelve el monto que el distribuidor DEBE pagar por una SALIDA.
|
||||||
|
// Para una ENTRADA (devolución), el valor de esa mercadería es el mismo, pero opera en sentido contrario al saldo.
|
||||||
|
|
||||||
|
decimal montoCalculadoParaDto = valorBrutoMovimiento;
|
||||||
|
// Si queremos que el DTO muestre las entradas como un valor que "reduce la deuda",
|
||||||
|
// podría ser positivo. Si queremos que refleje el impacto directo en la factura (salidas suman, entradas restan),
|
||||||
|
// podríamos hacerlo negativo.
|
||||||
|
// Por ahora, dejaremos que CalcularMontoMovimiento devuelva el valor de una "Salida",
|
||||||
|
// y si es "Entrada", este mismo valor es el que se acredita.
|
||||||
|
// La columna `MontoCalculado` en el DTO representará el valor de la transacción.
|
||||||
|
|
||||||
return new EntradaSalidaDistDto
|
return new EntradaSalidaDistDto
|
||||||
{
|
{
|
||||||
@@ -75,17 +95,29 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
Cantidad = es.Cantidad,
|
Cantidad = es.Cantidad,
|
||||||
Remito = es.Remito,
|
Remito = es.Remito,
|
||||||
Observacion = es.Observacion,
|
Observacion = es.Observacion,
|
||||||
MontoCalculado = montoCalculado
|
MontoCalculado = montoCalculadoParaDto // Representa el valor de los N ejemplares
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<decimal> CalcularMontoMovimiento(int idPublicacion, int idDistribuidor, DateTime fecha, int cantidad, string tipoMovimiento,
|
private async Task<decimal> CalcularMontoMovimiento(int idPublicacion, int idDistribuidor, DateTime fecha, int cantidad, string tipoMovimiento,
|
||||||
int idPrecio, int idRecargo, int idPorcentaje, int? idZonaDistribuidor)
|
int idPrecio, int idRecargo, int idPorcentaje, int? idZonaDistribuidor)
|
||||||
{
|
{
|
||||||
if (tipoMovimiento == "Entrada") return 0; // Las entradas (devoluciones) no generan "debe" inmediato, ajustan el saldo.
|
// YA NO SE DEVUELVE 0 PARA ENTRADA AQUÍ
|
||||||
|
// if (tipoMovimiento == "Entrada") return 0;
|
||||||
|
|
||||||
var precioConfig = await _precioRepository.GetByIdAsync(idPrecio);
|
var precioConfig = await _precioRepository.GetByIdAsync(idPrecio);
|
||||||
if (precioConfig == null) throw new InvalidOperationException("Configuración de precio no encontrada para el movimiento.");
|
// Es crucial que idPrecio sea válido y se haya determinado correctamente antes de llamar aquí.
|
||||||
|
// Si precioConfig es null, se lanzará una excepción abajo, lo cual está bien si es un estado inesperado.
|
||||||
|
if (precioConfig == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Configuración de precio ID {IdPrecio} no encontrada al calcular monto para Pub {IdPublicacion}, Dist {IdDistribuidor}, Fecha {Fecha}", idPrecio, idPublicacion, idDistribuidor, fecha);
|
||||||
|
// Dependiendo de la regla de negocio, podrías devolver 0 o lanzar una excepción.
|
||||||
|
// Si un precio es OBLIGATORIO para cualquier movimiento, lanzar excepción es más apropiado.
|
||||||
|
// Si puede haber movimientos sin precio (ej. cortesía que no se factura), entonces 0.
|
||||||
|
// En este contexto, un precio es fundamental para el cálculo.
|
||||||
|
throw new InvalidOperationException($"Configuración de precio ID {idPrecio} no encontrada. No se puede calcular el monto.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
decimal precioDia = 0;
|
decimal precioDia = 0;
|
||||||
DayOfWeek diaSemana = fecha.DayOfWeek;
|
DayOfWeek diaSemana = fecha.DayOfWeek;
|
||||||
@@ -101,9 +133,8 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
}
|
}
|
||||||
|
|
||||||
decimal valorRecargo = 0;
|
decimal valorRecargo = 0;
|
||||||
if (idRecargo > 0 && idZonaDistribuidor.HasValue) // El recargo se aplica por la zona del distribuidor
|
if (idRecargo > 0 && idZonaDistribuidor.HasValue)
|
||||||
{
|
{
|
||||||
// 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);
|
var recargoConfig = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(idPublicacion, idZonaDistribuidor.Value, fecha);
|
||||||
if (recargoConfig != null)
|
if (recargoConfig != null)
|
||||||
{
|
{
|
||||||
@@ -119,10 +150,13 @@ namespace GestionIntegral.Api.Services.Distribucion
|
|||||||
var porcConfig = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
|
var porcConfig = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
|
||||||
if (porcConfig != null)
|
if (porcConfig != null)
|
||||||
{
|
{
|
||||||
return (montoBase / 100) * porcConfig.Porcentaje;
|
// El porcentaje de pago del distribuidor es lo que ÉL PAGA a la editorial.
|
||||||
|
// Entonces, el monto es (precio_tapa_con_recargo * cantidad) * (porcentaje_pago_dist / 100)
|
||||||
|
return (montoBase * porcConfig.Porcentaje) / 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return montoBase; // Si no hay porcentaje, se factura el 100% del precio con recargo
|
// Si no hay porcentaje de pago específico, se asume que el distribuidor paga el 100% del monto base.
|
||||||
|
return montoBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 IControlDevolucionesService
|
||||||
|
{
|
||||||
|
Task<IEnumerable<ControlDevolucionesDto>> ObtenerTodosAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idEmpresa);
|
||||||
|
Task<ControlDevolucionesDto?> ObtenerPorIdAsync(int idControl);
|
||||||
|
Task<(ControlDevolucionesDto? Control, string? Error)> CrearAsync(CreateControlDevolucionesDto createDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> ActualizarAsync(int idControl, UpdateControlDevolucionesDto updateDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> EliminarAsync(int idControl, int idUsuario);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Distribucion;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Distribucion
|
||||||
|
{
|
||||||
|
public interface IEntradaSalidaCanillaService
|
||||||
|
{
|
||||||
|
Task<IEnumerable<EntradaSalidaCanillaDto>> ObtenerTodosAsync(
|
||||||
|
DateTime? fechaDesde, DateTime? fechaHasta,
|
||||||
|
int? idPublicacion, int? idCanilla, bool? liquidados, bool? incluirNoLiquidados);
|
||||||
|
|
||||||
|
Task<EntradaSalidaCanillaDto?> ObtenerPorIdAsync(int idParte);
|
||||||
|
Task<(IEnumerable<EntradaSalidaCanillaDto>? MovimientosCreados, string? Error)> CrearMovimientosEnLoteAsync(CreateBulkEntradaSalidaCanillaDto createBulkDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaCanillaDto updateDto, int idUsuario);
|
||||||
|
|
||||||
|
Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario, ClaimsPrincipal userPrincipal);
|
||||||
|
|
||||||
|
Task<(bool Exito, string? Error)> LiquidarMovimientosAsync(LiquidarMovimientosCanillaRequestDto liquidarDto, int idUsuarioLiquidador);
|
||||||
|
}
|
||||||
|
}
|
||||||
173
Backend/GestionIntegral.Api/Services/Radios/CancionService.cs
Normal file
173
Backend/GestionIntegral.Api/Services/Radios/CancionService.cs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
using GestionIntegral.Api.Data.Repositories.Radios;
|
||||||
|
using GestionIntegral.Api.Dtos.Radios;
|
||||||
|
using GestionIntegral.Api.Models.Radios;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
// using GestionIntegral.Api.Data; // Para DbConnectionFactory si se usa transacción
|
||||||
|
// using System.Data; // Para IsolationLevel si se usa transacción
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Radios
|
||||||
|
{
|
||||||
|
public class CancionService : ICancionService
|
||||||
|
{
|
||||||
|
private readonly ICancionRepository _cancionRepository;
|
||||||
|
private readonly IRitmoRepository _ritmoRepository;
|
||||||
|
private readonly ILogger<CancionService> _logger;
|
||||||
|
|
||||||
|
public CancionService(
|
||||||
|
ICancionRepository cancionRepository,
|
||||||
|
IRitmoRepository ritmoRepository,
|
||||||
|
ILogger<CancionService> logger)
|
||||||
|
{
|
||||||
|
_cancionRepository = cancionRepository;
|
||||||
|
_ritmoRepository = ritmoRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<CancionDto> MapToDto(Cancion cancion)
|
||||||
|
{
|
||||||
|
if (cancion == null) return null!;
|
||||||
|
|
||||||
|
string? nombreRitmo = null;
|
||||||
|
if (cancion.Ritmo.HasValue && cancion.Ritmo.Value > 0)
|
||||||
|
{
|
||||||
|
var ritmoDb = await _ritmoRepository.GetByIdAsync(cancion.Ritmo.Value);
|
||||||
|
nombreRitmo = ritmoDb?.NombreRitmo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CancionDto
|
||||||
|
{
|
||||||
|
Id = cancion.Id,
|
||||||
|
Tema = cancion.Tema,
|
||||||
|
CompositorAutor = cancion.CompositorAutor,
|
||||||
|
Interprete = cancion.Interprete,
|
||||||
|
Sello = cancion.Sello,
|
||||||
|
Placa = cancion.Placa,
|
||||||
|
Pista = cancion.Pista,
|
||||||
|
Introduccion = cancion.Introduccion,
|
||||||
|
IdRitmo = cancion.Ritmo, // Pasar el valor de cancion.Ritmo
|
||||||
|
NombreRitmo = nombreRitmo,
|
||||||
|
Formato = cancion.Formato,
|
||||||
|
Album = cancion.Album
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CancionDto>> ObtenerTodasAsync(string? temaFilter, string? interpreteFilter, int? idRitmoFilter)
|
||||||
|
{
|
||||||
|
var canciones = await _cancionRepository.GetAllAsync(temaFilter, interpreteFilter, idRitmoFilter);
|
||||||
|
var dtos = new List<CancionDto>();
|
||||||
|
foreach (var cancion in canciones)
|
||||||
|
{
|
||||||
|
dtos.Add(await MapToDto(cancion));
|
||||||
|
}
|
||||||
|
return dtos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CancionDto?> ObtenerPorIdAsync(int id)
|
||||||
|
{
|
||||||
|
var cancion = await _cancionRepository.GetByIdAsync(id);
|
||||||
|
return cancion == null ? null : await MapToDto(cancion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(CancionDto? Cancion, string? Error)> CrearAsync(CreateCancionDto createDto, int idUsuario)
|
||||||
|
{
|
||||||
|
if (createDto.IdRitmo.HasValue && createDto.IdRitmo.Value > 0) // Asegurar que > 0 para evitar buscar ritmo con ID 0
|
||||||
|
{
|
||||||
|
if(await _ritmoRepository.GetByIdAsync(createDto.IdRitmo.Value) == null)
|
||||||
|
return (null, "El ritmo seleccionado no es válido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(createDto.Tema) && !string.IsNullOrWhiteSpace(createDto.Interprete) &&
|
||||||
|
await _cancionRepository.ExistsByTemaAndInterpreteAsync(createDto.Tema, createDto.Interprete))
|
||||||
|
{
|
||||||
|
return (null, "Ya existe una canción con el mismo tema e intérprete.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var nuevaCancion = new Cancion
|
||||||
|
{
|
||||||
|
Tema = createDto.Tema, CompositorAutor = createDto.CompositorAutor, Interprete = createDto.Interprete,
|
||||||
|
Sello = createDto.Sello, Placa = createDto.Placa, Pista = createDto.Pista,
|
||||||
|
Introduccion = createDto.Introduccion,
|
||||||
|
Ritmo = createDto.IdRitmo, // Asignar createDto.IdRitmo a la propiedad Ritmo del modelo
|
||||||
|
Formato = createDto.Formato, Album = createDto.Album
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cancionCreada = await _cancionRepository.CreateAsync(nuevaCancion);
|
||||||
|
if (cancionCreada == null) return (null, "Error al crear la canción.");
|
||||||
|
|
||||||
|
_logger.LogInformation("Canción ID {Id} creada por Usuario ID {UserId}.", cancionCreada.Id, idUsuario);
|
||||||
|
return (await MapToDto(cancionCreada), null);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error CrearAsync Cancion: {Tema}", createDto.Tema);
|
||||||
|
return (null, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateCancionDto updateDto, int idUsuario)
|
||||||
|
{
|
||||||
|
var cancionExistente = await _cancionRepository.GetByIdAsync(id);
|
||||||
|
if (cancionExistente == null) return (false, "Canción no encontrada.");
|
||||||
|
|
||||||
|
if (updateDto.IdRitmo.HasValue && updateDto.IdRitmo.Value > 0) // Asegurar que > 0
|
||||||
|
{
|
||||||
|
if (await _ritmoRepository.GetByIdAsync(updateDto.IdRitmo.Value) == null)
|
||||||
|
return (false, "El ritmo seleccionado no es válido.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ((!string.IsNullOrWhiteSpace(updateDto.Tema) && !string.IsNullOrWhiteSpace(updateDto.Interprete)) &&
|
||||||
|
(cancionExistente.Tema != updateDto.Tema || cancionExistente.Interprete != updateDto.Interprete) &&
|
||||||
|
await _cancionRepository.ExistsByTemaAndInterpreteAsync(updateDto.Tema!, updateDto.Interprete!, id))
|
||||||
|
{
|
||||||
|
return (false, "Ya existe otra canción con el mismo tema e intérprete."); // Devolver tupla bool,string
|
||||||
|
}
|
||||||
|
|
||||||
|
cancionExistente.Tema = updateDto.Tema; cancionExistente.CompositorAutor = updateDto.CompositorAutor;
|
||||||
|
cancionExistente.Interprete = updateDto.Interprete; cancionExistente.Sello = updateDto.Sello;
|
||||||
|
cancionExistente.Placa = updateDto.Placa; cancionExistente.Pista = updateDto.Pista;
|
||||||
|
cancionExistente.Introduccion = updateDto.Introduccion;
|
||||||
|
cancionExistente.Ritmo = updateDto.IdRitmo; // Asignar updateDto.IdRitmo a la propiedad Ritmo del modelo
|
||||||
|
cancionExistente.Formato = updateDto.Formato; cancionExistente.Album = updateDto.Album;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var actualizado = await _cancionRepository.UpdateAsync(cancionExistente);
|
||||||
|
if (!actualizado) return (false, "Error al actualizar la canción.");
|
||||||
|
|
||||||
|
_logger.LogInformation("Canción ID {Id} actualizada por Usuario ID {UserId}.", id, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error ActualizarAsync Canción ID: {Id}", id);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
|
||||||
|
{
|
||||||
|
var cancionExistente = await _cancionRepository.GetByIdAsync(id);
|
||||||
|
if (cancionExistente == null) return (false, "Canción no encontrada.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var eliminado = await _cancionRepository.DeleteAsync(id);
|
||||||
|
if (!eliminado) return (false, "Error al eliminar la canción.");
|
||||||
|
|
||||||
|
_logger.LogInformation("Canción ID {Id} eliminada por Usuario ID {UserId}.", id, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error EliminarAsync Canción ID: {Id}", id);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Radios;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Radios
|
||||||
|
{
|
||||||
|
public interface ICancionService
|
||||||
|
{
|
||||||
|
Task<IEnumerable<CancionDto>> ObtenerTodasAsync(string? temaFilter, string? interpreteFilter, int? idRitmoFilter);
|
||||||
|
Task<CancionDto?> ObtenerPorIdAsync(int id);
|
||||||
|
Task<(CancionDto? Cancion, string? Error)> CrearAsync(CreateCancionDto createDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateCancionDto updateDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Radios;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc; // Para IActionResult (o devolver byte[])
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Radios
|
||||||
|
{
|
||||||
|
public interface IRadioListaService
|
||||||
|
{
|
||||||
|
// Devuelve byte[] para el archivo y el nombre del archivo sugerido
|
||||||
|
Task<(byte[] FileContents, string ContentType, string FileName, string? Error)> GenerarListaRadioAsync(GenerarListaRadioRequestDto request);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Backend/GestionIntegral.Api/Services/Radios/IRitmoService.cs
Normal file
15
Backend/GestionIntegral.Api/Services/Radios/IRitmoService.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using GestionIntegral.Api.Dtos.Radios;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Radios
|
||||||
|
{
|
||||||
|
public interface IRitmoService
|
||||||
|
{
|
||||||
|
Task<IEnumerable<RitmoDto>> ObtenerTodosAsync(string? nombreFilter);
|
||||||
|
Task<RitmoDto?> ObtenerPorIdAsync(int id);
|
||||||
|
Task<(RitmoDto? Ritmo, string? Error)> CrearAsync(CreateRitmoDto createDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateRitmoDto updateDto, int idUsuario);
|
||||||
|
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
|
||||||
|
}
|
||||||
|
}
|
||||||
224
Backend/GestionIntegral.Api/Services/Radios/RadioListaService.cs
Normal file
224
Backend/GestionIntegral.Api/Services/Radios/RadioListaService.cs
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
using GestionIntegral.Api.Data.Repositories.Radios;
|
||||||
|
using GestionIntegral.Api.Dtos.Radios;
|
||||||
|
using GestionIntegral.Api.Models.Radios; // Para Cancion
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NPOI.SS.UserModel;
|
||||||
|
using NPOI.XSSF.UserModel;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Radios
|
||||||
|
{
|
||||||
|
public class RadioListaService : IRadioListaService
|
||||||
|
{
|
||||||
|
private readonly ICancionRepository _cancionRepository;
|
||||||
|
// private readonly IRitmoRepository _ritmoRepository; // No se usa en la nueva lógica de generación de Excel
|
||||||
|
private readonly ILogger<RadioListaService> _logger;
|
||||||
|
|
||||||
|
public RadioListaService(
|
||||||
|
ICancionRepository cancionRepository,
|
||||||
|
// IRitmoRepository ritmoRepository, // Puede ser removido si no se usa en otro lado
|
||||||
|
ILogger<RadioListaService> logger)
|
||||||
|
{
|
||||||
|
_cancionRepository = cancionRepository;
|
||||||
|
// _ritmoRepository = ritmoRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(byte[] FileContents, string ContentType, string FileName, string? Error)> GenerarListaRadioAsync(GenerarListaRadioRequestDto request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Iniciando generación de lista de radio para Mes: {Mes}, Año: {Anio}, Institución: {Institucion}, Radio: {Radio}",
|
||||||
|
request.Mes, request.Anio, request.Institucion, request.Radio);
|
||||||
|
|
||||||
|
var programacionParaExcel = new List<ProgramacionHorariaExcelDto>();
|
||||||
|
int diasEnMes = DateTime.DaysInMonth(request.Anio, request.Mes);
|
||||||
|
|
||||||
|
for (int dia = 1; dia <= diasEnMes; dia++)
|
||||||
|
{
|
||||||
|
int horaInicio = (request.Radio == "FM 99.1") ? 0 : 9;
|
||||||
|
int horaFin = 23;
|
||||||
|
|
||||||
|
for (int hora = horaInicio; hora <= horaFin; hora++)
|
||||||
|
{
|
||||||
|
int cantidadRegistros;
|
||||||
|
if (request.Radio == "FM 99.1")
|
||||||
|
{
|
||||||
|
cantidadRegistros = (hora == 23 || hora == 7 || hora == 15) ? 1 : 5;
|
||||||
|
}
|
||||||
|
else // FM 100.3
|
||||||
|
{
|
||||||
|
cantidadRegistros = (hora == 23 || hora == 15) ? 1 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cantidadRegistros > 0)
|
||||||
|
{
|
||||||
|
var cancionesSeleccionadas = await _cancionRepository.GetRandomCancionesAsync(cantidadRegistros);
|
||||||
|
foreach (var cancion in cancionesSeleccionadas)
|
||||||
|
{
|
||||||
|
programacionParaExcel.Add(new ProgramacionHorariaExcelDto
|
||||||
|
{
|
||||||
|
Dia = dia,
|
||||||
|
Hora = hora,
|
||||||
|
TituloObra = cancion.Tema,
|
||||||
|
CompositorAutor = cancion.CompositorAutor,
|
||||||
|
Interprete = cancion.Interprete,
|
||||||
|
Sello = cancion.Sello,
|
||||||
|
Album = cancion.Album
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!programacionParaExcel.Any())
|
||||||
|
{
|
||||||
|
_logger.LogWarning("No se generaron datos para la lista de radio con los criterios: {@Request}", request);
|
||||||
|
return (Array.Empty<byte>(), "", "", "No se generaron datos para la lista con los criterios dados.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string mesText = request.Mes.ToString("00");
|
||||||
|
string anioFullText = request.Anio.ToString();
|
||||||
|
string anioShortText = anioFullText.Length > 2 ? anioFullText.Substring(anioFullText.Length - 2) : anioFullText;
|
||||||
|
|
||||||
|
byte[] excelBytes = GenerarExcel(programacionParaExcel, request.Institucion, request.Radio, mesText, anioFullText, anioShortText);
|
||||||
|
|
||||||
|
string baseFileName;
|
||||||
|
if (request.Institucion == "AADI")
|
||||||
|
{
|
||||||
|
baseFileName = request.Radio == "FM 99.1" ? $"AADI-FM99.1-FM-{mesText}{anioShortText}" : $"AADI-FM100.3-FM-{mesText}{anioShortText}";
|
||||||
|
}
|
||||||
|
else // SADAIC
|
||||||
|
{
|
||||||
|
baseFileName = request.Radio == "FM 99.1" ? $"FM99.1-FM-{mesText}{anioShortText}" : $"FM100.3-FM-{mesText}{anioShortText}";
|
||||||
|
}
|
||||||
|
string excelFileNameInZip = $"{baseFileName}.xlsx";
|
||||||
|
string zipFileName = $"{baseFileName}.xlsx.zip"; // Para replicar nombre original del zip
|
||||||
|
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
|
||||||
|
{
|
||||||
|
var excelEntry = archive.CreateEntry(excelFileNameInZip, CompressionLevel.Optimal);
|
||||||
|
using (var entryStream = excelEntry.Open())
|
||||||
|
{
|
||||||
|
await entryStream.WriteAsync(excelBytes, 0, excelBytes.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_logger.LogInformation("Lista de radio generada y empaquetada en ZIP exitosamente: {ZipFileName}", zipFileName);
|
||||||
|
return (memoryStream.ToArray(), "application/zip", zipFileName, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error al generar la lista de radio para Mes: {Mes}, Año: {Anio}, Institucion: {Institucion}, Radio: {Radio}", request.Mes, request.Anio, request.Institucion, request.Radio);
|
||||||
|
return (Array.Empty<byte>(), "", "", $"Error interno al generar la lista: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] GenerarExcel(List<ProgramacionHorariaExcelDto> programacion, string institucion, string radio, string mesText, string anioFullText, string anioShortText)
|
||||||
|
{
|
||||||
|
IWorkbook workbook = new XSSFWorkbook();
|
||||||
|
|
||||||
|
var coreProps = ((XSSFWorkbook)workbook).GetProperties().CoreProperties;
|
||||||
|
coreProps.Creator = "GestionIntegral"; // Puedes cambiarlo
|
||||||
|
coreProps.Title = $"Reporte {institucion} - {radio} - {mesText}/{anioFullText}";
|
||||||
|
// coreProps.Description = "Descripción del reporte"; // Opcional
|
||||||
|
// var extendedProps = ((XSSFWorkbook)workbook).GetProperties().ExtendedProperties.GetUnderlyingProperties();
|
||||||
|
// extendedProps.Application = "GestionIntegral System"; // Opcional
|
||||||
|
// extendedProps.AppVersion = "1.0.0"; // Opcional
|
||||||
|
|
||||||
|
string sheetName;
|
||||||
|
if (institucion == "AADI")
|
||||||
|
{
|
||||||
|
sheetName = radio == "FM 99.1" ? $"SA99{mesText}{anioShortText}" : $"SARE{mesText}{anioShortText}";
|
||||||
|
}
|
||||||
|
else // SADAIC
|
||||||
|
{
|
||||||
|
sheetName = radio == "FM 99.1" ? $"FM99{mesText}{anioShortText}" : $"FMRE{mesText}{anioShortText}";
|
||||||
|
}
|
||||||
|
ISheet sheet = workbook.CreateSheet(sheetName);
|
||||||
|
|
||||||
|
int currentRowIdx = 0;
|
||||||
|
if (institucion == "AADI")
|
||||||
|
{
|
||||||
|
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue(radio == "FM 99.1" ? "Nombre: FM LA 99.1" : "Nombre: FM 100.3 LA REDONDA");
|
||||||
|
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue("Localidad: La Plata");
|
||||||
|
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue("FM");
|
||||||
|
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue(radio == "FM 99.1" ? "Frecuencia: 99.1" : "Frecuencia: 100.3");
|
||||||
|
sheet.CreateRow(currentRowIdx++).CreateCell(0).SetCellValue($"Mes: {mesText}/{anioFullText}");
|
||||||
|
}
|
||||||
|
|
||||||
|
IRow headerDataRow = sheet.CreateRow(currentRowIdx++);
|
||||||
|
headerDataRow.CreateCell(0).SetCellValue("Día");
|
||||||
|
headerDataRow.CreateCell(1).SetCellValue("Hora");
|
||||||
|
headerDataRow.CreateCell(2).SetCellValue("Título de la Obra");
|
||||||
|
headerDataRow.CreateCell(3).SetCellValue("Compositor-Autor");
|
||||||
|
headerDataRow.CreateCell(4).SetCellValue("Intérprete");
|
||||||
|
headerDataRow.CreateCell(5).SetCellValue("Sello");
|
||||||
|
headerDataRow.CreateCell(6).SetCellValue("Álbum");
|
||||||
|
|
||||||
|
IFont font = workbook.CreateFont();
|
||||||
|
font.FontHeightInPoints = 10;
|
||||||
|
font.FontName = "Arial";
|
||||||
|
ICellStyle cellStyle = workbook.CreateCellStyle();
|
||||||
|
cellStyle.SetFont(font);
|
||||||
|
|
||||||
|
foreach (var item in programacion)
|
||||||
|
{
|
||||||
|
IRow dataRow = sheet.CreateRow(currentRowIdx++);
|
||||||
|
dataRow.CreateCell(0).SetCellValue(item.Dia);
|
||||||
|
dataRow.CreateCell(1).SetCellValue(item.Hora);
|
||||||
|
dataRow.CreateCell(2).SetCellValue(item.TituloObra);
|
||||||
|
dataRow.CreateCell(3).SetCellValue(item.CompositorAutor);
|
||||||
|
dataRow.CreateCell(4).SetCellValue(item.Interprete);
|
||||||
|
dataRow.CreateCell(5).SetCellValue(item.Sello);
|
||||||
|
dataRow.CreateCell(6).SetCellValue(item.Album);
|
||||||
|
|
||||||
|
for (int i = 0; i < 7; i++)
|
||||||
|
{
|
||||||
|
ICell cell = dataRow.GetCell(i) ?? dataRow.CreateCell(i); // Asegurarse que la celda exista
|
||||||
|
cell.CellStyle = cellStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet.SetColumnWidth(0, 4 * 256);
|
||||||
|
sheet.SetColumnWidth(1, 5 * 256);
|
||||||
|
sheet.SetColumnWidth(2, 25 * 256);
|
||||||
|
sheet.SetColumnWidth(3, 14 * 256);
|
||||||
|
sheet.SetColumnWidth(4, 11 * 256);
|
||||||
|
sheet.SetColumnWidth(5, 11 * 256);
|
||||||
|
sheet.SetColumnWidth(6, 30 * 256);
|
||||||
|
|
||||||
|
short rowHeight = 255; // 12.75 puntos
|
||||||
|
for (int i = 0; i < currentRowIdx; i++)
|
||||||
|
{
|
||||||
|
IRow row = sheet.GetRow(i) ?? sheet.CreateRow(i);
|
||||||
|
row.Height = rowHeight;
|
||||||
|
|
||||||
|
// Aplicar estilo a todas las celdas de las filas de encabezado también
|
||||||
|
if (i < (institucion == "AADI" ? 5 : 0) || i == (institucion == "AADI" ? 5 : 0)) // Filas de cabecera de AADI o la fila de títulos de columnas
|
||||||
|
{
|
||||||
|
// Iterar sobre las celdas que realmente existen o deberían existir
|
||||||
|
int lastCellNum = (institucion == "AADI" && i < 5) ? 1 : 7; // Cabecera AADI solo tiene 1 celda, títulos de datos tienen 7
|
||||||
|
for (int j = 0; j < lastCellNum; j++)
|
||||||
|
{
|
||||||
|
ICell cell = row.GetCell(j) ?? row.CreateCell(j);
|
||||||
|
cell.CellStyle = cellStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
workbook.Write(memoryStream, true); // El 'true' es para dejar el stream abierto si se necesita
|
||||||
|
return memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
Backend/GestionIntegral.Api/Services/Radios/RitmoService.cs
Normal file
133
Backend/GestionIntegral.Api/Services/Radios/RitmoService.cs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
using GestionIntegral.Api.Data.Repositories.Radios;
|
||||||
|
using GestionIntegral.Api.Dtos.Radios;
|
||||||
|
using GestionIntegral.Api.Models.Radios;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
// using GestionIntegral.Api.Data; // Para DbConnectionFactory si se usa transacción
|
||||||
|
// using System.Data; // Para IsolationLevel si se usa transacción
|
||||||
|
|
||||||
|
namespace GestionIntegral.Api.Services.Radios
|
||||||
|
{
|
||||||
|
public class RitmoService : IRitmoService
|
||||||
|
{
|
||||||
|
private readonly IRitmoRepository _ritmoRepository;
|
||||||
|
private readonly ILogger<RitmoService> _logger;
|
||||||
|
// private readonly DbConnectionFactory _connectionFactory; // Si se implementa historial
|
||||||
|
|
||||||
|
public RitmoService(IRitmoRepository ritmoRepository, ILogger<RitmoService> logger /*, DbConnectionFactory cf */)
|
||||||
|
{
|
||||||
|
_ritmoRepository = ritmoRepository;
|
||||||
|
_logger = logger;
|
||||||
|
// _connectionFactory = cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RitmoDto MapToDto(Ritmo ritmo) => new RitmoDto
|
||||||
|
{
|
||||||
|
Id = ritmo.Id,
|
||||||
|
NombreRitmo = ritmo.NombreRitmo
|
||||||
|
};
|
||||||
|
|
||||||
|
public async Task<IEnumerable<RitmoDto>> ObtenerTodosAsync(string? nombreFilter)
|
||||||
|
{
|
||||||
|
var ritmos = await _ritmoRepository.GetAllAsync(nombreFilter);
|
||||||
|
return ritmos.Select(MapToDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RitmoDto?> ObtenerPorIdAsync(int id)
|
||||||
|
{
|
||||||
|
var ritmo = await _ritmoRepository.GetByIdAsync(id);
|
||||||
|
return ritmo == null ? null : MapToDto(ritmo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(RitmoDto? Ritmo, string? Error)> CrearAsync(CreateRitmoDto createDto, int idUsuario)
|
||||||
|
{
|
||||||
|
if (await _ritmoRepository.ExistsByNameAsync(createDto.NombreRitmo))
|
||||||
|
{
|
||||||
|
return (null, "El nombre del ritmo ya existe.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var nuevoRitmo = new Ritmo { NombreRitmo = createDto.NombreRitmo };
|
||||||
|
|
||||||
|
// Sin historial, la transacción es opcional para una sola operación,
|
||||||
|
// pero se deja la estructura por si se añade más lógica o historial.
|
||||||
|
// 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 ritmoCreado = await _ritmoRepository.CreateAsync(nuevoRitmo /*, idUsuario, transaction */);
|
||||||
|
if (ritmoCreado == null) return (null, "Error al crear el ritmo.");
|
||||||
|
|
||||||
|
// transaction.Commit();
|
||||||
|
_logger.LogInformation("Ritmo ID {Id} creado por Usuario ID {UserId}.", ritmoCreado.Id, idUsuario);
|
||||||
|
return (MapToDto(ritmoCreado), null);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
// try { transaction.Rollback(); } catch {}
|
||||||
|
_logger.LogError(ex, "Error CrearAsync Ritmo: {NombreRitmo}", createDto.NombreRitmo);
|
||||||
|
return (null, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateRitmoDto updateDto, int idUsuario)
|
||||||
|
{
|
||||||
|
var ritmoExistente = await _ritmoRepository.GetByIdAsync(id);
|
||||||
|
if (ritmoExistente == null) return (false, "Ritmo no encontrado.");
|
||||||
|
|
||||||
|
if (ritmoExistente.NombreRitmo != updateDto.NombreRitmo && await _ritmoRepository.ExistsByNameAsync(updateDto.NombreRitmo, id))
|
||||||
|
{
|
||||||
|
return (false, "El nombre del ritmo ya existe para otro registro.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ritmoExistente.NombreRitmo = updateDto.NombreRitmo;
|
||||||
|
|
||||||
|
// Sin historial, la transacción es opcional...
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var actualizado = await _ritmoRepository.UpdateAsync(ritmoExistente /*, idUsuario, transaction */);
|
||||||
|
if (!actualizado) return (false, "Error al actualizar el ritmo.");
|
||||||
|
|
||||||
|
// transaction.Commit();
|
||||||
|
_logger.LogInformation("Ritmo ID {Id} actualizado por Usuario ID {UserId}.", id, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
// try { transaction.Rollback(); } catch {}
|
||||||
|
_logger.LogError(ex, "Error ActualizarAsync Ritmo ID: {Id}", id);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario)
|
||||||
|
{
|
||||||
|
var ritmoExistente = await _ritmoRepository.GetByIdAsync(id);
|
||||||
|
if (ritmoExistente == null) return (false, "Ritmo no encontrado.");
|
||||||
|
|
||||||
|
if (await _ritmoRepository.IsInUseAsync(id))
|
||||||
|
{
|
||||||
|
return (false, "No se puede eliminar. El ritmo está asignado a una o más canciones.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sin historial, la transacción es opcional...
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var eliminado = await _ritmoRepository.DeleteAsync(id /*, idUsuario, transaction */);
|
||||||
|
if (!eliminado) return (false, "Error al eliminar el ritmo.");
|
||||||
|
|
||||||
|
// transaction.Commit();
|
||||||
|
_logger.LogInformation("Ritmo ID {Id} eliminado por Usuario ID {UserId}.", id, idUsuario);
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
// try { transaction.Rollback(); } catch {}
|
||||||
|
_logger.LogError(ex, "Error EliminarAsync Ritmo ID: {Id}", id);
|
||||||
|
return (false, $"Error interno: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using GestionIntegral.Api.Dtos.Usuarios;
|
using GestionIntegral.Api.Dtos.Usuarios;
|
||||||
|
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ namespace GestionIntegral.Api.Services.Usuarios
|
|||||||
Task<(bool Exito, string? Error)> SetPasswordAsync(int userId, SetPasswordRequestDto setPasswordDto, int idUsuarioModificador);
|
Task<(bool Exito, string? Error)> SetPasswordAsync(int userId, SetPasswordRequestDto setPasswordDto, int idUsuarioModificador);
|
||||||
// Habilitar/Deshabilitar podría ser un método separado o parte de UpdateAsync
|
// Habilitar/Deshabilitar podría ser un método separado o parte de UpdateAsync
|
||||||
Task<(bool Exito, string? Error)> CambiarEstadoHabilitadoAsync(int userId, bool habilitar, int idUsuarioModificador);
|
Task<(bool Exito, string? Error)> CambiarEstadoHabilitadoAsync(int userId, bool habilitar, int idUsuarioModificador);
|
||||||
|
Task<IEnumerable<UsuarioHistorialDto>> ObtenerHistorialPorUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta);
|
||||||
|
Task<IEnumerable<UsuarioHistorialDto>> ObtenerTodoElHistorialAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModificoFilter, string? tipoModFilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using GestionIntegral.Api.Data;
|
using GestionIntegral.Api.Data;
|
||||||
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
using GestionIntegral.Api.Data.Repositories.Usuarios;
|
||||||
using GestionIntegral.Api.Dtos.Usuarios;
|
using GestionIntegral.Api.Dtos.Usuarios;
|
||||||
|
using GestionIntegral.Api.Dtos.Usuarios.Auditoria;
|
||||||
using GestionIntegral.Api.Models.Usuarios; // Para Usuario
|
using GestionIntegral.Api.Models.Usuarios; // Para Usuario
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -79,7 +80,7 @@ namespace GestionIntegral.Api.Services.Usuarios
|
|||||||
{
|
{
|
||||||
return (null, "El perfil seleccionado no es válido.");
|
return (null, "El perfil seleccionado no es válido.");
|
||||||
}
|
}
|
||||||
if(createDto.User.Equals(createDto.Password, System.StringComparison.OrdinalIgnoreCase))
|
if (createDto.User.Equals(createDto.Password, System.StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return (null, "La contraseña no puede ser igual al nombre de usuario.");
|
return (null, "La contraseña no puede ser igual al nombre de usuario.");
|
||||||
}
|
}
|
||||||
@@ -112,11 +113,18 @@ namespace GestionIntegral.Api.Services.Usuarios
|
|||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
|
|
||||||
// Construir el DTO para la respuesta
|
// Construir el DTO para la respuesta
|
||||||
var dto = new UsuarioDto {
|
var dto = new UsuarioDto
|
||||||
Id = usuarioCreado.Id, User = usuarioCreado.User, Habilitada = usuarioCreado.Habilitada, SupAdmin = usuarioCreado.SupAdmin,
|
{
|
||||||
Nombre = usuarioCreado.Nombre, Apellido = usuarioCreado.Apellido, IdPerfil = usuarioCreado.IdPerfil,
|
Id = usuarioCreado.Id,
|
||||||
|
User = usuarioCreado.User,
|
||||||
|
Habilitada = usuarioCreado.Habilitada,
|
||||||
|
SupAdmin = usuarioCreado.SupAdmin,
|
||||||
|
Nombre = usuarioCreado.Nombre,
|
||||||
|
Apellido = usuarioCreado.Apellido,
|
||||||
|
IdPerfil = usuarioCreado.IdPerfil,
|
||||||
NombrePerfil = perfilSeleccionado.NombrePerfil, // Usamos el nombre del perfil ya obtenido
|
NombrePerfil = perfilSeleccionado.NombrePerfil, // Usamos el nombre del perfil ya obtenido
|
||||||
DebeCambiarClave = usuarioCreado.DebeCambiarClave, VerLog = usuarioCreado.VerLog
|
DebeCambiarClave = usuarioCreado.DebeCambiarClave,
|
||||||
|
VerLog = usuarioCreado.VerLog
|
||||||
};
|
};
|
||||||
_logger.LogInformation("Usuario ID {UsuarioId} creado por Usuario ID {CreadorId}.", usuarioCreado.Id, idUsuarioCreador);
|
_logger.LogInformation("Usuario ID {UsuarioId} creado por Usuario ID {CreadorId}.", usuarioCreado.Id, idUsuarioCreador);
|
||||||
return (dto, null);
|
return (dto, null);
|
||||||
@@ -160,8 +168,9 @@ namespace GestionIntegral.Api.Services.Usuarios
|
|||||||
_logger.LogInformation("Usuario ID {UsuarioId} actualizado por Usuario ID {ModificadorId}.", id, idUsuarioModificador);
|
_logger.LogInformation("Usuario ID {UsuarioId} actualizado por Usuario ID {ModificadorId}.", id, idUsuarioModificador);
|
||||||
return (true, null);
|
return (true, null);
|
||||||
}
|
}
|
||||||
catch (KeyNotFoundException) {
|
catch (KeyNotFoundException)
|
||||||
try { transaction.Rollback(); } catch { /* Log */ }
|
{
|
||||||
|
try { transaction.Rollback(); } catch { /* Log */ }
|
||||||
return (false, "Usuario no encontrado durante la actualización.");
|
return (false, "Usuario no encontrado durante la actualización.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -176,9 +185,9 @@ namespace GestionIntegral.Api.Services.Usuarios
|
|||||||
var usuario = await _usuarioRepository.GetByIdAsync(userId);
|
var usuario = await _usuarioRepository.GetByIdAsync(userId);
|
||||||
if (usuario == null) return (false, "Usuario no encontrado.");
|
if (usuario == null) return (false, "Usuario no encontrado.");
|
||||||
|
|
||||||
if(usuario.User.Equals(setPasswordDto.NewPassword, System.StringComparison.OrdinalIgnoreCase))
|
if (usuario.User.Equals(setPasswordDto.NewPassword, System.StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return (false, "La nueva contraseña no puede ser igual al nombre de usuario.");
|
return (false, "La nueva contraseña no puede ser igual al nombre de usuario.");
|
||||||
}
|
}
|
||||||
|
|
||||||
(string hash, string salt) = _passwordHasher.HashPassword(setPasswordDto.NewPassword);
|
(string hash, string salt) = _passwordHasher.HashPassword(setPasswordDto.NewPassword);
|
||||||
@@ -189,14 +198,15 @@ namespace GestionIntegral.Api.Services.Usuarios
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var success = await _usuarioRepository.SetPasswordAsync(userId, hash, salt, setPasswordDto.ForceChangeOnNextLogin, idUsuarioModificador, transaction);
|
var success = await _usuarioRepository.SetPasswordAsync(userId, hash, salt, setPasswordDto.ForceChangeOnNextLogin, idUsuarioModificador, transaction);
|
||||||
if(!success) throw new DataException("Error al actualizar la contraseña en el repositorio.");
|
if (!success) throw new DataException("Error al actualizar la contraseña en el repositorio.");
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
_logger.LogInformation("Contraseña establecida para Usuario ID {TargetUserId} por Usuario ID {AdminUserId}.", userId, idUsuarioModificador);
|
_logger.LogInformation("Contraseña establecida para Usuario ID {TargetUserId} por Usuario ID {AdminUserId}.", userId, idUsuarioModificador);
|
||||||
return (true, null);
|
return (true, null);
|
||||||
}
|
}
|
||||||
catch (KeyNotFoundException) {
|
catch (KeyNotFoundException)
|
||||||
try { transaction.Rollback(); } catch { /* Log */ }
|
{
|
||||||
|
try { transaction.Rollback(); } catch { /* Log */ }
|
||||||
return (false, "Usuario no encontrado durante el cambio de contraseña.");
|
return (false, "Usuario no encontrado durante el cambio de contraseña.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -207,7 +217,7 @@ namespace GestionIntegral.Api.Services.Usuarios
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(bool Exito, string? Error)> CambiarEstadoHabilitadoAsync(int userId, bool habilitar, int idUsuarioModificador)
|
public async Task<(bool Exito, string? Error)> CambiarEstadoHabilitadoAsync(int userId, bool habilitar, int idUsuarioModificador)
|
||||||
{
|
{
|
||||||
var usuario = await _usuarioRepository.GetByIdAsync(userId);
|
var usuario = await _usuarioRepository.GetByIdAsync(userId);
|
||||||
if (usuario == null) return (false, "Usuario no encontrado.");
|
if (usuario == null) return (false, "Usuario no encontrado.");
|
||||||
@@ -225,22 +235,34 @@ namespace GestionIntegral.Api.Services.Usuarios
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var actualizado = await _usuarioRepository.UpdateAsync(usuario, idUsuarioModificador, transaction);
|
var actualizado = await _usuarioRepository.UpdateAsync(usuario, idUsuarioModificador, transaction);
|
||||||
if (!actualizado) throw new DataException("Error al cambiar estado de habilitación del usuario en el repositorio.");
|
if (!actualizado) throw new DataException("Error al cambiar estado de habilitación del usuario en el repositorio.");
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
_logger.LogInformation("Estado de habilitación cambiado a {Estado} para Usuario ID {TargetUserId} por Usuario ID {AdminUserId}.", habilitar, userId, idUsuarioModificador);
|
_logger.LogInformation("Estado de habilitación cambiado a {Estado} para Usuario ID {TargetUserId} por Usuario ID {AdminUserId}.", habilitar, userId, idUsuarioModificador);
|
||||||
return (true, null);
|
return (true, null);
|
||||||
}
|
}
|
||||||
catch (KeyNotFoundException) {
|
catch (KeyNotFoundException)
|
||||||
try { transaction.Rollback(); } catch { /* Log */ }
|
{
|
||||||
|
try { transaction.Rollback(); } catch { /* Log */ }
|
||||||
return (false, "Usuario no encontrado durante el cambio de estado.");
|
return (false, "Usuario no encontrado durante el cambio de estado.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
try { transaction.Rollback(); } catch { /* Log */ }
|
try { transaction.Rollback(); } catch { /* Log */ }
|
||||||
_logger.LogError(ex, "Error al cambiar estado de habilitación para Usuario ID {TargetUserId}.", userId);
|
_logger.LogError(ex, "Error al cambiar estado de habilitación para Usuario ID {TargetUserId}.", userId);
|
||||||
return (false, $"Error interno al cambiar estado de habilitación: {ex.Message}");
|
return (false, $"Error interno al cambiar estado de habilitación: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public async Task<IEnumerable<UsuarioHistorialDto>> ObtenerHistorialPorUsuarioIdAsync(int idUsuarioAfectado, DateTime? fechaDesde, DateTime? fechaHasta)
|
||||||
|
{
|
||||||
|
// Aquí podrías añadir validaciones extra si fuera necesario antes de llamar al repo
|
||||||
|
return await _usuarioRepository.GetHistorialByUsuarioIdAsync(idUsuarioAfectado, fechaDesde, fechaHasta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<UsuarioHistorialDto>> ObtenerTodoElHistorialAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModificoFilter, string? tipoModFilter)
|
||||||
|
{
|
||||||
|
// Aquí podrías añadir validaciones extra
|
||||||
|
return await _usuarioRepository.GetAllHistorialAsync(fechaDesde, fechaHasta, idUsuarioModificoFilter, tipoModFilter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"Microsoft.AspNetCore.Authentication.JwtBearer": "9.0.4",
|
"Microsoft.AspNetCore.Authentication.JwtBearer": "9.0.4",
|
||||||
"Microsoft.AspNetCore.OpenApi": "9.0.3",
|
"Microsoft.AspNetCore.OpenApi": "9.0.3",
|
||||||
"Microsoft.Data.SqlClient": "6.0.2",
|
"Microsoft.Data.SqlClient": "6.0.2",
|
||||||
|
"NPOI": "2.7.3",
|
||||||
"Swashbuckle.AspNetCore": "8.1.1",
|
"Swashbuckle.AspNetCore": "8.1.1",
|
||||||
"System.IdentityModel.Tokens.Jwt": "8.9.0"
|
"System.IdentityModel.Tokens.Jwt": "8.9.0"
|
||||||
},
|
},
|
||||||
@@ -54,6 +55,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"BouncyCastle.Cryptography/2.3.1": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/BouncyCastle.Cryptography.dll": {
|
||||||
|
"assemblyVersion": "2.0.0.0",
|
||||||
|
"fileVersion": "2.3.1.17862"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Dapper/2.1.66": {
|
"Dapper/2.1.66": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/net8.0/Dapper.dll": {
|
"lib/net8.0/Dapper.dll": {
|
||||||
@@ -62,6 +71,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Enums.NET/4.0.1": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/netcoreapp3.0/Enums.NET.dll": {
|
||||||
|
"assemblyVersion": "4.0.0.0",
|
||||||
|
"fileVersion": "4.0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ExtendedNumerics.BigDecimal/2025.1001.2.129": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/ExtendedNumerics.BigDecimal.dll": {
|
||||||
|
"assemblyVersion": "2025.1001.2.129",
|
||||||
|
"fileVersion": "2025.1001.2.129"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MathNet.Numerics.Signed/5.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/MathNet.Numerics.dll": {
|
||||||
|
"assemblyVersion": "5.0.0.0",
|
||||||
|
"fileVersion": "5.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
|
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.0.1"
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.0.1"
|
||||||
@@ -348,6 +381,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.IO.RecyclableMemoryStream/3.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/Microsoft.IO.RecyclableMemoryStream.dll": {
|
||||||
|
"assemblyVersion": "3.0.0.0",
|
||||||
|
"fileVersion": "3.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.NETCore.Platforms/5.0.0": {},
|
||||||
"Microsoft.OpenApi/1.6.23": {
|
"Microsoft.OpenApi/1.6.23": {
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
|
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
|
||||||
@@ -364,6 +406,66 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"NPOI/2.7.3": {
|
||||||
|
"dependencies": {
|
||||||
|
"BouncyCastle.Cryptography": "2.3.1",
|
||||||
|
"Enums.NET": "4.0.1",
|
||||||
|
"ExtendedNumerics.BigDecimal": "2025.1001.2.129",
|
||||||
|
"MathNet.Numerics.Signed": "5.0.0",
|
||||||
|
"Microsoft.IO.RecyclableMemoryStream": "3.0.0",
|
||||||
|
"SharpZipLib": "1.4.2",
|
||||||
|
"SixLabors.Fonts": "1.0.1",
|
||||||
|
"SixLabors.ImageSharp": "2.1.10",
|
||||||
|
"System.Security.Cryptography.Pkcs": "9.0.4",
|
||||||
|
"System.Security.Cryptography.Xml": "8.0.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/NPOI.Core.dll": {
|
||||||
|
"assemblyVersion": "2.7.3.0",
|
||||||
|
"fileVersion": "2.7.3.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/NPOI.OOXML.dll": {
|
||||||
|
"assemblyVersion": "2.7.3.0",
|
||||||
|
"fileVersion": "2.7.3.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/NPOI.OpenXml4Net.dll": {
|
||||||
|
"assemblyVersion": "2.7.3.0",
|
||||||
|
"fileVersion": "2.7.3.0"
|
||||||
|
},
|
||||||
|
"lib/net8.0/NPOI.OpenXmlFormats.dll": {
|
||||||
|
"assemblyVersion": "2.7.3.0",
|
||||||
|
"fileVersion": "2.7.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SharpZipLib/1.4.2": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/ICSharpCode.SharpZipLib.dll": {
|
||||||
|
"assemblyVersion": "1.4.2.13",
|
||||||
|
"fileVersion": "1.4.2.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SixLabors.Fonts/1.0.1": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/netcoreapp3.1/SixLabors.Fonts.dll": {
|
||||||
|
"assemblyVersion": "1.0.0.0",
|
||||||
|
"fileVersion": "1.0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SixLabors.ImageSharp/2.1.10": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
|
||||||
|
"System.Text.Encoding.CodePages": "5.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/netcoreapp3.1/SixLabors.ImageSharp.dll": {
|
||||||
|
"assemblyVersion": "2.0.0.0",
|
||||||
|
"fileVersion": "2.1.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Swashbuckle.AspNetCore/8.1.1": {
|
"Swashbuckle.AspNetCore/8.1.1": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Extensions.ApiDescription.Server": "6.0.5",
|
"Microsoft.Extensions.ApiDescription.Server": "6.0.5",
|
||||||
@@ -498,6 +600,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"System.Security.Cryptography.Xml/8.0.2": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.Security.Cryptography.Pkcs": "9.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Text.Encoding.CodePages/5.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.NETCore.Platforms": "5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"System.Text.Encodings.Web/4.7.2": {},
|
"System.Text.Encodings.Web/4.7.2": {},
|
||||||
"System.Text.Json/4.7.2": {},
|
"System.Text.Json/4.7.2": {},
|
||||||
"System.Threading.Tasks.Extensions/4.5.4": {}
|
"System.Threading.Tasks.Extensions/4.5.4": {}
|
||||||
@@ -523,6 +635,13 @@
|
|||||||
"path": "azure.identity/1.11.4",
|
"path": "azure.identity/1.11.4",
|
||||||
"hashPath": "azure.identity.1.11.4.nupkg.sha512"
|
"hashPath": "azure.identity.1.11.4.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"BouncyCastle.Cryptography/2.3.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-buwoISwecYke3CmgG1AQSg+sNZjJeIb93vTAtJiHZX35hP/teYMxsfg0NDXGUKjGx6BKBTNKc77O2M3vKvlXZQ==",
|
||||||
|
"path": "bouncycastle.cryptography/2.3.1",
|
||||||
|
"hashPath": "bouncycastle.cryptography.2.3.1.nupkg.sha512"
|
||||||
|
},
|
||||||
"Dapper/2.1.66": {
|
"Dapper/2.1.66": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
@@ -530,6 +649,27 @@
|
|||||||
"path": "dapper/2.1.66",
|
"path": "dapper/2.1.66",
|
||||||
"hashPath": "dapper.2.1.66.nupkg.sha512"
|
"hashPath": "dapper.2.1.66.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"Enums.NET/4.0.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-OUGCd5L8zHZ61GAf436G0gf/H6yrSUkEpV5vm2CbCUuz9Rx7iLFLP5iHSSfmOtqNpuyo4vYte0CvYEmPZXRmRQ==",
|
||||||
|
"path": "enums.net/4.0.1",
|
||||||
|
"hashPath": "enums.net.4.0.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"ExtendedNumerics.BigDecimal/2025.1001.2.129": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-+woGT1lsBtwkntOpx2EZbdbySv0aWPefE0vrfvclxVdbi4oa2bbtphFPWgMiQe+kRCPICbfFJwp6w1DuR7Ge2Q==",
|
||||||
|
"path": "extendednumerics.bigdecimal/2025.1001.2.129",
|
||||||
|
"hashPath": "extendednumerics.bigdecimal.2025.1001.2.129.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"MathNet.Numerics.Signed/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-PSrHBVMf41SjbhlnpOMnoir8YgkyEJ6/nwxvjYpH+vJCexNcx2ms6zRww5yLVqLet1xLJgZ39swtKRTLhWdnAw==",
|
||||||
|
"path": "mathnet.numerics.signed/5.0.0",
|
||||||
|
"hashPath": "mathnet.numerics.signed.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
|
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
@@ -677,6 +817,20 @@
|
|||||||
"path": "microsoft.identitymodel.tokens/8.9.0",
|
"path": "microsoft.identitymodel.tokens/8.9.0",
|
||||||
"hashPath": "microsoft.identitymodel.tokens.8.9.0.nupkg.sha512"
|
"hashPath": "microsoft.identitymodel.tokens.8.9.0.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"Microsoft.IO.RecyclableMemoryStream/3.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==",
|
||||||
|
"path": "microsoft.io.recyclablememorystream/3.0.0",
|
||||||
|
"hashPath": "microsoft.io.recyclablememorystream.3.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.NETCore.Platforms/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==",
|
||||||
|
"path": "microsoft.netcore.platforms/5.0.0",
|
||||||
|
"hashPath": "microsoft.netcore.platforms.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
"Microsoft.OpenApi/1.6.23": {
|
"Microsoft.OpenApi/1.6.23": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
@@ -691,6 +845,34 @@
|
|||||||
"path": "microsoft.sqlserver.server/1.0.0",
|
"path": "microsoft.sqlserver.server/1.0.0",
|
||||||
"hashPath": "microsoft.sqlserver.server.1.0.0.nupkg.sha512"
|
"hashPath": "microsoft.sqlserver.server.1.0.0.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"NPOI/2.7.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-iCZx3DSwUSwaV61E8tXgPlPuxYmcYV/Zi405nGlxQvWaGTAbuc0KvSBjsLucQUJ92iMeetT8iK9makLfF4uZ3g==",
|
||||||
|
"path": "npoi/2.7.3",
|
||||||
|
"hashPath": "npoi.2.7.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"SharpZipLib/1.4.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A==",
|
||||||
|
"path": "sharpziplib/1.4.2",
|
||||||
|
"hashPath": "sharpziplib.1.4.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"SixLabors.Fonts/1.0.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-ljezRHWc7N0azdQViib7Aa5v+DagRVkKI2/93kEbtjVczLs+yTkSq6gtGmvOcx4IqyNbO3GjLt7SAQTpLkySNw==",
|
||||||
|
"path": "sixlabors.fonts/1.0.1",
|
||||||
|
"hashPath": "sixlabors.fonts.1.0.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"SixLabors.ImageSharp/2.1.10": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-hk1E7U3RSlxrBVo6Gb6OjeM52fChpFYH+SZvyT/M2vzSGlzAaKE33hc3V/Pvnjcnn1opT8/Z+0QfqdM5HsIaeA==",
|
||||||
|
"path": "sixlabors.imagesharp/2.1.10",
|
||||||
|
"hashPath": "sixlabors.imagesharp.2.1.10.nupkg.sha512"
|
||||||
|
},
|
||||||
"Swashbuckle.AspNetCore/8.1.1": {
|
"Swashbuckle.AspNetCore/8.1.1": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
@@ -796,6 +978,20 @@
|
|||||||
"path": "system.security.cryptography.protecteddata/9.0.4",
|
"path": "system.security.cryptography.protecteddata/9.0.4",
|
||||||
"hashPath": "system.security.cryptography.protecteddata.9.0.4.nupkg.sha512"
|
"hashPath": "system.security.cryptography.protecteddata.9.0.4.nupkg.sha512"
|
||||||
},
|
},
|
||||||
|
"System.Security.Cryptography.Xml/8.0.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-aDM/wm0ZGEZ6ZYJLzgqjp2FZdHbDHh6/OmpGfb7AdZ105zYmPn/83JRU2xLIbwgoNz9U1SLUTJN0v5th3qmvjA==",
|
||||||
|
"path": "system.security.cryptography.xml/8.0.2",
|
||||||
|
"hashPath": "system.security.cryptography.xml.8.0.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Text.Encoding.CodePages/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
|
||||||
|
"path": "system.text.encoding.codepages/5.0.0",
|
||||||
|
"hashPath": "system.text.encoding.codepages.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
"System.Text.Encodings.Web/4.7.2": {
|
"System.Text.Encodings.Web/4.7.2": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"serviceable": true,
|
"serviceable": true,
|
||||||
|
|||||||
@@ -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+b6ba52f074807c7a2fddc76ab3cc2c45c446c1f8")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+e7e185a9cb2950cb77c96951d4c04ca768955d27")]
|
||||||
[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")]
|
||||||
|
|||||||
@@ -78,3 +78,15 @@ E:\GestionIntegralWeb\backend\gestionintegral.api\obj\Debug\net9.0\GestionIntegr
|
|||||||
E:\GestionIntegralWeb\backend\gestionintegral.api\obj\Debug\net9.0\GestionIntegral.Api.genruntimeconfig.cache
|
E:\GestionIntegralWeb\backend\gestionintegral.api\obj\Debug\net9.0\GestionIntegral.Api.genruntimeconfig.cache
|
||||||
E:\GestionIntegralWeb\backend\gestionintegral.api\obj\Debug\net9.0\ref\GestionIntegral.Api.dll
|
E:\GestionIntegralWeb\backend\gestionintegral.api\obj\Debug\net9.0\ref\GestionIntegral.Api.dll
|
||||||
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\obj\Debug\net9.0\staticwebassets.build.json.cache
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\obj\Debug\net9.0\staticwebassets.build.json.cache
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\BouncyCastle.Cryptography.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\Enums.NET.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\ExtendedNumerics.BigDecimal.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\MathNet.Numerics.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\Microsoft.IO.RecyclableMemoryStream.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\NPOI.Core.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\NPOI.OOXML.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\NPOI.OpenXml4Net.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\NPOI.OpenXmlFormats.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\ICSharpCode.SharpZipLib.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\SixLabors.Fonts.dll
|
||||||
|
E:\GestionIntegralWeb\Backend\GestionIntegral.Api\bin\Debug\net9.0\SixLabors.ImageSharp.dll
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","kULolnJcJq9du0a0dBwZaPVupTEFX15sai6mOONU2qk="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","898tiJH7z5LyOA6iz/6l2n6u\u002Bf/3Afm3R6QKhRmMlf0="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","kULolnJcJq9du0a0dBwZaPVupTEFX15sai6mOONU2qk="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["lgiSIq1Xdt6PC6CpA82eiZlqBZS3M8jckHELlrL00LI=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","898tiJH7z5LyOA6iz/6l2n6u\u002Bf/3Afm3R6QKhRmMlf0="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -66,6 +66,10 @@
|
|||||||
"target": "Package",
|
"target": "Package",
|
||||||
"version": "[6.0.2, )"
|
"version": "[6.0.2, )"
|
||||||
},
|
},
|
||||||
|
"NPOI": {
|
||||||
|
"target": "Package",
|
||||||
|
"version": "[2.7.3, )"
|
||||||
|
},
|
||||||
"Swashbuckle.AspNetCore": {
|
"Swashbuckle.AspNetCore": {
|
||||||
"target": "Package",
|
"target": "Package",
|
||||||
"version": "[8.1.1, )"
|
"version": "[8.1.1, )"
|
||||||
|
|||||||
@@ -47,6 +47,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"BouncyCastle.Cryptography/2.3.1": {
|
||||||
|
"type": "package",
|
||||||
|
"compile": {
|
||||||
|
"lib/net6.0/BouncyCastle.Cryptography.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/BouncyCastle.Cryptography.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Dapper/2.1.66": {
|
"Dapper/2.1.66": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"compile": {
|
"compile": {
|
||||||
@@ -60,6 +73,45 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Enums.NET/4.0.1": {
|
||||||
|
"type": "package",
|
||||||
|
"compile": {
|
||||||
|
"lib/netcoreapp3.0/Enums.NET.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/netcoreapp3.0/Enums.NET.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ExtendedNumerics.BigDecimal/2025.1001.2.129": {
|
||||||
|
"type": "package",
|
||||||
|
"compile": {
|
||||||
|
"lib/net8.0/ExtendedNumerics.BigDecimal.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/ExtendedNumerics.BigDecimal.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MathNet.Numerics.Signed/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"compile": {
|
||||||
|
"lib/net6.0/MathNet.Numerics.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/MathNet.Numerics.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
|
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -470,6 +522,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.IO.RecyclableMemoryStream/3.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"compile": {
|
||||||
|
"lib/net6.0/Microsoft.IO.RecyclableMemoryStream.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/Microsoft.IO.RecyclableMemoryStream.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.NETCore.Platforms/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"compile": {
|
||||||
|
"lib/netstandard1.0/_._": {}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard1.0/_._": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.OpenApi/1.6.23": {
|
"Microsoft.OpenApi/1.6.23": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"compile": {
|
"compile": {
|
||||||
@@ -496,6 +570,92 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"NPOI/2.7.3": {
|
||||||
|
"type": "package",
|
||||||
|
"dependencies": {
|
||||||
|
"BouncyCastle.Cryptography": "2.3.1",
|
||||||
|
"Enums.NET": "4.0.1",
|
||||||
|
"ExtendedNumerics.BigDecimal": "2025.1001.2.129",
|
||||||
|
"MathNet.Numerics.Signed": "5.0.0",
|
||||||
|
"Microsoft.IO.RecyclableMemoryStream": "3.0.0",
|
||||||
|
"SharpZipLib": "1.4.2",
|
||||||
|
"SixLabors.Fonts": "1.0.1",
|
||||||
|
"SixLabors.ImageSharp": "2.1.10",
|
||||||
|
"System.Security.Cryptography.Pkcs": "8.0.1",
|
||||||
|
"System.Security.Cryptography.Xml": "8.0.2"
|
||||||
|
},
|
||||||
|
"compile": {
|
||||||
|
"lib/net8.0/NPOI.Core.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
},
|
||||||
|
"lib/net8.0/NPOI.OOXML.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
},
|
||||||
|
"lib/net8.0/NPOI.OpenXml4Net.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
},
|
||||||
|
"lib/net8.0/NPOI.OpenXmlFormats.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/NPOI.Core.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
},
|
||||||
|
"lib/net8.0/NPOI.OOXML.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
},
|
||||||
|
"lib/net8.0/NPOI.OpenXml4Net.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
},
|
||||||
|
"lib/net8.0/NPOI.OpenXmlFormats.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SharpZipLib/1.4.2": {
|
||||||
|
"type": "package",
|
||||||
|
"compile": {
|
||||||
|
"lib/net6.0/ICSharpCode.SharpZipLib.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/ICSharpCode.SharpZipLib.dll": {
|
||||||
|
"related": ".pdb;.xml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SixLabors.Fonts/1.0.1": {
|
||||||
|
"type": "package",
|
||||||
|
"compile": {
|
||||||
|
"lib/netcoreapp3.1/SixLabors.Fonts.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/netcoreapp3.1/SixLabors.Fonts.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SixLabors.ImageSharp/2.1.10": {
|
||||||
|
"type": "package",
|
||||||
|
"dependencies": {
|
||||||
|
"System.Runtime.CompilerServices.Unsafe": "5.0.0",
|
||||||
|
"System.Text.Encoding.CodePages": "5.0.0"
|
||||||
|
},
|
||||||
|
"compile": {
|
||||||
|
"lib/netcoreapp3.1/SixLabors.ImageSharp.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/netcoreapp3.1/SixLabors.ImageSharp.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"Swashbuckle.AspNetCore/8.1.1": {
|
"Swashbuckle.AspNetCore/8.1.1": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -750,6 +910,47 @@
|
|||||||
"buildTransitive/net8.0/_._": {}
|
"buildTransitive/net8.0/_._": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"System.Security.Cryptography.Xml/8.0.2": {
|
||||||
|
"type": "package",
|
||||||
|
"dependencies": {
|
||||||
|
"System.Security.Cryptography.Pkcs": "8.0.1"
|
||||||
|
},
|
||||||
|
"compile": {
|
||||||
|
"lib/net8.0/System.Security.Cryptography.Xml.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/System.Security.Cryptography.Xml.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"buildTransitive/net6.0/_._": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Text.Encoding.CodePages/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.NETCore.Platforms": "5.0.0"
|
||||||
|
},
|
||||||
|
"compile": {
|
||||||
|
"lib/netstandard2.0/System.Text.Encoding.CodePages.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/netstandard2.0/System.Text.Encoding.CodePages.dll": {
|
||||||
|
"related": ".xml"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"runtimeTargets": {
|
||||||
|
"runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll": {
|
||||||
|
"assetType": "runtime",
|
||||||
|
"rid": "win"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"System.Text.Encodings.Web/4.7.2": {
|
"System.Text.Encodings.Web/4.7.2": {
|
||||||
"type": "package",
|
"type": "package",
|
||||||
"compile": {
|
"compile": {
|
||||||
@@ -826,6 +1027,26 @@
|
|||||||
"lib/netstandard2.0/Azure.Identity.xml"
|
"lib/netstandard2.0/Azure.Identity.xml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"BouncyCastle.Cryptography/2.3.1": {
|
||||||
|
"sha512": "buwoISwecYke3CmgG1AQSg+sNZjJeIb93vTAtJiHZX35hP/teYMxsfg0NDXGUKjGx6BKBTNKc77O2M3vKvlXZQ==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "bouncycastle.cryptography/2.3.1",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"LICENSE.md",
|
||||||
|
"README.md",
|
||||||
|
"bouncycastle.cryptography.2.3.1.nupkg.sha512",
|
||||||
|
"bouncycastle.cryptography.nuspec",
|
||||||
|
"lib/net461/BouncyCastle.Cryptography.dll",
|
||||||
|
"lib/net461/BouncyCastle.Cryptography.xml",
|
||||||
|
"lib/net6.0/BouncyCastle.Cryptography.dll",
|
||||||
|
"lib/net6.0/BouncyCastle.Cryptography.xml",
|
||||||
|
"lib/netstandard2.0/BouncyCastle.Cryptography.dll",
|
||||||
|
"lib/netstandard2.0/BouncyCastle.Cryptography.xml",
|
||||||
|
"packageIcon.png"
|
||||||
|
]
|
||||||
|
},
|
||||||
"Dapper/2.1.66": {
|
"Dapper/2.1.66": {
|
||||||
"sha512": "/q77jUgDOS+bzkmk3Vy9SiWMaetTw+NOoPAV0xPBsGVAyljd5S6P+4RUW7R3ZUGGr9lDRyPKgAMj2UAOwvqZYw==",
|
"sha512": "/q77jUgDOS+bzkmk3Vy9SiWMaetTw+NOoPAV0xPBsGVAyljd5S6P+4RUW7R3ZUGGr9lDRyPKgAMj2UAOwvqZYw==",
|
||||||
"type": "package",
|
"type": "package",
|
||||||
@@ -845,6 +1066,94 @@
|
|||||||
"readme.md"
|
"readme.md"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"Enums.NET/4.0.1": {
|
||||||
|
"sha512": "OUGCd5L8zHZ61GAf436G0gf/H6yrSUkEpV5vm2CbCUuz9Rx7iLFLP5iHSSfmOtqNpuyo4vYte0CvYEmPZXRmRQ==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "enums.net/4.0.1",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"enums.net.4.0.1.nupkg.sha512",
|
||||||
|
"enums.net.nuspec",
|
||||||
|
"lib/net45/Enums.NET.dll",
|
||||||
|
"lib/net45/Enums.NET.pdb",
|
||||||
|
"lib/net45/Enums.NET.xml",
|
||||||
|
"lib/netcoreapp3.0/Enums.NET.dll",
|
||||||
|
"lib/netcoreapp3.0/Enums.NET.pdb",
|
||||||
|
"lib/netcoreapp3.0/Enums.NET.xml",
|
||||||
|
"lib/netstandard1.0/Enums.NET.dll",
|
||||||
|
"lib/netstandard1.0/Enums.NET.pdb",
|
||||||
|
"lib/netstandard1.0/Enums.NET.xml",
|
||||||
|
"lib/netstandard1.1/Enums.NET.dll",
|
||||||
|
"lib/netstandard1.1/Enums.NET.pdb",
|
||||||
|
"lib/netstandard1.1/Enums.NET.xml",
|
||||||
|
"lib/netstandard1.3/Enums.NET.dll",
|
||||||
|
"lib/netstandard1.3/Enums.NET.pdb",
|
||||||
|
"lib/netstandard1.3/Enums.NET.xml",
|
||||||
|
"lib/netstandard2.0/Enums.NET.dll",
|
||||||
|
"lib/netstandard2.0/Enums.NET.pdb",
|
||||||
|
"lib/netstandard2.0/Enums.NET.xml",
|
||||||
|
"lib/netstandard2.1/Enums.NET.dll",
|
||||||
|
"lib/netstandard2.1/Enums.NET.pdb",
|
||||||
|
"lib/netstandard2.1/Enums.NET.xml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ExtendedNumerics.BigDecimal/2025.1001.2.129": {
|
||||||
|
"sha512": "+woGT1lsBtwkntOpx2EZbdbySv0aWPefE0vrfvclxVdbi4oa2bbtphFPWgMiQe+kRCPICbfFJwp6w1DuR7Ge2Q==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "extendednumerics.bigdecimal/2025.1001.2.129",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"README.md",
|
||||||
|
"extendednumerics.bigdecimal.2025.1001.2.129.nupkg.sha512",
|
||||||
|
"extendednumerics.bigdecimal.nuspec",
|
||||||
|
"lib/net45/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/net45/ExtendedNumerics.BigDecimal.xml",
|
||||||
|
"lib/net46/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/net46/ExtendedNumerics.BigDecimal.xml",
|
||||||
|
"lib/net472/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/net472/ExtendedNumerics.BigDecimal.xml",
|
||||||
|
"lib/net48/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/net48/ExtendedNumerics.BigDecimal.xml",
|
||||||
|
"lib/net5.0/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/net5.0/ExtendedNumerics.BigDecimal.xml",
|
||||||
|
"lib/net6.0/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/net6.0/ExtendedNumerics.BigDecimal.xml",
|
||||||
|
"lib/net7.0/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/net7.0/ExtendedNumerics.BigDecimal.xml",
|
||||||
|
"lib/net8.0/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/net8.0/ExtendedNumerics.BigDecimal.xml",
|
||||||
|
"lib/netcoreapp3.1/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/netcoreapp3.1/ExtendedNumerics.BigDecimal.xml",
|
||||||
|
"lib/netstandard2.0/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/netstandard2.0/ExtendedNumerics.BigDecimal.xml",
|
||||||
|
"lib/netstandard2.1/ExtendedNumerics.BigDecimal.dll",
|
||||||
|
"lib/netstandard2.1/ExtendedNumerics.BigDecimal.xml"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"MathNet.Numerics.Signed/5.0.0": {
|
||||||
|
"sha512": "PSrHBVMf41SjbhlnpOMnoir8YgkyEJ6/nwxvjYpH+vJCexNcx2ms6zRww5yLVqLet1xLJgZ39swtKRTLhWdnAw==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "mathnet.numerics.signed/5.0.0",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"icon.png",
|
||||||
|
"lib/net461/MathNet.Numerics.dll",
|
||||||
|
"lib/net461/MathNet.Numerics.xml",
|
||||||
|
"lib/net48/MathNet.Numerics.dll",
|
||||||
|
"lib/net48/MathNet.Numerics.xml",
|
||||||
|
"lib/net5.0/MathNet.Numerics.dll",
|
||||||
|
"lib/net5.0/MathNet.Numerics.xml",
|
||||||
|
"lib/net6.0/MathNet.Numerics.dll",
|
||||||
|
"lib/net6.0/MathNet.Numerics.xml",
|
||||||
|
"lib/netstandard2.0/MathNet.Numerics.dll",
|
||||||
|
"lib/netstandard2.0/MathNet.Numerics.xml",
|
||||||
|
"mathnet.numerics.signed.5.0.0.nupkg.sha512",
|
||||||
|
"mathnet.numerics.signed.nuspec"
|
||||||
|
]
|
||||||
|
},
|
||||||
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
|
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.4": {
|
||||||
"sha512": "0HgfWPfnjlzWFbW4pw6FYNuIMV8obVU+MUkiZ33g4UOpvZcmdWzdayfheKPZ5+EUly8SvfgW0dJwwIrW4IVLZQ==",
|
"sha512": "0HgfWPfnjlzWFbW4pw6FYNuIMV8obVU+MUkiZ33g4UOpvZcmdWzdayfheKPZ5+EUly8SvfgW0dJwwIrW4IVLZQ==",
|
||||||
"type": "package",
|
"type": "package",
|
||||||
@@ -1655,6 +1964,42 @@
|
|||||||
"microsoft.identitymodel.tokens.nuspec"
|
"microsoft.identitymodel.tokens.nuspec"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"Microsoft.IO.RecyclableMemoryStream/3.0.0": {
|
||||||
|
"sha512": "irv0HuqoH8Ig5i2fO+8dmDNdFdsrO+DoQcedwIlb810qpZHBNQHZLW7C/AHBQDgLLpw2T96vmMAy/aE4Yj55Sg==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "microsoft.io.recyclablememorystream/3.0.0",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"README.md",
|
||||||
|
"lib/net6.0/Microsoft.IO.RecyclableMemoryStream.dll",
|
||||||
|
"lib/net6.0/Microsoft.IO.RecyclableMemoryStream.xml",
|
||||||
|
"lib/netstandard2.0/Microsoft.IO.RecyclableMemoryStream.dll",
|
||||||
|
"lib/netstandard2.0/Microsoft.IO.RecyclableMemoryStream.xml",
|
||||||
|
"lib/netstandard2.1/Microsoft.IO.RecyclableMemoryStream.dll",
|
||||||
|
"lib/netstandard2.1/Microsoft.IO.RecyclableMemoryStream.xml",
|
||||||
|
"microsoft.io.recyclablememorystream.3.0.0.nupkg.sha512",
|
||||||
|
"microsoft.io.recyclablememorystream.nuspec"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Microsoft.NETCore.Platforms/5.0.0": {
|
||||||
|
"sha512": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "microsoft.netcore.platforms/5.0.0",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"Icon.png",
|
||||||
|
"LICENSE.TXT",
|
||||||
|
"THIRD-PARTY-NOTICES.TXT",
|
||||||
|
"lib/netstandard1.0/_._",
|
||||||
|
"microsoft.netcore.platforms.5.0.0.nupkg.sha512",
|
||||||
|
"microsoft.netcore.platforms.nuspec",
|
||||||
|
"runtime.json",
|
||||||
|
"useSharedDesignerContext.txt",
|
||||||
|
"version.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
"Microsoft.OpenApi/1.6.23": {
|
"Microsoft.OpenApi/1.6.23": {
|
||||||
"sha512": "tZ1I0KXnn98CWuV8cpI247A17jaY+ILS9vvF7yhI0uPPEqF4P1d7BWL5Uwtel10w9NucllHB3nTkfYTAcHAh8g==",
|
"sha512": "tZ1I0KXnn98CWuV8cpI247A17jaY+ILS9vvF7yhI0uPPEqF4P1d7BWL5Uwtel10w9NucllHB3nTkfYTAcHAh8g==",
|
||||||
"type": "package",
|
"type": "package",
|
||||||
@@ -1688,6 +2033,132 @@
|
|||||||
"microsoft.sqlserver.server.nuspec"
|
"microsoft.sqlserver.server.nuspec"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"NPOI/2.7.3": {
|
||||||
|
"sha512": "iCZx3DSwUSwaV61E8tXgPlPuxYmcYV/Zi405nGlxQvWaGTAbuc0KvSBjsLucQUJ92iMeetT8iK9makLfF4uZ3g==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "npoi/2.7.3",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"LICENSE",
|
||||||
|
"README.md",
|
||||||
|
"lib/net472/NPOI.Core.dll",
|
||||||
|
"lib/net472/NPOI.Core.pdb",
|
||||||
|
"lib/net472/NPOI.Core.xml",
|
||||||
|
"lib/net472/NPOI.OOXML.dll",
|
||||||
|
"lib/net472/NPOI.OOXML.pdb",
|
||||||
|
"lib/net472/NPOI.OOXML.xml",
|
||||||
|
"lib/net472/NPOI.OpenXml4Net.dll",
|
||||||
|
"lib/net472/NPOI.OpenXml4Net.pdb",
|
||||||
|
"lib/net472/NPOI.OpenXml4Net.xml",
|
||||||
|
"lib/net472/NPOI.OpenXmlFormats.dll",
|
||||||
|
"lib/net472/NPOI.OpenXmlFormats.pdb",
|
||||||
|
"lib/net472/NPOI.OpenXmlFormats.xml",
|
||||||
|
"lib/net8.0/NPOI.Core.dll",
|
||||||
|
"lib/net8.0/NPOI.Core.pdb",
|
||||||
|
"lib/net8.0/NPOI.Core.xml",
|
||||||
|
"lib/net8.0/NPOI.OOXML.dll",
|
||||||
|
"lib/net8.0/NPOI.OOXML.pdb",
|
||||||
|
"lib/net8.0/NPOI.OOXML.xml",
|
||||||
|
"lib/net8.0/NPOI.OpenXml4Net.dll",
|
||||||
|
"lib/net8.0/NPOI.OpenXml4Net.pdb",
|
||||||
|
"lib/net8.0/NPOI.OpenXml4Net.xml",
|
||||||
|
"lib/net8.0/NPOI.OpenXmlFormats.dll",
|
||||||
|
"lib/net8.0/NPOI.OpenXmlFormats.pdb",
|
||||||
|
"lib/net8.0/NPOI.OpenXmlFormats.xml",
|
||||||
|
"lib/netstandard2.0/NPOI.Core.dll",
|
||||||
|
"lib/netstandard2.0/NPOI.Core.pdb",
|
||||||
|
"lib/netstandard2.0/NPOI.Core.xml",
|
||||||
|
"lib/netstandard2.0/NPOI.OOXML.dll",
|
||||||
|
"lib/netstandard2.0/NPOI.OOXML.pdb",
|
||||||
|
"lib/netstandard2.0/NPOI.OOXML.xml",
|
||||||
|
"lib/netstandard2.0/NPOI.OpenXml4Net.dll",
|
||||||
|
"lib/netstandard2.0/NPOI.OpenXml4Net.pdb",
|
||||||
|
"lib/netstandard2.0/NPOI.OpenXml4Net.xml",
|
||||||
|
"lib/netstandard2.0/NPOI.OpenXmlFormats.dll",
|
||||||
|
"lib/netstandard2.0/NPOI.OpenXmlFormats.pdb",
|
||||||
|
"lib/netstandard2.0/NPOI.OpenXmlFormats.xml",
|
||||||
|
"lib/netstandard2.1/NPOI.Core.dll",
|
||||||
|
"lib/netstandard2.1/NPOI.Core.pdb",
|
||||||
|
"lib/netstandard2.1/NPOI.Core.xml",
|
||||||
|
"lib/netstandard2.1/NPOI.OOXML.dll",
|
||||||
|
"lib/netstandard2.1/NPOI.OOXML.pdb",
|
||||||
|
"lib/netstandard2.1/NPOI.OOXML.xml",
|
||||||
|
"lib/netstandard2.1/NPOI.OpenXml4Net.dll",
|
||||||
|
"lib/netstandard2.1/NPOI.OpenXml4Net.pdb",
|
||||||
|
"lib/netstandard2.1/NPOI.OpenXml4Net.xml",
|
||||||
|
"lib/netstandard2.1/NPOI.OpenXmlFormats.dll",
|
||||||
|
"lib/netstandard2.1/NPOI.OpenXmlFormats.pdb",
|
||||||
|
"lib/netstandard2.1/NPOI.OpenXmlFormats.xml",
|
||||||
|
"logo/120_120.jpg",
|
||||||
|
"logo/240_240.png",
|
||||||
|
"logo/32_32.jpg",
|
||||||
|
"logo/60_60.jpg",
|
||||||
|
"npoi.2.7.3.nupkg.sha512",
|
||||||
|
"npoi.nuspec"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"SharpZipLib/1.4.2": {
|
||||||
|
"sha512": "yjj+3zgz8zgXpiiC3ZdF/iyTBbz2fFvMxZFEBPUcwZjIvXOf37Ylm+K58hqMfIBt5JgU/Z2uoUS67JmTLe973A==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "sharpziplib/1.4.2",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"images/sharpziplib-nuget-256x256.png",
|
||||||
|
"lib/net6.0/ICSharpCode.SharpZipLib.dll",
|
||||||
|
"lib/net6.0/ICSharpCode.SharpZipLib.pdb",
|
||||||
|
"lib/net6.0/ICSharpCode.SharpZipLib.xml",
|
||||||
|
"lib/netstandard2.0/ICSharpCode.SharpZipLib.dll",
|
||||||
|
"lib/netstandard2.0/ICSharpCode.SharpZipLib.pdb",
|
||||||
|
"lib/netstandard2.0/ICSharpCode.SharpZipLib.xml",
|
||||||
|
"lib/netstandard2.1/ICSharpCode.SharpZipLib.dll",
|
||||||
|
"lib/netstandard2.1/ICSharpCode.SharpZipLib.pdb",
|
||||||
|
"lib/netstandard2.1/ICSharpCode.SharpZipLib.xml",
|
||||||
|
"sharpziplib.1.4.2.nupkg.sha512",
|
||||||
|
"sharpziplib.nuspec"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"SixLabors.Fonts/1.0.1": {
|
||||||
|
"sha512": "ljezRHWc7N0azdQViib7Aa5v+DagRVkKI2/93kEbtjVczLs+yTkSq6gtGmvOcx4IqyNbO3GjLt7SAQTpLkySNw==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "sixlabors.fonts/1.0.1",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"lib/netcoreapp3.1/SixLabors.Fonts.dll",
|
||||||
|
"lib/netcoreapp3.1/SixLabors.Fonts.xml",
|
||||||
|
"lib/netstandard2.0/SixLabors.Fonts.dll",
|
||||||
|
"lib/netstandard2.0/SixLabors.Fonts.xml",
|
||||||
|
"lib/netstandard2.1/SixLabors.Fonts.dll",
|
||||||
|
"lib/netstandard2.1/SixLabors.Fonts.xml",
|
||||||
|
"sixlabors.fonts.1.0.1.nupkg.sha512",
|
||||||
|
"sixlabors.fonts.128.png",
|
||||||
|
"sixlabors.fonts.nuspec"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"SixLabors.ImageSharp/2.1.10": {
|
||||||
|
"sha512": "hk1E7U3RSlxrBVo6Gb6OjeM52fChpFYH+SZvyT/M2vzSGlzAaKE33hc3V/Pvnjcnn1opT8/Z+0QfqdM5HsIaeA==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "sixlabors.imagesharp/2.1.10",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"lib/net472/SixLabors.ImageSharp.dll",
|
||||||
|
"lib/net472/SixLabors.ImageSharp.xml",
|
||||||
|
"lib/netcoreapp2.1/SixLabors.ImageSharp.dll",
|
||||||
|
"lib/netcoreapp2.1/SixLabors.ImageSharp.xml",
|
||||||
|
"lib/netcoreapp3.1/SixLabors.ImageSharp.dll",
|
||||||
|
"lib/netcoreapp3.1/SixLabors.ImageSharp.xml",
|
||||||
|
"lib/netstandard2.0/SixLabors.ImageSharp.dll",
|
||||||
|
"lib/netstandard2.0/SixLabors.ImageSharp.xml",
|
||||||
|
"lib/netstandard2.1/SixLabors.ImageSharp.dll",
|
||||||
|
"lib/netstandard2.1/SixLabors.ImageSharp.xml",
|
||||||
|
"sixlabors.imagesharp.128.png",
|
||||||
|
"sixlabors.imagesharp.2.1.10.nupkg.sha512",
|
||||||
|
"sixlabors.imagesharp.nuspec"
|
||||||
|
]
|
||||||
|
},
|
||||||
"Swashbuckle.AspNetCore/8.1.1": {
|
"Swashbuckle.AspNetCore/8.1.1": {
|
||||||
"sha512": "HJHexmU0PiYevgTLvKjYkxEtclF2w4O7iTd3Ef3p6KeT0kcYLpkFVgCw6glpGS57h8769anv8G+NFi9Kge+/yw==",
|
"sha512": "HJHexmU0PiYevgTLvKjYkxEtclF2w4O7iTd3Ef3p6KeT0kcYLpkFVgCw6glpGS57h8769anv8G+NFi9Kge+/yw==",
|
||||||
"type": "package",
|
"type": "package",
|
||||||
@@ -2074,6 +2545,76 @@
|
|||||||
"useSharedDesignerContext.txt"
|
"useSharedDesignerContext.txt"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"System.Security.Cryptography.Xml/8.0.2": {
|
||||||
|
"sha512": "aDM/wm0ZGEZ6ZYJLzgqjp2FZdHbDHh6/OmpGfb7AdZ105zYmPn/83JRU2xLIbwgoNz9U1SLUTJN0v5th3qmvjA==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "system.security.cryptography.xml/8.0.2",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"Icon.png",
|
||||||
|
"LICENSE.TXT",
|
||||||
|
"THIRD-PARTY-NOTICES.TXT",
|
||||||
|
"buildTransitive/net461/System.Security.Cryptography.Xml.targets",
|
||||||
|
"buildTransitive/net462/_._",
|
||||||
|
"buildTransitive/net6.0/_._",
|
||||||
|
"buildTransitive/netcoreapp2.0/System.Security.Cryptography.Xml.targets",
|
||||||
|
"lib/net462/System.Security.Cryptography.Xml.dll",
|
||||||
|
"lib/net462/System.Security.Cryptography.Xml.xml",
|
||||||
|
"lib/net6.0/System.Security.Cryptography.Xml.dll",
|
||||||
|
"lib/net6.0/System.Security.Cryptography.Xml.xml",
|
||||||
|
"lib/net7.0/System.Security.Cryptography.Xml.dll",
|
||||||
|
"lib/net7.0/System.Security.Cryptography.Xml.xml",
|
||||||
|
"lib/net8.0/System.Security.Cryptography.Xml.dll",
|
||||||
|
"lib/net8.0/System.Security.Cryptography.Xml.xml",
|
||||||
|
"lib/netstandard2.0/System.Security.Cryptography.Xml.dll",
|
||||||
|
"lib/netstandard2.0/System.Security.Cryptography.Xml.xml",
|
||||||
|
"system.security.cryptography.xml.8.0.2.nupkg.sha512",
|
||||||
|
"system.security.cryptography.xml.nuspec",
|
||||||
|
"useSharedDesignerContext.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"System.Text.Encoding.CodePages/5.0.0": {
|
||||||
|
"sha512": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==",
|
||||||
|
"type": "package",
|
||||||
|
"path": "system.text.encoding.codepages/5.0.0",
|
||||||
|
"files": [
|
||||||
|
".nupkg.metadata",
|
||||||
|
".signature.p7s",
|
||||||
|
"Icon.png",
|
||||||
|
"LICENSE.TXT",
|
||||||
|
"THIRD-PARTY-NOTICES.TXT",
|
||||||
|
"lib/MonoAndroid10/_._",
|
||||||
|
"lib/MonoTouch10/_._",
|
||||||
|
"lib/net46/System.Text.Encoding.CodePages.dll",
|
||||||
|
"lib/net461/System.Text.Encoding.CodePages.dll",
|
||||||
|
"lib/net461/System.Text.Encoding.CodePages.xml",
|
||||||
|
"lib/netstandard1.3/System.Text.Encoding.CodePages.dll",
|
||||||
|
"lib/netstandard2.0/System.Text.Encoding.CodePages.dll",
|
||||||
|
"lib/netstandard2.0/System.Text.Encoding.CodePages.xml",
|
||||||
|
"lib/xamarinios10/_._",
|
||||||
|
"lib/xamarinmac20/_._",
|
||||||
|
"lib/xamarintvos10/_._",
|
||||||
|
"lib/xamarinwatchos10/_._",
|
||||||
|
"ref/MonoAndroid10/_._",
|
||||||
|
"ref/MonoTouch10/_._",
|
||||||
|
"ref/xamarinios10/_._",
|
||||||
|
"ref/xamarinmac20/_._",
|
||||||
|
"ref/xamarintvos10/_._",
|
||||||
|
"ref/xamarinwatchos10/_._",
|
||||||
|
"runtimes/win/lib/net461/System.Text.Encoding.CodePages.dll",
|
||||||
|
"runtimes/win/lib/net461/System.Text.Encoding.CodePages.xml",
|
||||||
|
"runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.dll",
|
||||||
|
"runtimes/win/lib/netcoreapp2.0/System.Text.Encoding.CodePages.xml",
|
||||||
|
"runtimes/win/lib/netstandard1.3/System.Text.Encoding.CodePages.dll",
|
||||||
|
"runtimes/win/lib/netstandard2.0/System.Text.Encoding.CodePages.dll",
|
||||||
|
"runtimes/win/lib/netstandard2.0/System.Text.Encoding.CodePages.xml",
|
||||||
|
"system.text.encoding.codepages.5.0.0.nupkg.sha512",
|
||||||
|
"system.text.encoding.codepages.nuspec",
|
||||||
|
"useSharedDesignerContext.txt",
|
||||||
|
"version.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
"System.Text.Encodings.Web/4.7.2": {
|
"System.Text.Encodings.Web/4.7.2": {
|
||||||
"sha512": "iTUgB/WtrZ1sWZs84F2hwyQhiRH6QNjQv2DkwrH+WP6RoFga2Q1m3f9/Q7FG8cck8AdHitQkmkXSY8qylcDmuA==",
|
"sha512": "iTUgB/WtrZ1sWZs84F2hwyQhiRH6QNjQv2DkwrH+WP6RoFga2Q1m3f9/Q7FG8cck8AdHitQkmkXSY8qylcDmuA==",
|
||||||
"type": "package",
|
"type": "package",
|
||||||
@@ -2164,6 +2705,7 @@
|
|||||||
"Microsoft.AspNetCore.Authentication.JwtBearer >= 9.0.4",
|
"Microsoft.AspNetCore.Authentication.JwtBearer >= 9.0.4",
|
||||||
"Microsoft.AspNetCore.OpenApi >= 9.0.3",
|
"Microsoft.AspNetCore.OpenApi >= 9.0.3",
|
||||||
"Microsoft.Data.SqlClient >= 6.0.2",
|
"Microsoft.Data.SqlClient >= 6.0.2",
|
||||||
|
"NPOI >= 2.7.3",
|
||||||
"Swashbuckle.AspNetCore >= 8.1.1",
|
"Swashbuckle.AspNetCore >= 8.1.1",
|
||||||
"System.IdentityModel.Tokens.Jwt >= 8.9.0"
|
"System.IdentityModel.Tokens.Jwt >= 8.9.0"
|
||||||
]
|
]
|
||||||
@@ -2234,6 +2776,10 @@
|
|||||||
"target": "Package",
|
"target": "Package",
|
||||||
"version": "[6.0.2, )"
|
"version": "[6.0.2, )"
|
||||||
},
|
},
|
||||||
|
"NPOI": {
|
||||||
|
"target": "Package",
|
||||||
|
"version": "[2.7.3, )"
|
||||||
|
},
|
||||||
"Swashbuckle.AspNetCore": {
|
"Swashbuckle.AspNetCore": {
|
||||||
"target": "Package",
|
"target": "Package",
|
||||||
"version": "[8.1.1, )"
|
"version": "[8.1.1, )"
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, RadioGroup, FormControlLabel, Radio, InputAdornment
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { NotaCreditoDebitoDto } from '../../../models/dtos/Contables/NotaCreditoDebitoDto';
|
||||||
|
import type { CreateNotaDto } from '../../../models/dtos/Contables/CreateNotaDto';
|
||||||
|
import type { UpdateNotaDto } from '../../../models/dtos/Contables/UpdateNotaDto';
|
||||||
|
import type { EmpresaDto } from '../../../models/dtos/Distribucion/EmpresaDto';
|
||||||
|
import type { DistribuidorDto } from '../../../models/dtos/Distribucion/DistribuidorDto';
|
||||||
|
import type { CanillaDto } from '../../../models/dtos/Distribucion/CanillaDto'; // Para el dropdown
|
||||||
|
import empresaService from '../../../services/Distribucion/empresaService';
|
||||||
|
import distribuidorService from '../../../services/Distribucion/distribuidorService';
|
||||||
|
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'
|
||||||
|
};
|
||||||
|
|
||||||
|
type DestinoType = 'Distribuidores' | 'Canillas';
|
||||||
|
type TipoNotaType = 'Credito' | 'Debito';
|
||||||
|
|
||||||
|
interface NotaCreditoDebitoFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreateNotaDto | UpdateNotaDto, idNota?: number) => Promise<void>;
|
||||||
|
initialData?: NotaCreditoDebitoDto | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NotaCreditoDebitoFormModal: React.FC<NotaCreditoDebitoFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [destino, setDestino] = useState<DestinoType>('Distribuidores');
|
||||||
|
const [idDestino, setIdDestino] = useState<number | string>('');
|
||||||
|
const [referencia, setReferencia] = useState('');
|
||||||
|
const [tipo, setTipo] = useState<TipoNotaType>('Credito');
|
||||||
|
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [monto, setMonto] = useState<string>('');
|
||||||
|
const [observaciones, setObservaciones] = useState('');
|
||||||
|
const [idEmpresa, setIdEmpresa] = useState<number | string>('');
|
||||||
|
|
||||||
|
const [destinatarios, setDestinatarios] = useState<(DistribuidorDto | CanillaDto)[]>([]);
|
||||||
|
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
const fetchEmpresas = useCallback(async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const data = await empresaService.getAllEmpresas();
|
||||||
|
setEmpresas(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar empresas", error);
|
||||||
|
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar empresas.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchDestinatarios = useCallback(async (tipoDestino: DestinoType) => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
setIdDestino(''); // Resetear selección de destinatario al cambiar tipo
|
||||||
|
setDestinatarios([]);
|
||||||
|
try {
|
||||||
|
if (tipoDestino === 'Distribuidores') {
|
||||||
|
const data = await distribuidorService.getAllDistribuidores();
|
||||||
|
setDestinatarios(data);
|
||||||
|
} else if (tipoDestino === 'Canillas') {
|
||||||
|
const data = await canillaService.getAllCanillas(undefined, undefined, true); // Solo activos
|
||||||
|
setDestinatarios(data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error al cargar ${tipoDestino}`, error);
|
||||||
|
setLocalErrors(prev => ({...prev, dropdowns: `Error al cargar ${tipoDestino}.`}));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
fetchEmpresas();
|
||||||
|
// Cargar destinatarios basados en el initialData o el default
|
||||||
|
const initialDestinoType = initialData?.destino || 'Distribuidores';
|
||||||
|
setDestino(initialDestinoType as DestinoType);
|
||||||
|
fetchDestinatarios(initialDestinoType as DestinoType);
|
||||||
|
|
||||||
|
setIdDestino(initialData?.idDestino || '');
|
||||||
|
setReferencia(initialData?.referencia || '');
|
||||||
|
setTipo(initialData?.tipo as TipoNotaType || 'Credito');
|
||||||
|
setFecha(initialData?.fecha || new Date().toISOString().split('T')[0]);
|
||||||
|
setMonto(initialData?.monto?.toString() || '');
|
||||||
|
setObservaciones(initialData?.observaciones || '');
|
||||||
|
setIdEmpresa(initialData?.idEmpresa || '');
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage, fetchEmpresas, fetchDestinatarios]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(open && !isEditing) { // Solo cambiar destinatarios si es creación y cambia el tipo de Destino
|
||||||
|
fetchDestinatarios(destino);
|
||||||
|
}
|
||||||
|
}, [destino, open, isEditing, fetchDestinatarios]);
|
||||||
|
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!destino) errors.destino = 'Seleccione un tipo de destino.';
|
||||||
|
if (!idDestino) errors.idDestino = 'Seleccione un destinatario.';
|
||||||
|
if (!tipo) errors.tipo = 'Seleccione el tipo de nota.';
|
||||||
|
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 (!monto.trim() || isNaN(parseFloat(monto)) || parseFloat(monto) <= 0) errors.monto = 'Monto debe ser un número positivo.';
|
||||||
|
if (!idEmpresa) errors.idEmpresa = 'Seleccione una empresa (para el saldo).';
|
||||||
|
|
||||||
|
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 montoNum = parseFloat(monto);
|
||||||
|
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
const dataToSubmit: UpdateNotaDto = {
|
||||||
|
monto: montoNum,
|
||||||
|
observaciones: observaciones || undefined,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit, initialData.idNota);
|
||||||
|
} else {
|
||||||
|
const dataToSubmit: CreateNotaDto = {
|
||||||
|
destino,
|
||||||
|
idDestino: Number(idDestino),
|
||||||
|
referencia: referencia || undefined,
|
||||||
|
tipo,
|
||||||
|
fecha,
|
||||||
|
monto: montoNum,
|
||||||
|
observaciones: observaciones || undefined,
|
||||||
|
idEmpresa: Number(idEmpresa),
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de NotaCreditoDebitoFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Nota de Crédito/Débito' : 'Registrar Nota de Crédito/Débito'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||||
|
<FormControl component="fieldset" margin="dense" required disabled={isEditing}>
|
||||||
|
<Typography component="legend" variant="body2" sx={{mb:0.5}}>Destino</Typography>
|
||||||
|
<RadioGroup row value={destino} onChange={(e) => {setDestino(e.target.value as DestinoType); handleInputChange('destino'); }}>
|
||||||
|
<FormControlLabel value="Distribuidores" control={<Radio size="small"/>} label="Distribuidor" disabled={loading || isEditing}/>
|
||||||
|
<FormControlLabel value="Canillas" control={<Radio size="small"/>} label="Canillita" disabled={loading || isEditing}/>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idDestino} required disabled={isEditing}>
|
||||||
|
<InputLabel id="destinatario-nota-select-label">Destinatario</InputLabel>
|
||||||
|
<Select labelId="destinatario-nota-select-label" label="Destinatario" value={idDestino}
|
||||||
|
onChange={(e) => {setIdDestino(e.target.value as number); handleInputChange('idDestino');}}
|
||||||
|
disabled={loading || loadingDropdowns || isEditing || destinatarios.length === 0}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{destinatarios.map((d) => (
|
||||||
|
<MenuItem key={'idDistribuidor' in d ? d.idDistribuidor : d.idCanilla} value={'idDistribuidor' in d ? d.idDistribuidor : d.idCanilla}>
|
||||||
|
{'nomApe' in d ? d.nomApe : d.nombre} {/* Muestra nomApe para Canilla, nombre para Distribuidor */}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idDestino && <Typography color="error" variant="caption">{localErrors.idDestino}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idEmpresa} required disabled={isEditing}>
|
||||||
|
<InputLabel id="empresa-nota-select-label">Empresa (del Saldo)</InputLabel>
|
||||||
|
<Select labelId="empresa-nota-select-label" label="Empresa (del Saldo)" value={idEmpresa}
|
||||||
|
onChange={(e) => {setIdEmpresa(e.target.value as number); handleInputChange('idEmpresa');}}
|
||||||
|
disabled={loading || loadingDropdowns || isEditing}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{empresas.map((e) => (<MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idEmpresa && <Typography color="error" variant="caption">{localErrors.idEmpresa}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField label="Fecha Nota" 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="Referencia (Opcional)" value={referencia}
|
||||||
|
onChange={(e) => setReferencia(e.target.value)}
|
||||||
|
margin="dense" fullWidth disabled={loading || isEditing}
|
||||||
|
/>
|
||||||
|
<FormControl component="fieldset" margin="dense" error={!!localErrors.tipo} required>
|
||||||
|
<Typography component="legend" variant="body2" sx={{mb:0.5}}>Tipo de Nota</Typography>
|
||||||
|
<RadioGroup row value={tipo} onChange={(e) => {setTipo(e.target.value as TipoNotaType); handleInputChange('tipo');}} >
|
||||||
|
<FormControlLabel value="Credito" control={<Radio size="small"/>} label="Crédito" disabled={loading || isEditing}/>
|
||||||
|
<FormControlLabel value="Debito" control={<Radio size="small"/>} label="Débito" disabled={loading || isEditing}/>
|
||||||
|
</RadioGroup>
|
||||||
|
{localErrors.tipo && <Typography color="error" variant="caption">{localErrors.tipo}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Monto" type="number" value={monto} required
|
||||||
|
onChange={(e) => {setMonto(e.target.value); handleInputChange('monto');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.monto} helperText={localErrors.monto || ''}
|
||||||
|
disabled={loading} inputProps={{step: "0.01", min:0.01, lang:"es-AR" }}
|
||||||
|
InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
|
||||||
|
/>
|
||||||
|
<TextField label="Observaciones (Opcional)" value={observaciones}
|
||||||
|
onChange={(e) => setObservaciones(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 Nota')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotaCreditoDebitoFormModal;
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, RadioGroup, FormControlLabel, Radio, InputAdornment
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PagoDistribuidorDto } from '../../../models/dtos/Contables/PagoDistribuidorDto';
|
||||||
|
import type { CreatePagoDistribuidorDto } from '../../../models/dtos/Contables/CreatePagoDistribuidorDto';
|
||||||
|
import type { UpdatePagoDistribuidorDto } from '../../../models/dtos/Contables/UpdatePagoDistribuidorDto';
|
||||||
|
import type { DistribuidorDto } from '../../../models/dtos/Distribucion/DistribuidorDto';
|
||||||
|
import type { TipoPago } from '../../../models/Entities/TipoPago';
|
||||||
|
import type { EmpresaDto } from '../../../models/dtos/Distribucion/EmpresaDto';
|
||||||
|
import distribuidorService from '../../../services/Distribucion/distribuidorService';
|
||||||
|
import tipoPagoService from '../../../services/Contables/tipoPagoService';
|
||||||
|
import empresaService from '../../../services/Distribucion/empresaService';
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 600 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PagoDistribuidorFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreatePagoDistribuidorDto | UpdatePagoDistribuidorDto, idPago?: number) => Promise<void>;
|
||||||
|
initialData?: PagoDistribuidorDto | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PagoDistribuidorFormModal: React.FC<PagoDistribuidorFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idDistribuidor, setIdDistribuidor] = useState<number | string>('');
|
||||||
|
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [tipoMovimiento, setTipoMovimiento] = useState<'Recibido' | 'Realizado'>('Recibido');
|
||||||
|
const [recibo, setRecibo] = useState<string>('');
|
||||||
|
const [monto, setMonto] = useState<string>('');
|
||||||
|
const [idTipoPago, setIdTipoPago] = useState<number | string>('');
|
||||||
|
const [detalle, setDetalle] = useState('');
|
||||||
|
const [idEmpresa, setIdEmpresa] = useState<number | string>('');
|
||||||
|
|
||||||
|
|
||||||
|
const [distribuidores, setDistribuidores] = useState<DistribuidorDto[]>([]);
|
||||||
|
const [tiposPago, setTiposPago] = useState<TipoPago[]>([]);
|
||||||
|
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||||
|
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 [distData, tiposPagoData, empresasData] = await Promise.all([
|
||||||
|
distribuidorService.getAllDistribuidores(),
|
||||||
|
tipoPagoService.getAllTiposPago(),
|
||||||
|
empresaService.getAllEmpresas()
|
||||||
|
]);
|
||||||
|
setDistribuidores(distData);
|
||||||
|
setTiposPago(tiposPagoData);
|
||||||
|
setEmpresas(empresasData);
|
||||||
|
} 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();
|
||||||
|
setIdDistribuidor(initialData?.idDistribuidor || '');
|
||||||
|
setFecha(initialData?.fecha || new Date().toISOString().split('T')[0]);
|
||||||
|
setTipoMovimiento(initialData?.tipoMovimiento || 'Recibido');
|
||||||
|
setRecibo(initialData?.recibo?.toString() || '');
|
||||||
|
setMonto(initialData?.monto?.toString() || '');
|
||||||
|
setIdTipoPago(initialData?.idTipoPago || '');
|
||||||
|
setDetalle(initialData?.detalle || '');
|
||||||
|
setIdEmpresa(initialData?.idEmpresa || '');
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
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 (!recibo.trim() || isNaN(parseInt(recibo)) || parseInt(recibo) <= 0) errors.recibo = 'Nro. Recibo es obligatorio y numérico.';
|
||||||
|
if (!monto.trim() || isNaN(parseFloat(monto)) || parseFloat(monto) <= 0) errors.monto = 'Monto debe ser un número positivo.';
|
||||||
|
if (!idTipoPago) errors.idTipoPago = 'Seleccione un tipo de pago.';
|
||||||
|
if (!idEmpresa) errors.idEmpresa = 'Seleccione una empresa (para el saldo).';
|
||||||
|
|
||||||
|
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 montoNum = parseFloat(monto);
|
||||||
|
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
const dataToSubmit: UpdatePagoDistribuidorDto = {
|
||||||
|
monto: montoNum,
|
||||||
|
idTipoPago: Number(idTipoPago),
|
||||||
|
detalle: detalle || undefined,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit, initialData.idPago);
|
||||||
|
} else {
|
||||||
|
const dataToSubmit: CreatePagoDistribuidorDto = {
|
||||||
|
idDistribuidor: Number(idDistribuidor),
|
||||||
|
fecha,
|
||||||
|
tipoMovimiento,
|
||||||
|
recibo: parseInt(recibo, 10),
|
||||||
|
monto: montoNum,
|
||||||
|
idTipoPago: Number(idTipoPago),
|
||||||
|
detalle: detalle || undefined,
|
||||||
|
idEmpresa: Number(idEmpresa),
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de PagoDistribuidorFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Pago de Distribuidor' : 'Registrar Nuevo Pago de 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.idDistribuidor} required disabled={isEditing}>
|
||||||
|
<InputLabel id="distribuidor-pago-select-label">Distribuidor</InputLabel>
|
||||||
|
<Select labelId="distribuidor-pago-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>
|
||||||
|
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idEmpresa} required disabled={isEditing}>
|
||||||
|
<InputLabel id="empresa-pago-select-label">Empresa (del Saldo)</InputLabel>
|
||||||
|
<Select labelId="empresa-pago-select-label" label="Empresa (del Saldo)" value={idEmpresa}
|
||||||
|
onChange={(e) => {setIdEmpresa(e.target.value as number); handleInputChange('idEmpresa');}}
|
||||||
|
disabled={loading || loadingDropdowns || isEditing}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{empresas.map((e) => (<MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idEmpresa && <Typography color="error" variant="caption">{localErrors.idEmpresa}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField label="Fecha Pago" 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 'Recibido' | 'Realizado'); handleInputChange('tipoMovimiento');}} >
|
||||||
|
<FormControlLabel value="Recibido" control={<Radio size="small"/>} label="Recibido de Distribuidor" disabled={loading || isEditing}/>
|
||||||
|
<FormControlLabel value="Realizado" control={<Radio size="small"/>} label="Realizado a Distribuidor" disabled={loading || isEditing}/>
|
||||||
|
</RadioGroup>
|
||||||
|
{localErrors.tipoMovimiento && <Typography color="error" variant="caption">{localErrors.tipoMovimiento}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField label="Nro. Recibo" type="number" value={recibo} required
|
||||||
|
onChange={(e) => {setRecibo(e.target.value); handleInputChange('recibo');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.recibo} helperText={localErrors.recibo || ''}
|
||||||
|
disabled={loading || isEditing} inputProps={{min:1}}
|
||||||
|
/>
|
||||||
|
<TextField label="Monto" type="number" value={monto} required
|
||||||
|
onChange={(e) => {setMonto(e.target.value); handleInputChange('monto');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.monto} helperText={localErrors.monto || ''}
|
||||||
|
disabled={loading} inputProps={{step: "0.01", min:0.01, lang:"es-AR" }}
|
||||||
|
InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
|
||||||
|
/>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idTipoPago} required>
|
||||||
|
<InputLabel id="tipopago-pago-select-label">Tipo de Pago</InputLabel>
|
||||||
|
<Select labelId="tipopago-pago-select-label" label="Tipo de Pago" value={idTipoPago}
|
||||||
|
onChange={(e) => {setIdTipoPago(e.target.value as number); handleInputChange('idTipoPago');}}
|
||||||
|
disabled={loading || loadingDropdowns}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{tiposPago.map((tp) => (<MenuItem key={tp.idTipoPago} value={tp.idTipoPago}>{tp.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idTipoPago && <Typography color="error" variant="caption">{localErrors.idTipoPago}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Detalle (Opcional)" value={detalle}
|
||||||
|
onChange={(e) => setDetalle(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 Pago')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PagoDistribuidorFormModal;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Modal, Box, Typography, TextField, Button, CircularProgress, Alert } from '@mui/material';
|
import { Modal, Box, Typography, TextField, Button, CircularProgress, Alert } from '@mui/material';
|
||||||
import type { TipoPago } from '../../../models/Entities/TipoPago';
|
import type { TipoPago } from '../../../models/Entities/TipoPago';
|
||||||
import type { CreateTipoPagoDto } from '../../../models/dtos/tiposPago/CreateTipoPagoDto';
|
import type { CreateTipoPagoDto } from '../../../models/dtos/Contables/CreateTipoPagoDto';
|
||||||
|
|
||||||
const modalStyle = {
|
const modalStyle = {
|
||||||
position: 'absolute' as 'absolute',
|
position: 'absolute' as 'absolute',
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { ControlDevolucionesDto } from '../../../models/dtos/Distribucion/ControlDevolucionesDto';
|
||||||
|
import type { CreateControlDevolucionesDto } from '../../../models/dtos/Distribucion/CreateControlDevolucionesDto';
|
||||||
|
import type { UpdateControlDevolucionesDto } from '../../../models/dtos/Distribucion/UpdateControlDevolucionesDto';
|
||||||
|
import type { EmpresaDto } from '../../../models/dtos/Distribucion/EmpresaDto'; // DTO de Empresa
|
||||||
|
import empresaService from '../../../services//Distribucion/empresaService'; // Servicio de Empresa
|
||||||
|
|
||||||
|
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 ControlDevolucionesFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreateControlDevolucionesDto | UpdateControlDevolucionesDto, idControl?: number) => Promise<void>;
|
||||||
|
initialData?: ControlDevolucionesDto | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ControlDevolucionesFormModal: React.FC<ControlDevolucionesFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idEmpresa, setIdEmpresa] = useState<number | string>('');
|
||||||
|
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [entrada, setEntrada] = useState<string>('');
|
||||||
|
const [sobrantes, setSobrantes] = useState<string>('');
|
||||||
|
const [detalle, setDetalle] = useState('');
|
||||||
|
const [sinCargo, setSinCargo] = useState<string>('0'); // Default 0
|
||||||
|
|
||||||
|
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingEmpresas, setLoadingEmpresas] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchEmpresas = async () => {
|
||||||
|
setLoadingEmpresas(true);
|
||||||
|
try {
|
||||||
|
const data = await empresaService.getAllEmpresas();
|
||||||
|
setEmpresas(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar empresas", error);
|
||||||
|
setLocalErrors(prev => ({...prev, empresas: 'Error al cargar empresas.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingEmpresas(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
fetchEmpresas();
|
||||||
|
setIdEmpresa(initialData?.idEmpresa || '');
|
||||||
|
setFecha(initialData?.fecha || new Date().toISOString().split('T')[0]);
|
||||||
|
setEntrada(initialData?.entrada?.toString() || '0');
|
||||||
|
setSobrantes(initialData?.sobrantes?.toString() || '0');
|
||||||
|
setDetalle(initialData?.detalle || '');
|
||||||
|
setSinCargo(initialData?.sinCargo?.toString() || '0');
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idEmpresa) errors.idEmpresa = 'Seleccione una empresa.';
|
||||||
|
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.';
|
||||||
|
|
||||||
|
const entradaNum = parseInt(entrada, 10);
|
||||||
|
const sobrantesNum = parseInt(sobrantes, 10);
|
||||||
|
const sinCargoNum = parseInt(sinCargo, 10);
|
||||||
|
|
||||||
|
if (entrada.trim() === '' || isNaN(entradaNum) || entradaNum < 0) errors.entrada = 'Entrada debe ser un número >= 0.';
|
||||||
|
if (sobrantes.trim() === '' || isNaN(sobrantesNum) || sobrantesNum < 0) errors.sobrantes = 'Sobrantes debe ser un número >= 0.';
|
||||||
|
if (sinCargo.trim() === '' || isNaN(sinCargoNum) || sinCargoNum < 0) errors.sinCargo = 'Sin Cargo debe ser un número >= 0.';
|
||||||
|
|
||||||
|
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 commonData = {
|
||||||
|
entrada: parseInt(entrada, 10),
|
||||||
|
sobrantes: parseInt(sobrantes, 10),
|
||||||
|
detalle: detalle || undefined,
|
||||||
|
sinCargo: parseInt(sinCargo, 10),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
const dataToSubmit: UpdateControlDevolucionesDto = { ...commonData };
|
||||||
|
await onSubmit(dataToSubmit, initialData.idControl);
|
||||||
|
} else {
|
||||||
|
const dataToSubmit: CreateControlDevolucionesDto = {
|
||||||
|
...commonData,
|
||||||
|
idEmpresa: Number(idEmpresa),
|
||||||
|
fecha,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmit);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de ControlDevolucionesFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Control de Devoluciones' : 'Registrar Control de Devoluciones'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idEmpresa} required>
|
||||||
|
<InputLabel id="empresa-cd-select-label">Empresa</InputLabel>
|
||||||
|
<Select labelId="empresa-cd-select-label" label="Empresa" value={idEmpresa}
|
||||||
|
onChange={(e) => {setIdEmpresa(e.target.value as number); handleInputChange('idEmpresa');}}
|
||||||
|
disabled={loading || loadingEmpresas || isEditing}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{empresas.map((e) => (<MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idEmpresa && <Typography color="error" variant="caption">{localErrors.idEmpresa}</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="Entrada (Devolución Total)" type="number" value={entrada} required
|
||||||
|
onChange={(e) => {setEntrada(e.target.value); handleInputChange('entrada');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.entrada} helperText={localErrors.entrada || ''}
|
||||||
|
disabled={loading} inputProps={{min:0}}
|
||||||
|
/>
|
||||||
|
<TextField label="Sobrantes" type="number" value={sobrantes} required
|
||||||
|
onChange={(e) => {setSobrantes(e.target.value); handleInputChange('sobrantes');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.sobrantes} helperText={localErrors.sobrantes || ''}
|
||||||
|
disabled={loading} inputProps={{min:0}}
|
||||||
|
/>
|
||||||
|
<TextField label="Sin Cargo" type="number" value={sinCargo} required
|
||||||
|
onChange={(e) => {setSinCargo(e.target.value); handleInputChange('sinCargo');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.sinCargo} helperText={localErrors.sinCargo || ''}
|
||||||
|
disabled={loading} inputProps={{min:0}}
|
||||||
|
/>
|
||||||
|
<TextField label="Detalle (Opcional)" value={detalle}
|
||||||
|
onChange={(e) => setDetalle(e.target.value)}
|
||||||
|
margin="dense" fullWidth multiline rows={2} disabled={loading}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.empresas && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.empresas}</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 || loadingEmpresas}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Registrar Control')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ControlDevolucionesFormModal;
|
||||||
@@ -0,0 +1,396 @@
|
|||||||
|
// src/components/Modals/Distribucion/EntradaSalidaCanillaFormModal.tsx
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, Paper, IconButton, FormHelperText
|
||||||
|
} from '@mui/material';
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
|
||||||
|
import type { EntradaSalidaCanillaDto } from '../../../models/dtos/Distribucion/EntradaSalidaCanillaDto';
|
||||||
|
import type { UpdateEntradaSalidaCanillaDto } from '../../../models/dtos/Distribucion/UpdateEntradaSalidaCanillaDto';
|
||||||
|
import type { PublicacionDto } from '../../../models/dtos/Distribucion/PublicacionDto';
|
||||||
|
import type { CanillaDto } from '../../../models/dtos/Distribucion/CanillaDto';
|
||||||
|
import publicacionService from '../../../services/Distribucion/publicacionService';
|
||||||
|
import canillaService from '../../../services/Distribucion/canillaService';
|
||||||
|
import entradaSalidaCanillaService from '../../../services/Distribucion/entradaSalidaCanillaService';
|
||||||
|
import type { CreateBulkEntradaSalidaCanillaDto } from '../../../models/dtos/Distribucion/CreateBulkEntradaSalidaCanillaDto';
|
||||||
|
import type { EntradaSalidaCanillaItemDto } from '../../../models/dtos/Distribucion/EntradaSalidaCanillaItemDto';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const modalStyle = {
|
||||||
|
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: 2.5,
|
||||||
|
maxHeight: '90vh',
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface EntradaSalidaCanillaFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: UpdateEntradaSalidaCanillaDto, idParte: number) => Promise<void>;
|
||||||
|
initialData?: EntradaSalidaCanillaDto | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormRowItem {
|
||||||
|
id: string;
|
||||||
|
idPublicacion: number | string;
|
||||||
|
cantSalida: string;
|
||||||
|
cantEntrada: string;
|
||||||
|
observacion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EntradaSalidaCanillaFormModal: React.FC<EntradaSalidaCanillaFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
initialData,
|
||||||
|
errorMessage: parentErrorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idCanilla, setIdCanilla] = useState<number | string>('');
|
||||||
|
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [editIdPublicacion, setEditIdPublicacion] = useState<number | string>('');
|
||||||
|
const [editCantSalida, setEditCantSalida] = useState<string>('0');
|
||||||
|
const [editCantEntrada, setEditCantEntrada] = useState<string>('0');
|
||||||
|
const [editObservacion, setEditObservacion] = useState('');
|
||||||
|
const [items, setItems] = useState<FormRowItem[]>([]);
|
||||||
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
|
const [canillitas, setCanillitas] = useState<CanillaDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
const [modalSpecificApiError, setModalSpecificApiError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDropdownData = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: null }));
|
||||||
|
try {
|
||||||
|
const [pubsData, canillitasData] = await Promise.all([
|
||||||
|
publicacionService.getAllPublicaciones(undefined, undefined, true),
|
||||||
|
canillaService.getAllCanillas(undefined, undefined, true)
|
||||||
|
]);
|
||||||
|
setPublicaciones(pubsData);
|
||||||
|
setCanillitas(canillitasData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar datos para dropdowns", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar datos necesarios (publicaciones/canillitas).' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
fetchDropdownData();
|
||||||
|
clearErrorMessage();
|
||||||
|
setModalSpecificApiError(null);
|
||||||
|
setLocalErrors({});
|
||||||
|
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
setIdCanilla(initialData.idCanilla || '');
|
||||||
|
setFecha(initialData.fecha ? initialData.fecha.split('T')[0] : new Date().toISOString().split('T')[0]);
|
||||||
|
setEditIdPublicacion(initialData.idPublicacion || '');
|
||||||
|
setEditCantSalida(initialData.cantSalida?.toString() || '0');
|
||||||
|
setEditCantEntrada(initialData.cantEntrada?.toString() || '0');
|
||||||
|
setEditObservacion(initialData.observacion || '');
|
||||||
|
setItems([]);
|
||||||
|
} else {
|
||||||
|
setItems([{ id: Date.now().toString(), idPublicacion: '', cantSalida: '0', cantEntrada: '0', observacion: '' }]);
|
||||||
|
setIdCanilla('');
|
||||||
|
setFecha(new Date().toISOString().split('T')[0]);
|
||||||
|
setEditCantSalida('0');
|
||||||
|
setEditCantEntrada('0');
|
||||||
|
setEditObservacion('');
|
||||||
|
setEditIdPublicacion('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [open, initialData, isEditing, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const currentErrors: { [key: string]: string | null } = {};
|
||||||
|
if (!idCanilla) currentErrors.idCanilla = 'Seleccione un canillita.';
|
||||||
|
if (!fecha.trim()) currentErrors.fecha = 'La fecha es obligatoria.';
|
||||||
|
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fecha)) currentErrors.fecha = 'Formato de fecha inválido (YYYY-MM-DD).';
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
const salidaNum = parseInt(editCantSalida, 10);
|
||||||
|
const entradaNum = parseInt(editCantEntrada, 10);
|
||||||
|
if (editCantSalida.trim() === '' || isNaN(salidaNum) || salidaNum < 0) {
|
||||||
|
currentErrors.editCantSalida = 'Cant. Salida debe ser un número >= 0.';
|
||||||
|
}
|
||||||
|
if (editCantEntrada.trim() === '' || isNaN(entradaNum) || entradaNum < 0) {
|
||||||
|
currentErrors.editCantEntrada = 'Cant. Entrada debe ser un número >= 0.';
|
||||||
|
} else if (!isNaN(salidaNum) && !isNaN(entradaNum) && entradaNum > salidaNum) {
|
||||||
|
currentErrors.editCantEntrada = 'Cant. Entrada no puede ser mayor a Cant. Salida.';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let hasValidItemWithQuantityOrPub = false;
|
||||||
|
const publicacionIdsEnLote = new Set<number>();
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
currentErrors.general = "Debe agregar al menos una publicación.";
|
||||||
|
}
|
||||||
|
|
||||||
|
items.forEach((item, index) => {
|
||||||
|
const salidaNum = parseInt(item.cantSalida, 10);
|
||||||
|
const entradaNum = parseInt(item.cantEntrada, 10);
|
||||||
|
const hasQuantity = !isNaN(salidaNum) && salidaNum >=0 && !isNaN(entradaNum) && entradaNum >=0 && (salidaNum > 0 || entradaNum > 0);
|
||||||
|
const hasObservation = item.observacion.trim() !== '';
|
||||||
|
|
||||||
|
if (item.idPublicacion === '') {
|
||||||
|
if (hasQuantity || hasObservation) {
|
||||||
|
currentErrors[`item_${item.id}_idPublicacion`] = `Pub. ${index + 1} obligatoria si hay datos.`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const pubIdNum = Number(item.idPublicacion);
|
||||||
|
if (publicacionIdsEnLote.has(pubIdNum)) {
|
||||||
|
currentErrors[`item_${item.id}_idPublicacion`] = `Pub. ${index + 1} duplicada.`;
|
||||||
|
} else {
|
||||||
|
publicacionIdsEnLote.add(pubIdNum);
|
||||||
|
}
|
||||||
|
if (item.cantSalida.trim() === '' || isNaN(salidaNum) || salidaNum < 0) {
|
||||||
|
currentErrors[`item_${item.id}_cantSalida`] = `Salida Pub. ${index + 1} inválida.`;
|
||||||
|
}
|
||||||
|
if (item.cantEntrada.trim() === '' || isNaN(entradaNum) || entradaNum < 0) {
|
||||||
|
currentErrors[`item_${item.id}_cantEntrada`] = `Entrada Pub. ${index + 1} inválida.`;
|
||||||
|
} else if (!isNaN(salidaNum) && !isNaN(entradaNum) && entradaNum > salidaNum) {
|
||||||
|
currentErrors[`item_${item.id}_cantEntrada`] = `Dev. Pub. ${index + 1} > Salida.`;
|
||||||
|
}
|
||||||
|
if (item.idPublicacion !== '') hasValidItemWithQuantityOrPub = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const allItemsAreEmptyAndNoPubSelected = items.every(
|
||||||
|
itm => itm.idPublicacion === '' &&
|
||||||
|
(itm.cantSalida.trim() === '' || parseInt(itm.cantSalida, 10) === 0) &&
|
||||||
|
(itm.cantEntrada.trim() === '' || parseInt(itm.cantEntrada, 10) === 0) &&
|
||||||
|
itm.observacion.trim() === ''
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isEditing && items.length > 0 && !hasValidItemWithQuantityOrPub && !allItemsAreEmptyAndNoPubSelected) {
|
||||||
|
currentErrors.general = "Debe seleccionar una publicación para los ítems con cantidades y/o observaciones.";
|
||||||
|
} else if (!isEditing && items.length > 0 && !items.some(i => i.idPublicacion !== '' && (parseInt(i.cantSalida,10) > 0 || parseInt(i.cantEntrada,10) > 0)) && !allItemsAreEmptyAndNoPubSelected) {
|
||||||
|
currentErrors.general = "Debe ingresar cantidades para al menos una publicación con datos significativos.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLocalErrors(currentErrors);
|
||||||
|
return Object.keys(currentErrors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (fieldName: string) => {
|
||||||
|
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||||
|
if (parentErrorMessage) clearErrorMessage();
|
||||||
|
if (modalSpecificApiError) setModalSpecificApiError(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
setModalSpecificApiError(null);
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
const salidaNum = parseInt(editCantSalida, 10);
|
||||||
|
const entradaNum = parseInt(editCantEntrada, 10);
|
||||||
|
const dataToSubmitSingle: UpdateEntradaSalidaCanillaDto = {
|
||||||
|
cantSalida: salidaNum,
|
||||||
|
cantEntrada: entradaNum,
|
||||||
|
observacion: editObservacion.trim() || undefined,
|
||||||
|
};
|
||||||
|
await onSubmit(dataToSubmitSingle, initialData.idParte);
|
||||||
|
} else {
|
||||||
|
const itemsToSubmit: EntradaSalidaCanillaItemDto[] = items
|
||||||
|
.filter(item =>
|
||||||
|
item.idPublicacion !== '' &&
|
||||||
|
( (parseInt(item.cantSalida, 10) >= 0 && parseInt(item.cantEntrada, 10) >= 0) && (parseInt(item.cantSalida,10) > 0 || parseInt(item.cantEntrada,10) > 0 ) || item.observacion.trim() !== '')
|
||||||
|
)
|
||||||
|
.map(item => ({
|
||||||
|
idPublicacion: Number(item.idPublicacion),
|
||||||
|
cantSalida: parseInt(item.cantSalida, 10) || 0,
|
||||||
|
cantEntrada: parseInt(item.cantEntrada, 10) || 0,
|
||||||
|
observacion: item.observacion.trim() || undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (itemsToSubmit.length === 0) {
|
||||||
|
setLocalErrors(prev => ({...prev, general: "No hay movimientos válidos para registrar. Asegúrese de seleccionar una publicación y/o ingresar cantidades."}));
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bulkData: CreateBulkEntradaSalidaCanillaDto = {
|
||||||
|
idCanilla: Number(idCanilla),
|
||||||
|
fecha,
|
||||||
|
items: itemsToSubmit,
|
||||||
|
};
|
||||||
|
await entradaSalidaCanillaService.createBulkEntradasSalidasCanilla(bulkData);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de EntradaSalidaCanillaFormModal:", error);
|
||||||
|
if (axios.isAxiosError(error) && error.response) {
|
||||||
|
setModalSpecificApiError(error.response.data?.message || 'Error al procesar la solicitud.');
|
||||||
|
} else {
|
||||||
|
setModalSpecificApiError('Ocurrió un error inesperado.');
|
||||||
|
}
|
||||||
|
if (isEditing) throw error;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddRow = () => {
|
||||||
|
if (items.length >= publicaciones.length) {
|
||||||
|
setLocalErrors(prev => ({ ...prev, general: "No se pueden agregar más filas que publicaciones disponibles." }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setItems([...items, { id: Date.now().toString(), idPublicacion: '', cantSalida: '0', cantEntrada: '0', observacion: '' }]);
|
||||||
|
setLocalErrors(prev => ({ ...prev, general: null }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveRow = (idToRemove: string) => {
|
||||||
|
if (items.length <= 1 && !isEditing) return;
|
||||||
|
setItems(items.filter(item => item.id !== idToRemove));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleItemChange = (id: string, field: keyof Omit<FormRowItem, 'id'>, value: string | number) => {
|
||||||
|
setItems(items.map(itemRow => itemRow.id === id ? { ...itemRow, [field]: value } : itemRow)); // CORREGIDO: item a itemRow para evitar conflicto de nombres de variable con el `item` del map en el JSX
|
||||||
|
if (localErrors[`item_${id}_${field}`]) { // Aquí item se refiere al id del item.
|
||||||
|
setLocalErrors(prev => ({ ...prev, [`item_${id}_${field}`]: null }));
|
||||||
|
}
|
||||||
|
if (localErrors.general) setLocalErrors(prev => ({ ...prev, general: null }));
|
||||||
|
if (parentErrorMessage) clearErrorMessage();
|
||||||
|
if (modalSpecificApiError) setModalSpecificApiError(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Movimiento Canillita' : 'Registrar Movimientos Canillita'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||||
|
<FormControl fullWidth margin="dense" error={!!localErrors.idCanilla} required>
|
||||||
|
<InputLabel id="canilla-esc-select-label">Canillita</InputLabel>
|
||||||
|
<Select labelId="canilla-esc-select-label" label="Canillita" value={idCanilla}
|
||||||
|
onChange={(e) => { setIdCanilla(e.target.value as number); handleInputChange('idCanilla'); }}
|
||||||
|
disabled={loading || loadingDropdowns || 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 && <FormHelperText>{localErrors.idCanilla}</FormHelperText>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField label="Fecha Movimientos" 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}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isEditing && initialData && (
|
||||||
|
<Paper elevation={1} sx={{ p: 1.5, mt: 1 }}>
|
||||||
|
<Typography variant="body2" gutterBottom color="text.secondary">Editando para Publicación: {publicaciones.find(p=>p.idPublicacion === editIdPublicacion)?.nombre || `ID ${editIdPublicacion}`}</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, mt:0.5}}>
|
||||||
|
<TextField label="Cant. Salida" type="number" value={editCantSalida}
|
||||||
|
onChange={(e) => {setEditCantSalida(e.target.value); handleInputChange('editCantSalida');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.editCantSalida} helperText={localErrors.editCantSalida || ''}
|
||||||
|
disabled={loading} inputProps={{ min: 0 }} sx={{flex:1}}
|
||||||
|
/>
|
||||||
|
<TextField label="Cant. Entrada" type="number" value={editCantEntrada}
|
||||||
|
onChange={(e) => {setEditCantEntrada(e.target.value); handleInputChange('editCantEntrada');}}
|
||||||
|
margin="dense" fullWidth error={!!localErrors.editCantEntrada} helperText={localErrors.editCantEntrada || ''}
|
||||||
|
disabled={loading} inputProps={{ min: 0 }} sx={{flex:1}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<TextField label="Observación (General)" value={editObservacion}
|
||||||
|
onChange={(e) => setEditObservacion(e.target.value)}
|
||||||
|
margin="dense" fullWidth multiline rows={2} disabled={loading} sx={{mt:1}}
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isEditing && (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle2" sx={{ mt: 1.5, mb: 0.5 }}>Movimientos por Publicación:</Typography>
|
||||||
|
{items.map((itemRow, index) => ( // item renombrado a itemRow
|
||||||
|
<Paper key={itemRow.id} elevation={1} sx={{ p: 1.5, mb: 1, position: 'relative' }}>
|
||||||
|
{items.length > 1 && (
|
||||||
|
<IconButton onClick={() => handleRemoveRow(itemRow.id)} color="error" size="small"
|
||||||
|
sx={{ position: 'absolute', top: 4, right: 4, zIndex:1 }}
|
||||||
|
aria-label="Quitar fila"
|
||||||
|
>
|
||||||
|
<DeleteIcon fontSize="inherit" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, flexWrap: 'wrap' }}>
|
||||||
|
<FormControl sx={{ minWidth: 160, flexBasis: 'calc(40% - 8px)', flexGrow:1 }} size="small" error={!!localErrors[`item_${itemRow.id}_idPublicacion`]}>
|
||||||
|
<InputLabel required={parseInt(itemRow.cantSalida) > 0 || parseInt(itemRow.cantEntrada) > 0 || itemRow.observacion.trim() !== ''}>Pub. {index + 1}</InputLabel>
|
||||||
|
<Select value={itemRow.idPublicacion} label={`Publicación ${index + 1}`}
|
||||||
|
onChange={(e) => handleItemChange(itemRow.id, 'idPublicacion', e.target.value as number)}
|
||||||
|
disabled={loading || loadingDropdowns}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||||
|
{publicaciones.map((p) => (
|
||||||
|
<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors[`item_${itemRow.id}_idPublicacion`] && <FormHelperText>{localErrors[`item_${itemRow.id}_idPublicacion`]}</FormHelperText>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField label="Llevados" type="number" size="small" value={itemRow.cantSalida}
|
||||||
|
onChange={(e) => handleItemChange(itemRow.id, 'cantSalida', e.target.value)}
|
||||||
|
error={!!localErrors[`item_${itemRow.id}_cantSalida`]} helperText={localErrors[`item_${itemRow.id}_cantSalida`]}
|
||||||
|
inputProps={{ min: 0 }} sx={{ width: 'auto', flexBasis: 'calc(15% - 8px)', minWidth: '80px' }}
|
||||||
|
/>
|
||||||
|
<TextField label="Devueltos" type="number" size="small" value={itemRow.cantEntrada}
|
||||||
|
onChange={(e) => handleItemChange(itemRow.id, 'cantEntrada', e.target.value)}
|
||||||
|
error={!!localErrors[`item_${itemRow.id}_cantEntrada`]} helperText={localErrors[`item_${itemRow.id}_cantEntrada`]}
|
||||||
|
inputProps={{ min: 0 }} sx={{ width: 'auto', flexBasis: 'calc(15% - 8px)', minWidth: '80px' }}
|
||||||
|
/>
|
||||||
|
<TextField label="Obs." value={itemRow.observacion}
|
||||||
|
onChange={(e) => handleItemChange(itemRow.id, 'observacion', e.target.value)}
|
||||||
|
size="small" sx={{ flexGrow: 1, flexBasis: 'calc(25% - 8px)', minWidth: '120px' }} multiline maxRows={1}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
{localErrors.general && <Alert severity="error" sx={{ mt: 1 }}>{localErrors.general}</Alert>}
|
||||||
|
<Button onClick={handleAddRow} startIcon={<AddIcon />} sx={{ mt: 1, alignSelf: 'flex-start' }} disabled={loading || loadingDropdowns || items.length >= publicaciones.length}>
|
||||||
|
Agregar Publicación
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{parentErrorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{parentErrorMessage}</Alert>}
|
||||||
|
{modalSpecificApiError && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{modalSpecificApiError}</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 Movimientos')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EntradaSalidaCanillaFormModal;
|
||||||
@@ -29,7 +29,7 @@ interface EntradaSalidaDistFormModalProps {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: (data: CreateEntradaSalidaDistDto | UpdateEntradaSalidaDistDto, idParte?: number) => Promise<void>;
|
onSubmit: (data: CreateEntradaSalidaDistDto | UpdateEntradaSalidaDistDto, idParte?: number) => Promise<void>;
|
||||||
initialData?: EntradaSalidaDistDto | null; // Para editar
|
initialData?: EntradaSalidaDistDto | null;
|
||||||
errorMessage?: string | null;
|
errorMessage?: string | null;
|
||||||
clearErrorMessage: () => void;
|
clearErrorMessage: () => void;
|
||||||
}
|
}
|
||||||
@@ -63,8 +63,8 @@ const EntradaSalidaDistFormModal: React.FC<EntradaSalidaDistFormModalProps> = ({
|
|||||||
setLoadingDropdowns(true);
|
setLoadingDropdowns(true);
|
||||||
try {
|
try {
|
||||||
const [pubsData, distData] = await Promise.all([
|
const [pubsData, distData] = await Promise.all([
|
||||||
publicacionService.getAllPublicaciones(undefined, undefined, true), // Solo habilitadas
|
publicacionService.getAllPublicaciones(undefined, undefined, true),
|
||||||
distribuidorService.getAllDistribuidores()
|
distribuidorService.getAllDistribuidores() // Asume que este servicio existe y funciona
|
||||||
]);
|
]);
|
||||||
setPublicaciones(pubsData);
|
setPublicaciones(pubsData);
|
||||||
setDistribuidores(distData);
|
setDistribuidores(distData);
|
||||||
@@ -100,7 +100,7 @@ const EntradaSalidaDistFormModal: React.FC<EntradaSalidaDistFormModalProps> = ({
|
|||||||
if (!cantidad.trim() || isNaN(parseInt(cantidad)) || parseInt(cantidad) <= 0) {
|
if (!cantidad.trim() || isNaN(parseInt(cantidad)) || parseInt(cantidad) <= 0) {
|
||||||
errors.cantidad = 'La cantidad debe ser un número positivo.';
|
errors.cantidad = 'La cantidad debe ser un número positivo.';
|
||||||
}
|
}
|
||||||
if (!isEditing && (!remito.trim() || isNaN(parseInt(remito)) || parseInt(remito) <= 0)) {
|
if (!remito.trim() || isNaN(parseInt(remito)) || parseInt(remito) <= 0) { // Remito obligatorio en creación y edición
|
||||||
errors.remito = 'El Nro. Remito es obligatorio y debe ser un número positivo.';
|
errors.remito = 'El Nro. Remito es obligatorio y debe ser un número positivo.';
|
||||||
}
|
}
|
||||||
setLocalErrors(errors);
|
setLocalErrors(errors);
|
||||||
@@ -123,6 +123,7 @@ const EntradaSalidaDistFormModal: React.FC<EntradaSalidaDistFormModalProps> = ({
|
|||||||
const dataToSubmit: UpdateEntradaSalidaDistDto = {
|
const dataToSubmit: UpdateEntradaSalidaDistDto = {
|
||||||
cantidad: parseInt(cantidad, 10),
|
cantidad: parseInt(cantidad, 10),
|
||||||
observacion: observacion || undefined,
|
observacion: observacion || undefined,
|
||||||
|
// Remito no se edita según el DTO de Update
|
||||||
};
|
};
|
||||||
await onSubmit(dataToSubmit, initialData.idParte);
|
await onSubmit(dataToSubmit, initialData.idParte);
|
||||||
} else {
|
} else {
|
||||||
@@ -184,15 +185,15 @@ const EntradaSalidaDistFormModal: React.FC<EntradaSalidaDistFormModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl component="fieldset" margin="dense" error={!!localErrors.tipoMovimiento} required>
|
<FormControl component="fieldset" margin="dense" error={!!localErrors.tipoMovimiento} required>
|
||||||
<Typography component="legend" variant="body2" sx={{mb:0.5}}>Tipo de Movimiento</Typography>
|
<Typography component="legend" variant="body2" sx={{mb:0.5, fontSize:'0.8rem'}}>Tipo Movimiento</Typography>
|
||||||
<RadioGroup row value={tipoMovimiento} onChange={(e) => {setTipoMovimiento(e.target.value as 'Salida' | 'Entrada'); handleInputChange('tipoMovimiento');}} >
|
<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="Salida" control={<Radio size="small"/>} label="Salida" disabled={loading || isEditing}/>
|
||||||
<FormControlLabel value="Entrada" control={<Radio size="small"/>} label="Entrada (de Distribuidor)" disabled={loading || isEditing}/>
|
<FormControlLabel value="Entrada" control={<Radio size="small"/>} label="Entrada" disabled={loading || isEditing}/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
{localErrors.tipoMovimiento && <Typography color="error" variant="caption">{localErrors.tipoMovimiento}</Typography>}
|
{localErrors.tipoMovimiento && <Typography color="error" variant="caption">{localErrors.tipoMovimiento}</Typography>}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<TextField label="Nro. Remito" type="number" value={remito} required={!isEditing}
|
<TextField label="Nro. Remito" type="number" value={remito} required
|
||||||
onChange={(e) => {setRemito(e.target.value); handleInputChange('remito');}}
|
onChange={(e) => {setRemito(e.target.value); handleInputChange('remito');}}
|
||||||
margin="dense" fullWidth error={!!localErrors.remito} helperText={localErrors.remito || ''}
|
margin="dense" fullWidth error={!!localErrors.remito} helperText={localErrors.remito || ''}
|
||||||
disabled={loading || isEditing} inputProps={{min:1}}
|
disabled={loading || isEditing} inputProps={{min:1}}
|
||||||
|
|||||||
212
Frontend/src/components/Modals/Radios/CancionFormModal.tsx
Normal file
212
Frontend/src/components/Modals/Radios/CancionFormModal.tsx
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { CancionDto } from '../../../models/dtos/Radios/CancionDto';
|
||||||
|
import type { CreateCancionDto } from '../../../models/dtos/Radios/CreateCancionDto';
|
||||||
|
import type { UpdateCancionDto } from '../../../models/dtos/Radios/UpdateCancionDto';
|
||||||
|
import type { RitmoDto } from '../../../models/dtos/Radios/RitmoDto'; // Para el dropdown de ritmos
|
||||||
|
import ritmoService from '../../../services/Radios/ritmoService'; // Para cargar ritmos
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo, pero más ancho y alto) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '95%', sm: '80%', md: '700px' }, // Más ancho
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 3,
|
||||||
|
maxHeight: '90vh', // Permitir scroll
|
||||||
|
overflowY: 'auto'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface CancionFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreateCancionDto | UpdateCancionDto, id?: number) => Promise<void>;
|
||||||
|
initialData?: CancionDto | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CancionFormState = Omit<CreateCancionDto, 'pista' | 'idRitmo'> & {
|
||||||
|
pista: string; // TextField siempre es string
|
||||||
|
idRitmo: number | string; // Select puede ser string vacío
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const CancionFormModal: React.FC<CancionFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const initialFormState: CancionFormState = {
|
||||||
|
tema: '', compositorAutor: '', interprete: '', sello: '', placa: '',
|
||||||
|
pista: '', introduccion: '', idRitmo: '', formato: '', album: ''
|
||||||
|
};
|
||||||
|
const [formState, setFormState] = useState<CancionFormState>(initialFormState);
|
||||||
|
const [ritmos, setRitmos] = useState<RitmoDto[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingRitmos, setLoadingRitmos] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchRitmos = async () => {
|
||||||
|
setLoadingRitmos(true);
|
||||||
|
try {
|
||||||
|
const data = await ritmoService.getAllRitmos();
|
||||||
|
setRitmos(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar ritmos", error);
|
||||||
|
setLocalErrors(prev => ({...prev, ritmos: 'Error al cargar ritmos.'}));
|
||||||
|
} finally {
|
||||||
|
setLoadingRitmos(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
fetchRitmos();
|
||||||
|
if (initialData) {
|
||||||
|
setFormState({
|
||||||
|
tema: initialData.tema || '',
|
||||||
|
compositorAutor: initialData.compositorAutor || '',
|
||||||
|
interprete: initialData.interprete || '',
|
||||||
|
sello: initialData.sello || '',
|
||||||
|
placa: initialData.placa || '',
|
||||||
|
pista: initialData.pista?.toString() || '',
|
||||||
|
introduccion: initialData.introduccion || '',
|
||||||
|
idRitmo: initialData.idRitmo || '',
|
||||||
|
formato: initialData.formato || '',
|
||||||
|
album: initialData.album || '',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setFormState(initialFormState);
|
||||||
|
}
|
||||||
|
setLocalErrors({});
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]); // No incluir initialFormState aquí directamente
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!formState.tema?.trim() && !formState.interprete?.trim()) { // Al menos tema o intérprete
|
||||||
|
errors.tema = 'Se requiere al menos el Tema o el Intérprete.';
|
||||||
|
errors.interprete = 'Se requiere al menos el Tema o el Intérprete.';
|
||||||
|
}
|
||||||
|
if (formState.pista.trim() && isNaN(parseInt(formState.pista))) {
|
||||||
|
errors.pista = 'Pista debe ser un número.';
|
||||||
|
}
|
||||||
|
// Otras validaciones específicas si son necesarias
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | (Event & { target: { name: string; value: unknown } })) => {
|
||||||
|
const { name, value } = event.target as { name: keyof CancionFormState, value: string };
|
||||||
|
setFormState(prev => ({ ...prev, [name]: value }));
|
||||||
|
if (localErrors[name]) {
|
||||||
|
setLocalErrors(prev => ({ ...prev, [name]: null }));
|
||||||
|
}
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectChange = (event: React.ChangeEvent<{ name?: string; value: unknown }>) => {
|
||||||
|
const name = event.target.name as keyof CancionFormState;
|
||||||
|
setFormState(prev => ({ ...prev, [name]: event.target.value as string | number }));
|
||||||
|
if (localErrors[name]) {
|
||||||
|
setLocalErrors(prev => ({ ...prev, [name]: null }));
|
||||||
|
}
|
||||||
|
if (errorMessage) clearErrorMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
clearErrorMessage();
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const dataToSubmit: CreateCancionDto | UpdateCancionDto = {
|
||||||
|
...formState,
|
||||||
|
pista: formState.pista.trim() ? parseInt(formState.pista, 10) : null,
|
||||||
|
idRitmo: formState.idRitmo ? Number(formState.idRitmo) : null,
|
||||||
|
// Convertir strings vacíos a undefined para que no se envíen si son opcionales en el backend
|
||||||
|
tema: formState.tema?.trim() || undefined,
|
||||||
|
compositorAutor: formState.compositorAutor?.trim() || undefined,
|
||||||
|
interprete: formState.interprete?.trim() || undefined,
|
||||||
|
sello: formState.sello?.trim() || undefined,
|
||||||
|
placa: formState.placa?.trim() || undefined,
|
||||||
|
introduccion: formState.introduccion?.trim() || undefined,
|
||||||
|
formato: formState.formato?.trim() || undefined,
|
||||||
|
album: formState.album?.trim() || undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
await onSubmit(dataToSubmit as UpdateCancionDto, initialData.id);
|
||||||
|
} else {
|
||||||
|
await onSubmit(dataToSubmit as CreateCancionDto);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de CancionFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Canción' : 'Agregar Nueva Canción'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||||
|
<TextField name="tema" label="Tema" value={formState.tema} onChange={handleChange} margin="dense" fullWidth error={!!localErrors.tema} helperText={localErrors.tema || ''} autoFocus />
|
||||||
|
<TextField name="interprete" label="Intérprete" value={formState.interprete} onChange={handleChange} margin="dense" fullWidth error={!!localErrors.interprete} helperText={localErrors.interprete || ''} />
|
||||||
|
<TextField name="compositorAutor" label="Compositor/Autor" value={formState.compositorAutor} onChange={handleChange} margin="dense" fullWidth />
|
||||||
|
<TextField name="album" label="Álbum" value={formState.album} onChange={handleChange} margin="dense" fullWidth />
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', mt:1 }}>
|
||||||
|
<TextField name="sello" label="Sello" value={formState.sello} onChange={handleChange} margin="dense" sx={{flex:1, minWidth: 'calc(50% - 8px)'}} />
|
||||||
|
<TextField name="placa" label="Placa" value={formState.placa} onChange={handleChange} margin="dense" sx={{flex:1, minWidth: 'calc(50% - 8px)'}} />
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', mt:1}}>
|
||||||
|
<TextField name="pista" label="N° Pista" type="number" value={formState.pista} onChange={handleChange} margin="dense" error={!!localErrors.pista} helperText={localErrors.pista || ''} sx={{flex:1, minWidth: 'calc(33% - 8px)'}}/>
|
||||||
|
<FormControl fullWidth margin="dense" sx={{flex:2, minWidth: 'calc(67% - 8px)'}} disabled={loadingRitmos}>
|
||||||
|
<InputLabel id="ritmo-cancion-select-label">Ritmo</InputLabel>
|
||||||
|
<Select name="idRitmo" labelId="ritmo-cancion-select-label" label="Ritmo" value={formState.idRitmo} onChange={handleSelectChange as any}>
|
||||||
|
<MenuItem value=""><em>Ninguno</em></MenuItem>
|
||||||
|
{ritmos.map((r) => (<MenuItem key={r.id} value={r.id}>{r.nombreRitmo}</MenuItem>))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
<TextField name="introduccion" label="Introducción (ej: (:10)/(3:30)/(F))" value={formState.introduccion} onChange={handleChange} margin="dense" fullWidth />
|
||||||
|
<TextField name="formato" label="Formato (ej: REC, ANG)" value={formState.formato} onChange={handleChange} margin="dense" fullWidth />
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||||
|
{localErrors.ritmos && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.ritmos}</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 || loadingRitmos}>
|
||||||
|
{loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Agregar Canción')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CancionFormModal;
|
||||||
111
Frontend/src/components/Modals/Radios/RitmoFormModal.tsx
Normal file
111
Frontend/src/components/Modals/Radios/RitmoFormModal.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Modal, Box, Typography, TextField, Button, CircularProgress, Alert
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { RitmoDto } from '../../../models/dtos/Radios/RitmoDto';
|
||||||
|
import type { CreateRitmoDto } from '../../../models/dtos/Radios/CreateRitmoDto';
|
||||||
|
import type { UpdateRitmoDto } from '../../../models/dtos/Radios/UpdateRitmoDto';
|
||||||
|
|
||||||
|
const modalStyle = { /* ... (mismo estilo) ... */
|
||||||
|
position: 'absolute' as 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: { xs: '90%', sm: 400 },
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
border: '2px solid #000',
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface RitmoFormModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: CreateRitmoDto | UpdateRitmoDto, id?: number) => Promise<void>;
|
||||||
|
initialData?: RitmoDto | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
clearErrorMessage: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RitmoFormModal: React.FC<RitmoFormModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
initialData,
|
||||||
|
errorMessage,
|
||||||
|
clearErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [nombreRitmo, setNombreRitmo] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [localErrorNombre, setLocalErrorNombre] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const isEditing = Boolean(initialData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setNombreRitmo(initialData?.nombreRitmo || '');
|
||||||
|
setLocalErrorNombre(null);
|
||||||
|
clearErrorMessage();
|
||||||
|
}
|
||||||
|
}, [open, initialData, clearErrorMessage]);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
if (!nombreRitmo.trim()) {
|
||||||
|
setLocalErrorNombre('El nombre del ritmo es obligatorio.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
const dataToSubmit = { nombreRitmo };
|
||||||
|
if (isEditing && initialData) {
|
||||||
|
await onSubmit(dataToSubmit as UpdateRitmoDto, initialData.id);
|
||||||
|
} else {
|
||||||
|
await onSubmit(dataToSubmit as CreateRitmoDto);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Error en submit de RitmoFormModal:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Box sx={modalStyle}>
|
||||||
|
<Typography variant="h6" component="h2" gutterBottom>
|
||||||
|
{isEditing ? 'Editar Ritmo' : 'Agregar Nuevo Ritmo'}
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<TextField label="Nombre del Ritmo" value={nombreRitmo} required
|
||||||
|
onChange={(e) => {setNombreRitmo(e.target.value); handleInputChange();}}
|
||||||
|
margin="dense" fullWidth error={!!localErrorNombre} helperText={localErrorNombre || ''}
|
||||||
|
disabled={loading} autoFocus
|
||||||
|
/>
|
||||||
|
{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 Ritmo')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RitmoFormModal;
|
||||||
10
Frontend/src/models/dtos/Contables/CreateNotaDto.ts
Normal file
10
Frontend/src/models/dtos/Contables/CreateNotaDto.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export interface CreateNotaDto {
|
||||||
|
destino: 'Distribuidores' | 'Canillas';
|
||||||
|
idDestino: number;
|
||||||
|
referencia?: string | null;
|
||||||
|
tipo: 'Debito' | 'Credito';
|
||||||
|
fecha: string; // "yyyy-MM-dd"
|
||||||
|
monto: number;
|
||||||
|
observaciones?: string | null;
|
||||||
|
idEmpresa: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface CreatePagoDistribuidorDto {
|
||||||
|
idDistribuidor: number;
|
||||||
|
fecha: string; // "yyyy-MM-dd"
|
||||||
|
tipoMovimiento: 'Recibido' | 'Realizado';
|
||||||
|
recibo: number;
|
||||||
|
monto: number;
|
||||||
|
idTipoPago: number;
|
||||||
|
detalle?: string | null;
|
||||||
|
idEmpresa: number;
|
||||||
|
}
|
||||||
13
Frontend/src/models/dtos/Contables/NotaCreditoDebitoDto.ts
Normal file
13
Frontend/src/models/dtos/Contables/NotaCreditoDebitoDto.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export interface NotaCreditoDebitoDto {
|
||||||
|
idNota: number;
|
||||||
|
destino: 'Distribuidores' | 'Canillas';
|
||||||
|
idDestino: number;
|
||||||
|
nombreDestinatario: string;
|
||||||
|
referencia?: string | null;
|
||||||
|
tipo: 'Debito' | 'Credito';
|
||||||
|
fecha: string; // "yyyy-MM-dd"
|
||||||
|
monto: number;
|
||||||
|
observaciones?: string | null;
|
||||||
|
idEmpresa: number;
|
||||||
|
nombreEmpresa: string;
|
||||||
|
}
|
||||||
14
Frontend/src/models/dtos/Contables/PagoDistribuidorDto.ts
Normal file
14
Frontend/src/models/dtos/Contables/PagoDistribuidorDto.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export interface PagoDistribuidorDto {
|
||||||
|
idPago: number;
|
||||||
|
idDistribuidor: number;
|
||||||
|
nombreDistribuidor: string;
|
||||||
|
fecha: string; // "yyyy-MM-dd"
|
||||||
|
tipoMovimiento: 'Recibido' | 'Realizado';
|
||||||
|
recibo: number;
|
||||||
|
monto: number;
|
||||||
|
idTipoPago: number;
|
||||||
|
nombreTipoPago: string;
|
||||||
|
detalle?: string | null;
|
||||||
|
idEmpresa: number;
|
||||||
|
nombreEmpresa: string;
|
||||||
|
}
|
||||||
4
Frontend/src/models/dtos/Contables/UpdateNotaDto.ts
Normal file
4
Frontend/src/models/dtos/Contables/UpdateNotaDto.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface UpdateNotaDto {
|
||||||
|
monto: number;
|
||||||
|
observaciones?: string | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface UpdatePagoDistribuidorDto {
|
||||||
|
monto: number;
|
||||||
|
idTipoPago: number;
|
||||||
|
detalle?: string | null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface ControlDevolucionesDto {
|
||||||
|
idControl: number;
|
||||||
|
idEmpresa: number;
|
||||||
|
nombreEmpresa: string;
|
||||||
|
fecha: string; // "yyyy-MM-dd"
|
||||||
|
entrada: number;
|
||||||
|
sobrantes: number;
|
||||||
|
detalle?: string | null;
|
||||||
|
sinCargo: number;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user