Refinamiento de permisos y ajustes en controles. Añade gestión sobre saldos y visualización. Entre otros..
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
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/saldos")]
|
||||
[ApiController]
|
||||
[Authorize] // Requiere autenticación para todos los endpoints
|
||||
public class SaldosController : ControllerBase
|
||||
{
|
||||
private readonly ISaldoService _saldoService;
|
||||
private readonly ILogger<SaldosController> _logger;
|
||||
|
||||
// Define un permiso específico para ver saldos, y otro para ajustarlos (SuperAdmin implícito)
|
||||
private const string PermisoVerSaldos = "CS001"; // Ejemplo: Cuentas Saldos Ver
|
||||
private const string PermisoAjustarSaldos = "CS002"; // Ejemplo: Cuentas Saldos Ajustar (o solo SuperAdmin)
|
||||
|
||||
|
||||
public SaldosController(ISaldoService saldoService, ILogger<SaldosController> logger)
|
||||
{
|
||||
_saldoService = saldoService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private bool TienePermiso(string codAccRequerido)
|
||||
{
|
||||
if (User.IsInRole("SuperAdmin")) return true;
|
||||
return User.HasClaim(c => c.Type == "permission" && c.Value == codAccRequerido);
|
||||
}
|
||||
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 SaldosController.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// GET: api/saldos
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<SaldoGestionDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<IActionResult> GetSaldosGestion(
|
||||
[FromQuery] string? destino,
|
||||
[FromQuery] int? idDestino,
|
||||
[FromQuery] int? idEmpresa)
|
||||
{
|
||||
if (!TienePermiso(PermisoVerSaldos)) // Usar el nuevo permiso
|
||||
{
|
||||
_logger.LogWarning("Acceso denegado a GetSaldosGestion para Usuario ID {userId}", GetCurrentUserId() ?? 0);
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var saldos = await _saldoService.ObtenerSaldosParaGestionAsync(destino, idDestino, idEmpresa);
|
||||
return Ok(saldos);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener saldos para gestión.");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener saldos.");
|
||||
}
|
||||
}
|
||||
|
||||
// POST: api/saldos/ajustar
|
||||
[HttpPost("ajustar")]
|
||||
[ProducesResponseType(typeof(SaldoGestionDto), StatusCodes.Status200OK)] // Devuelve el saldo actualizado
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)] // Solo SuperAdmin o con permiso específico
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> AjustarSaldoManualmente([FromBody] AjusteSaldoRequestDto ajusteDto)
|
||||
{
|
||||
// Esta operación debería ser MUY restringida. Solo SuperAdmin o un permiso muy específico.
|
||||
if (!User.IsInRole("SuperAdmin") && !TienePermiso(PermisoAjustarSaldos))
|
||||
{
|
||||
_logger.LogWarning("Intento no autorizado de ajustar saldo por Usuario ID {userId}", GetCurrentUserId() ?? 0);
|
||||
return Forbid("No tiene permisos para realizar ajustes manuales de saldo.");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||
|
||||
var idUsuario = GetCurrentUserId();
|
||||
if (idUsuario == null) return Unauthorized("No se pudo identificar al usuario.");
|
||||
|
||||
try
|
||||
{
|
||||
var (exito, error, saldoActualizado) = await _saldoService.RealizarAjusteManualSaldoAsync(ajusteDto, idUsuario.Value);
|
||||
if (!exito)
|
||||
{
|
||||
if (error != null && error.Contains("No se encontró un saldo existente"))
|
||||
return NotFound(new { message = error });
|
||||
return BadRequest(new { message = error ?? "Error desconocido al ajustar el saldo." });
|
||||
}
|
||||
return Ok(saldoActualizado);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error crítico al ajustar saldo manualmente.");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al procesar el ajuste de saldo.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,11 +47,11 @@ namespace GestionIntegral.Api.Controllers.Distribucion
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<CanillaDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<IActionResult> GetAllCanillas([FromQuery] string? nomApe, [FromQuery] int? legajo, [FromQuery] bool? soloActivos = true)
|
||||
public async Task<IActionResult> GetAllCanillas([FromQuery] string? nomApe, [FromQuery] int? legajo, [FromQuery] bool? esAccionista, [FromQuery] bool? soloActivos = true)
|
||||
{
|
||||
if (!TienePermiso(PermisoVer)) return Forbid();
|
||||
var canillas = await _canillaService.ObtenerTodosAsync(nomApe, legajo, soloActivos);
|
||||
return Ok(canillas);
|
||||
var canillitas = await _canillaService.ObtenerTodosAsync(nomApe, legajo, soloActivos, esAccionista); // <<-- Pasa el parámetro
|
||||
return Ok(canillitas);
|
||||
}
|
||||
|
||||
// GET: api/canillas/{id}
|
||||
@@ -117,7 +117,7 @@ namespace GestionIntegral.Api.Controllers.Distribucion
|
||||
public async Task<IActionResult> ToggleBajaCanilla(int id, [FromBody] ToggleBajaCanillaDto bajaDto)
|
||||
{
|
||||
if (!TienePermiso(PermisoBaja)) return Forbid();
|
||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||
var idUsuario = GetCurrentUserId();
|
||||
if (idUsuario == null) return Unauthorized();
|
||||
|
||||
|
||||
@@ -47,6 +47,15 @@ namespace GestionIntegral.Api.Controllers.Distribucion
|
||||
return Ok(distribuidores);
|
||||
}
|
||||
|
||||
[HttpGet("dropdown")]
|
||||
[ProducesResponseType(typeof(IEnumerable<DistribuidorDropdownDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<IActionResult> GetAllDropdownDistribuidores()
|
||||
{
|
||||
var distribuidores = await _distribuidorService.GetAllDropdownAsync();
|
||||
return Ok(distribuidores);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}", Name = "GetDistribuidorById")]
|
||||
[ProducesResponseType(typeof(DistribuidorDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
@@ -59,6 +68,17 @@ namespace GestionIntegral.Api.Controllers.Distribucion
|
||||
return Ok(distribuidor);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}/lookup", Name = "GetDistribuidorLookupById")]
|
||||
[ProducesResponseType(typeof(DistribuidorLookupDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> ObtenerLookupPorIdAsync(int id)
|
||||
{
|
||||
var distribuidor = await _distribuidorService.ObtenerLookupPorIdAsync(id);
|
||||
if (distribuidor == null) return NotFound();
|
||||
return Ok(distribuidor);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(DistribuidorDto), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
|
||||
@@ -74,6 +74,25 @@ namespace GestionIntegral.Api.Controllers // Ajusta el namespace si es necesario
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/empresas/dropdown
|
||||
[HttpGet("dropdown")]
|
||||
[ProducesResponseType(typeof(IEnumerable<EmpresaDropdownDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> GetEmpresasDropdown()
|
||||
{
|
||||
try
|
||||
{
|
||||
var empresas = await _empresaService.ObtenerParaDropdown();
|
||||
return Ok(empresas);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener todas las Empresas.");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener las empresas.");
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/empresas/{id}
|
||||
// Permiso Requerido: DE001 (Ver Empresas)
|
||||
[HttpGet("{id:int}", Name = "GetEmpresaById")]
|
||||
@@ -101,6 +120,29 @@ namespace GestionIntegral.Api.Controllers // Ajusta el namespace si es necesario
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}/lookup", Name = "GetEmpresaLookupById")]
|
||||
[ProducesResponseType(typeof(EmpresaDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> ObtenerLookupPorIdAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var empresa = await _empresaService.ObtenerLookupPorIdAsync(id);
|
||||
if (empresa == null)
|
||||
{
|
||||
return NotFound(new { message = $"Empresa con ID {id} no encontrada." });
|
||||
}
|
||||
return Ok(empresa);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener Empresa por ID: {Id}", id);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener la empresa.");
|
||||
}
|
||||
}
|
||||
|
||||
// POST: api/empresas
|
||||
// Permiso Requerido: DE002 (Agregar Empresas)
|
||||
[HttpPost]
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
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.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Controllers.Distribucion
|
||||
{
|
||||
[Route("api/novedadescanilla")] // Ruta base más genérica para las novedades
|
||||
[ApiController]
|
||||
[Authorize] // Todas las acciones requieren autenticación
|
||||
public class NovedadesCanillaController : ControllerBase
|
||||
{
|
||||
private readonly INovedadCanillaService _novedadService;
|
||||
private readonly ILogger<NovedadesCanillaController> _logger;
|
||||
|
||||
public NovedadesCanillaController(INovedadCanillaService novedadService, ILogger<NovedadesCanillaController> logger)
|
||||
{
|
||||
_novedadService = novedadService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// --- Helper para verificar permisos ---
|
||||
private bool TienePermiso(string codAccRequerido)
|
||||
{
|
||||
if (User.IsInRole("SuperAdmin")) return true;
|
||||
return User.HasClaim(c => c.Type == "permission" && c.Value == codAccRequerido);
|
||||
}
|
||||
|
||||
// --- Helper para obtener User ID ---
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub");
|
||||
if (int.TryParse(userIdClaim, out int userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
_logger.LogWarning("No se pudo obtener el UserId del token JWT en NovedadesCanillaController.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// GET: api/novedadescanilla/porcanilla/{idCanilla}
|
||||
// Obtiene todas las novedades para un canillita específico, opcionalmente filtrado por fecha.
|
||||
// Permiso: CG001 (Ver Canillas) o CG006 (Gestionar Novedades).
|
||||
// Si CG006 es "Permite la Carga/Modificación", entonces CG001 podría ser más apropiado solo para ver.
|
||||
// Vamos a usar CG001 para ver. Si se quiere más granularidad, se puede crear un permiso "Ver Novedades".
|
||||
[HttpGet("porcanilla/{idCanilla:int}")]
|
||||
[ProducesResponseType(typeof(IEnumerable<NovedadCanillaDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> GetNovedadesPorCanilla(int idCanilla, [FromQuery] DateTime? fechaDesde, [FromQuery] DateTime? fechaHasta)
|
||||
{
|
||||
if (!TienePermiso("CG001") && !TienePermiso("CG006")) // Necesita al menos uno de los dos
|
||||
{
|
||||
_logger.LogWarning("Acceso denegado a GetNovedadesPorCanilla para el usuario {UserId} y canillita {IdCanilla}", GetCurrentUserId() ?? 0, idCanilla);
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var novedades = await _novedadService.ObtenerPorCanillaAsync(idCanilla, fechaDesde, fechaHasta);
|
||||
return Ok(novedades);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener novedades para Canillita ID: {IdCanilla}", idCanilla);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener las novedades.");
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/novedadescanilla/{idNovedad}
|
||||
// Obtiene una novedad específica por su ID.
|
||||
// Permiso: CG001 o CG006
|
||||
[HttpGet("{idNovedad:int}", Name = "GetNovedadCanillaById")]
|
||||
[ProducesResponseType(typeof(NovedadCanillaDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> GetNovedadCanillaById(int idNovedad)
|
||||
{
|
||||
if (!TienePermiso("CG001") && !TienePermiso("CG006")) return Forbid();
|
||||
|
||||
try
|
||||
{
|
||||
var novedad = await _novedadService.ObtenerPorIdAsync(idNovedad);
|
||||
if (novedad == null)
|
||||
{
|
||||
return NotFound(new { message = $"Novedad con ID {idNovedad} no encontrada." });
|
||||
}
|
||||
return Ok(novedad);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener NovedadCanilla por ID: {IdNovedad}", idNovedad);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al obtener la novedad.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// POST: api/novedadescanilla
|
||||
// Crea una nueva novedad. El IdCanilla viene en el DTO.
|
||||
// Permiso: CG006 (Permite la Carga/Modificación de Novedades)
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(NovedadCanillaDto), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> CreateNovedadCanilla([FromBody] CreateNovedadCanillaDto createDto)
|
||||
{
|
||||
if (!TienePermiso("CG006")) return Forbid();
|
||||
|
||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||
|
||||
var idUsuario = GetCurrentUserId();
|
||||
if (idUsuario == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
|
||||
|
||||
try
|
||||
{
|
||||
var (novedadCreada, error) = await _novedadService.CrearAsync(createDto, idUsuario.Value);
|
||||
|
||||
if (error != null) return BadRequest(new { message = error });
|
||||
if (novedadCreada == null) return StatusCode(StatusCodes.Status500InternalServerError, "Error al crear la novedad.");
|
||||
|
||||
// Devuelve la ruta al recurso creado y el recurso mismo
|
||||
return CreatedAtRoute("GetNovedadCanillaById", new { idNovedad = novedadCreada.IdNovedad }, novedadCreada);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al crear NovedadCanilla para Canilla ID: {IdCanilla} por Usuario ID: {UsuarioId}", createDto.IdCanilla, idUsuario);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al crear la novedad.");
|
||||
}
|
||||
}
|
||||
|
||||
// PUT: api/novedadescanilla/{idNovedad}
|
||||
// Actualiza una novedad existente.
|
||||
// Permiso: CG006
|
||||
[HttpPut("{idNovedad:int}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> UpdateNovedadCanilla(int idNovedad, [FromBody] UpdateNovedadCanillaDto updateDto)
|
||||
{
|
||||
if (!TienePermiso("CG006")) return Forbid();
|
||||
|
||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||
|
||||
var idUsuario = GetCurrentUserId();
|
||||
if (idUsuario == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
|
||||
|
||||
try
|
||||
{
|
||||
var (exito, error) = await _novedadService.ActualizarAsync(idNovedad, updateDto, idUsuario.Value);
|
||||
|
||||
if (!exito)
|
||||
{
|
||||
if (error == "Novedad no encontrada.") return NotFound(new { message = error });
|
||||
return BadRequest(new { message = error });
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al actualizar NovedadCanilla ID: {IdNovedad} por Usuario ID: {UsuarioId}", idNovedad, idUsuario);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al actualizar la novedad.");
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE: api/novedadescanilla/{idNovedad}
|
||||
// Elimina una novedad.
|
||||
// Permiso: CG006 (Asumiendo que el mismo permiso para Carga/Modificación incluye eliminación)
|
||||
// Si la eliminación es un permiso separado (ej: CG00X), ajústalo.
|
||||
[HttpDelete("{idNovedad:int}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> DeleteNovedadCanilla(int idNovedad)
|
||||
{
|
||||
if (!TienePermiso("CG006")) return Forbid();
|
||||
|
||||
var idUsuario = GetCurrentUserId();
|
||||
if (idUsuario == null) return Unauthorized("No se pudo obtener el ID del usuario del token.");
|
||||
|
||||
try
|
||||
{
|
||||
var (exito, error) = await _novedadService.EliminarAsync(idNovedad, idUsuario.Value);
|
||||
|
||||
if (!exito)
|
||||
{
|
||||
if (error == "Novedad no encontrada.") return NotFound(new { message = error });
|
||||
return BadRequest(new { message = error }); // Podría ser otro error, como "no se pudo eliminar"
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al eliminar NovedadCanilla ID: {IdNovedad} por Usuario ID: {UsuarioId}", idNovedad, idUsuario);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al eliminar la novedad.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using GestionIntegral.Api.Data.Repositories.Impresion;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
using GestionIntegral.Api.Services.Distribucion;
|
||||
|
||||
namespace GestionIntegral.Api.Controllers
|
||||
{
|
||||
@@ -25,6 +26,7 @@ namespace GestionIntegral.Api.Controllers
|
||||
private readonly IPublicacionRepository _publicacionRepository;
|
||||
private readonly IEmpresaRepository _empresaRepository;
|
||||
private readonly IDistribuidorRepository _distribuidorRepository; // Para obtener el nombre del distribuidor
|
||||
private readonly INovedadCanillaService _novedadCanillaService;
|
||||
|
||||
|
||||
// Permisos
|
||||
@@ -36,22 +38,25 @@ namespace GestionIntegral.Api.Controllers
|
||||
private const string PermisoVerBalanceCuentas = "RR001";
|
||||
private const string PermisoVerReporteTiradas = "RR008";
|
||||
private const string PermisoVerReporteConsumoBobinas = "RR007";
|
||||
|
||||
private const string PermisoVerReporteNovedadesCanillas = "RR004";
|
||||
private const string PermisoVerReporteListadoDistMensual = "RR009";
|
||||
|
||||
public ReportesController(
|
||||
IReportesService reportesService, // <--- CORREGIDO
|
||||
IReportesService reportesService,
|
||||
INovedadCanillaService novedadCanillaService,
|
||||
ILogger<ReportesController> logger,
|
||||
IPlantaRepository plantaRepository,
|
||||
IPublicacionRepository publicacionRepository,
|
||||
IEmpresaRepository empresaRepository,
|
||||
IDistribuidorRepository distribuidorRepository) // Añadido
|
||||
IDistribuidorRepository distribuidorRepository)
|
||||
{
|
||||
_reportesService = reportesService; // <--- CORREGIDO
|
||||
_reportesService = reportesService;
|
||||
_novedadCanillaService = novedadCanillaService;
|
||||
_logger = logger;
|
||||
_plantaRepository = plantaRepository;
|
||||
_publicacionRepository = publicacionRepository;
|
||||
_empresaRepository = empresaRepository;
|
||||
_distribuidorRepository = distribuidorRepository; // Añadido
|
||||
_distribuidorRepository = distribuidorRepository;
|
||||
}
|
||||
|
||||
private bool TienePermiso(string codAcc) => User.IsInRole("SuperAdmin") || User.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
|
||||
@@ -1457,5 +1462,277 @@ namespace GestionIntegral.Api.Controllers
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Error interno al generar el PDF del ticket.");
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/reportes/novedades-canillas
|
||||
// Obtiene los datos para el reporte de novedades de canillitas
|
||||
[HttpGet("novedades-canillas")]
|
||||
[ProducesResponseType(typeof(IEnumerable<NovedadesCanillasReporteDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)] // Si no hay datos
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> GetReporteNovedadesCanillasData(
|
||||
[FromQuery] int idEmpresa,
|
||||
[FromQuery] DateTime fechaDesde,
|
||||
[FromQuery] DateTime fechaHasta)
|
||||
{
|
||||
if (!TienePermiso(PermisoVerReporteNovedadesCanillas))
|
||||
{
|
||||
_logger.LogWarning("Acceso denegado a GetReporteNovedadesCanillasData. Usuario: {User}", User.Identity?.Name ?? "Desconocido");
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
if (fechaDesde > fechaHasta)
|
||||
{
|
||||
return BadRequest(new { message = "La fecha 'desde' no puede ser posterior a la fecha 'hasta'." });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var reporteData = await _novedadCanillaService.ObtenerReporteNovedadesAsync(idEmpresa, fechaDesde, fechaHasta);
|
||||
if (reporteData == null || !reporteData.Any())
|
||||
{
|
||||
// Devolver Ok con array vacío en lugar de NotFound para que el frontend pueda manejarlo como "sin datos"
|
||||
return Ok(Enumerable.Empty<NovedadesCanillasReporteDto>());
|
||||
}
|
||||
return Ok(reporteData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al generar datos para el reporte de novedades de canillitas. Empresa: {IdEmpresa}, Desde: {FechaDesde}, Hasta: {FechaHasta}", idEmpresa, fechaDesde, fechaHasta);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Error interno al generar el reporte de novedades." });
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/reportes/novedades-canillas/pdf
|
||||
// Genera el PDF del reporte de novedades de canillitas
|
||||
[HttpGet("novedades-canillas/pdf")]
|
||||
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> GetReporteNovedadesCanillasPdf(
|
||||
[FromQuery] int idEmpresa,
|
||||
[FromQuery] DateTime fechaDesde,
|
||||
[FromQuery] DateTime fechaHasta)
|
||||
{
|
||||
if (!TienePermiso(PermisoVerReporteNovedadesCanillas)) // RR004
|
||||
{
|
||||
_logger.LogWarning("Acceso denegado a GetReporteNovedadesCanillasPdf. Usuario: {User}", User.Identity?.Name ?? "Desconocido");
|
||||
return Forbid();
|
||||
}
|
||||
if (fechaDesde > fechaHasta)
|
||||
{
|
||||
return BadRequest(new { message = "La fecha 'desde' no puede ser posterior a la fecha 'hasta'." });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Obtener datos para AMBOS datasets
|
||||
var novedadesData = await _novedadCanillaService.ObtenerReporteNovedadesAsync(idEmpresa, fechaDesde, fechaHasta);
|
||||
var gananciasData = await _novedadCanillaService.ObtenerReporteGananciasAsync(idEmpresa, fechaDesde, fechaHasta); // << OBTENER DATOS DE GANANCIAS
|
||||
|
||||
// Verificar si hay datos en *alguno* de los datasets necesarios para el reporte
|
||||
if ((novedadesData == null || !novedadesData.Any()) && (gananciasData == null || !gananciasData.Any()))
|
||||
{
|
||||
return NotFound(new { message = "No hay datos para generar el PDF con los parámetros seleccionados." });
|
||||
}
|
||||
|
||||
var empresa = await _empresaRepository.GetByIdAsync(idEmpresa);
|
||||
|
||||
LocalReport report = new LocalReport();
|
||||
string rdlcPath = Path.Combine("Controllers", "Reportes", "RDLC", "ReporteListadoNovedadesCanillas.rdlc");
|
||||
if (!System.IO.File.Exists(rdlcPath))
|
||||
{
|
||||
_logger.LogError("Archivo RDLC no encontrado en la ruta: {RdlcPath}", rdlcPath);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Archivo de definición de reporte no encontrado.");
|
||||
}
|
||||
|
||||
using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
report.LoadReportDefinition(fs);
|
||||
}
|
||||
|
||||
// Nombre del DataSet en RDLC para SP_DistCanillasNovedades (detalles)
|
||||
report.DataSources.Add(new ReportDataSource("DSNovedadesCanillasDetalles", novedadesData ?? new List<NovedadesCanillasReporteDto>()));
|
||||
|
||||
// Nombre del DataSet en RDLC para SP_DistCanillasGanancias (ganancias/resumen)
|
||||
report.DataSources.Add(new ReportDataSource("DSNovedadesCanillas", gananciasData ?? new List<CanillaGananciaReporteDto>()));
|
||||
|
||||
|
||||
var parameters = new List<ReportParameter>
|
||||
{
|
||||
new ReportParameter("NomEmp", empresa?.Nombre ?? "N/A"),
|
||||
new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")),
|
||||
new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy"))
|
||||
};
|
||||
report.SetParameters(parameters);
|
||||
|
||||
byte[] pdfBytes = report.Render("PDF");
|
||||
string fileName = $"ReporteNovedadesCanillas_Emp{idEmpresa}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf";
|
||||
return File(pdfBytes, "application/pdf", fileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al generar PDF para el reporte de novedades de canillitas. Empresa: {IdEmpresa}", idEmpresa);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = $"Error interno al generar el PDF: {ex.Message}" });
|
||||
}
|
||||
}
|
||||
// GET: api/reportes/novedades-canillas-ganancias
|
||||
[HttpGet("novedades-canillas-ganancias")]
|
||||
[ProducesResponseType(typeof(IEnumerable<CanillaGananciaReporteDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetReporteGananciasCanillasData(
|
||||
[FromQuery] int idEmpresa,
|
||||
[FromQuery] DateTime fechaDesde,
|
||||
[FromQuery] DateTime fechaHasta)
|
||||
{
|
||||
if (!TienePermiso(PermisoVerReporteNovedadesCanillas)) return Forbid(); // RR004
|
||||
|
||||
if (fechaDesde > fechaHasta)
|
||||
{
|
||||
return BadRequest(new { message = "La fecha 'desde' no puede ser posterior a la fecha 'hasta'." });
|
||||
}
|
||||
try
|
||||
{
|
||||
var gananciasData = await _novedadCanillaService.ObtenerReporteGananciasAsync(idEmpresa, fechaDesde, fechaHasta);
|
||||
if (gananciasData == null || !gananciasData.Any())
|
||||
{
|
||||
return Ok(Enumerable.Empty<CanillaGananciaReporteDto>());
|
||||
}
|
||||
return Ok(gananciasData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener datos de ganancias para el reporte de novedades. Empresa: {IdEmpresa}", idEmpresa);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Error interno al obtener datos de ganancias." });
|
||||
}
|
||||
}
|
||||
|
||||
// GET: api/reportes/listado-distribucion-mensual/diarios
|
||||
[HttpGet("listado-distribucion-mensual/diarios")]
|
||||
[ProducesResponseType(typeof(IEnumerable<ListadoDistCanMensualDiariosDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<IActionResult> GetListadoDistMensualDiarios(
|
||||
[FromQuery] DateTime fechaDesde,
|
||||
[FromQuery] DateTime fechaHasta,
|
||||
[FromQuery] bool esAccionista)
|
||||
{
|
||||
if (!TienePermiso(PermisoVerReporteListadoDistMensual)) return Forbid();
|
||||
if (fechaDesde > fechaHasta) return BadRequest(new { message = "Fecha Desde no puede ser mayor a Fecha Hasta." });
|
||||
|
||||
var (data, error) = await _reportesService.ObtenerReporteMensualDiariosAsync(fechaDesde, fechaHasta, esAccionista);
|
||||
if (error != null) return BadRequest(new { message = error });
|
||||
return Ok(data ?? Enumerable.Empty<ListadoDistCanMensualDiariosDto>());
|
||||
}
|
||||
|
||||
[HttpGet("listado-distribucion-mensual/diarios/pdf")]
|
||||
public async Task<IActionResult> GetListadoDistMensualDiariosPdf(
|
||||
[FromQuery] DateTime fechaDesde,
|
||||
[FromQuery] DateTime fechaHasta,
|
||||
[FromQuery] bool esAccionista)
|
||||
{
|
||||
if (!TienePermiso(PermisoVerReporteListadoDistMensual)) return Forbid();
|
||||
if (fechaDesde > fechaHasta) return BadRequest(new { message = "Fecha Desde no puede ser mayor a Fecha Hasta." });
|
||||
|
||||
var (data, error) = await _reportesService.ObtenerReporteMensualDiariosAsync(fechaDesde, fechaHasta, esAccionista);
|
||||
if (error != null) return BadRequest(new { message = error });
|
||||
if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el PDF." });
|
||||
|
||||
try
|
||||
{
|
||||
LocalReport report = new LocalReport();
|
||||
string rdlcPath = Path.Combine("Controllers", "Reportes", "RDLC", "ReporteListadoDistribucionCanMensualDiarios.rdlc");
|
||||
if (!System.IO.File.Exists(rdlcPath))
|
||||
{
|
||||
_logger.LogError("Archivo RDLC no encontrado: {Path}", rdlcPath);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, $"Archivo de reporte no encontrado: {Path.GetFileName(rdlcPath)}");
|
||||
}
|
||||
using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
report.LoadReportDefinition(fs);
|
||||
}
|
||||
report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCanMensualDiarios", data));
|
||||
|
||||
var parameters = new List<ReportParameter>
|
||||
{
|
||||
new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")),
|
||||
new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")),
|
||||
new ReportParameter("CanAcc", esAccionista ? "1" : "0") // El RDLC espera un Integer para CanAcc
|
||||
};
|
||||
report.SetParameters(parameters);
|
||||
|
||||
byte[] pdfBytes = report.Render("PDF");
|
||||
string tipoDesc = esAccionista ? "Accionistas" : "Canillitas";
|
||||
return File(pdfBytes, "application/pdf", $"ListadoDistMensualDiarios_{tipoDesc}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf");
|
||||
}
|
||||
catch (Exception ex) { _logger.LogError(ex, "Error PDF ListadoDistMensualDiarios"); return StatusCode(500, "Error interno."); }
|
||||
}
|
||||
|
||||
|
||||
// GET: api/reportes/listado-distribucion-mensual/publicaciones
|
||||
[HttpGet("listado-distribucion-mensual/publicaciones")]
|
||||
[ProducesResponseType(typeof(IEnumerable<ListadoDistCanMensualPubDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<IActionResult> GetListadoDistMensualPorPublicacion(
|
||||
[FromQuery] DateTime fechaDesde,
|
||||
[FromQuery] DateTime fechaHasta,
|
||||
[FromQuery] bool esAccionista)
|
||||
{
|
||||
if (!TienePermiso(PermisoVerReporteListadoDistMensual)) return Forbid();
|
||||
if (fechaDesde > fechaHasta) return BadRequest(new { message = "Fecha Desde no puede ser mayor a Fecha Hasta." });
|
||||
|
||||
var (data, error) = await _reportesService.ObtenerReporteMensualPorPublicacionAsync(fechaDesde, fechaHasta, esAccionista);
|
||||
if (error != null) return BadRequest(new { message = error });
|
||||
return Ok(data ?? Enumerable.Empty<ListadoDistCanMensualPubDto>());
|
||||
}
|
||||
|
||||
[HttpGet("listado-distribucion-mensual/publicaciones/pdf")]
|
||||
public async Task<IActionResult> GetListadoDistMensualPorPublicacionPdf(
|
||||
[FromQuery] DateTime fechaDesde,
|
||||
[FromQuery] DateTime fechaHasta,
|
||||
[FromQuery] bool esAccionista)
|
||||
{
|
||||
if (!TienePermiso(PermisoVerReporteListadoDistMensual)) return Forbid();
|
||||
if (fechaDesde > fechaHasta) return BadRequest(new { message = "Fecha Desde no puede ser mayor a Fecha Hasta." });
|
||||
|
||||
var (data, error) = await _reportesService.ObtenerReporteMensualPorPublicacionAsync(fechaDesde, fechaHasta, esAccionista);
|
||||
if (error != null) return BadRequest(new { message = error });
|
||||
if (data == null || !data.Any()) return NotFound(new { message = "No hay datos para generar el PDF." });
|
||||
|
||||
try
|
||||
{
|
||||
LocalReport report = new LocalReport();
|
||||
string rdlcPath = Path.Combine("Controllers", "Reportes", "RDLC", "ReporteListadoDistribucionCanMensual.rdlc");
|
||||
if (!System.IO.File.Exists(rdlcPath))
|
||||
{
|
||||
_logger.LogError("Archivo RDLC no encontrado: {Path}", rdlcPath);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, $"Archivo de reporte no encontrado: {Path.GetFileName(rdlcPath)}");
|
||||
}
|
||||
using (var fs = new FileStream(rdlcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
report.LoadReportDefinition(fs);
|
||||
}
|
||||
report.DataSources.Add(new ReportDataSource("DSListadoDistribucionCanMensual", data));
|
||||
|
||||
var parameters = new List<ReportParameter>
|
||||
{
|
||||
new ReportParameter("FechaDesde", fechaDesde.ToString("dd/MM/yyyy")),
|
||||
new ReportParameter("FechaHasta", fechaHasta.ToString("dd/MM/yyyy")),
|
||||
new ReportParameter("CanAcc", esAccionista ? "1" : "0")
|
||||
};
|
||||
report.SetParameters(parameters);
|
||||
|
||||
byte[] pdfBytes = report.Render("PDF");
|
||||
string tipoDesc = esAccionista ? "Accionistas" : "Canillitas";
|
||||
return File(pdfBytes, "application/pdf", $"ListadoDistMensualPub_{tipoDesc}_{fechaDesde:yyyyMMdd}_{fechaHasta:yyyyMMdd}.pdf");
|
||||
}
|
||||
catch (Exception ex) { _logger.LogError(ex, "Error PDF ListadoDistMensualPorPublicacion"); return StatusCode(500, "Error interno."); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic; // Para IEnumerable
|
||||
using System.Data;
|
||||
using GestionIntegral.Api.Dtos.Contables; // Para SaldoGestionDto si lo usas aquí
|
||||
using GestionIntegral.Api.Models.Contables; // Para Saldo, SaldoAjusteHistorial
|
||||
|
||||
namespace GestionIntegral.Api.Data.Repositories.Contables
|
||||
{
|
||||
@@ -15,5 +17,12 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
||||
// Método para modificar saldo (lo teníamos como privado antes, ahora en el repo)
|
||||
Task<bool> ModificarSaldoAsync(string destino, int idDestino, int idEmpresa, decimal montoAAgregar, IDbTransaction? transaction = null);
|
||||
Task<bool> CheckIfSaldosExistForEmpresaAsync(int id);
|
||||
|
||||
// Para obtener la lista de saldos para la página de gestión
|
||||
Task<IEnumerable<Saldo>> GetSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter);
|
||||
// Para obtener un saldo específico (ya podría existir uno similar, o crearlo si es necesario)
|
||||
Task<Saldo?> GetSaldoAsync(string destino, int idDestino, int idEmpresa, IDbTransaction? transaction = null);
|
||||
// Para registrar el historial de ajuste
|
||||
Task CreateSaldoAjusteHistorialAsync(SaldoAjusteHistorial historialEntry, IDbTransaction transaction);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using Dapper;
|
||||
using GestionIntegral.Api.Data.Repositories;
|
||||
using GestionIntegral.Api.Data.Repositories;
|
||||
using GestionIntegral.Api.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Threading.Tasks;
|
||||
using GestionIntegral.Api.Models.Contables;
|
||||
using System.Text;
|
||||
|
||||
namespace GestionIntegral.Api.Data.Repositories.Contables
|
||||
{
|
||||
@@ -57,61 +59,64 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
||||
|
||||
public async Task<bool> DeleteSaldosByEmpresaAsync(int idEmpresa, IDbTransaction transaction)
|
||||
{
|
||||
var sql = "DELETE FROM dbo.cue_Saldos WHERE Id_Empresa = @IdEmpresa";
|
||||
try
|
||||
{
|
||||
await transaction.Connection!.ExecuteAsync(sql, new { IdEmpresa = idEmpresa }, transaction: transaction);
|
||||
return true; // Asumir éxito si no hay excepción
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al eliminar saldos para Empresa ID {IdEmpresa}.", idEmpresa);
|
||||
throw;
|
||||
}
|
||||
var sql = "DELETE FROM dbo.cue_Saldos WHERE Id_Empresa = @IdEmpresa";
|
||||
try
|
||||
{
|
||||
await transaction.Connection!.ExecuteAsync(sql, new { IdEmpresa = idEmpresa }, transaction: transaction);
|
||||
return true; // Asumir éxito si no hay excepción
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al eliminar saldos para Empresa ID {IdEmpresa}.", idEmpresa);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ModificarSaldoAsync(string destino, int idDestino, int idEmpresa, decimal montoAAgregar, IDbTransaction? transaction = null)
|
||||
{
|
||||
var sql = @"UPDATE dbo.cue_Saldos
|
||||
SET Monto = Monto + @MontoAAgregar
|
||||
WHERE Destino = @Destino AND Id_Destino = @IdDestino AND Id_Empresa = @IdEmpresa;";
|
||||
{
|
||||
var sql = @"UPDATE dbo.cue_Saldos
|
||||
SET Monto = Monto + @MontoAAgregar,
|
||||
FechaUltimaModificacion = @FechaActualizacion -- << AÑADIR
|
||||
WHERE Destino = @Destino AND Id_Destino = @IdDestino AND Id_Empresa = @IdEmpresa;";
|
||||
|
||||
// Usar una variable para la conexión para poder aplicar el '!' si es necesario
|
||||
IDbConnection connection = transaction?.Connection ?? _connectionFactory.CreateConnection();
|
||||
bool ownConnection = transaction == null; // Saber si necesitamos cerrar la conexión nosotros
|
||||
IDbConnection connection = transaction?.Connection ?? _connectionFactory.CreateConnection();
|
||||
bool ownConnection = transaction == null;
|
||||
|
||||
try
|
||||
{
|
||||
if (ownConnection) await (connection as System.Data.Common.DbConnection)!.OpenAsync(); // Abrir solo si no hay transacción externa
|
||||
try
|
||||
{
|
||||
if (ownConnection && connection.State != ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
}
|
||||
|
||||
var parameters = new {
|
||||
MontoAAgregar = montoAAgregar,
|
||||
Destino = destino,
|
||||
IdDestino = idDestino,
|
||||
IdEmpresa = idEmpresa
|
||||
};
|
||||
// Aplicar '!' aquí también si viene de la transacción
|
||||
int rowsAffected = await connection.ExecuteAsync(sql, parameters, transaction: transaction);
|
||||
return rowsAffected == 1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al modificar saldo para {Destino} ID {IdDestino}, Empresa ID {IdEmpresa}.", destino, idDestino, idEmpresa);
|
||||
if (transaction != null) throw; // Re-lanzar si estamos en una transacción externa
|
||||
return false; // Devolver false si fue una operación aislada que falló
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cerrar la conexión solo si la abrimos nosotros (no había transacción externa)
|
||||
if (ownConnection && connection.State == ConnectionState.Open)
|
||||
{
|
||||
await (connection as System.Data.Common.DbConnection)!.CloseAsync();
|
||||
}
|
||||
// Disponer de la conexión si la creamos nosotros
|
||||
if(ownConnection) (connection as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
public async Task<bool> CheckIfSaldosExistForEmpresaAsync(int idEmpresa)
|
||||
var parameters = new
|
||||
{
|
||||
MontoAAgregar = montoAAgregar,
|
||||
Destino = destino,
|
||||
IdDestino = idDestino,
|
||||
IdEmpresa = idEmpresa,
|
||||
FechaActualizacion = DateTime.Now // O DateTime.UtcNow si prefieres
|
||||
};
|
||||
int rowsAffected = await connection.ExecuteAsync(sql, parameters, transaction: transaction);
|
||||
return rowsAffected == 1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al modificar saldo para {Destino} ID {IdDestino}, Empresa ID {IdEmpresa}.", destino, idDestino, idEmpresa);
|
||||
if (transaction != null) throw;
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ownConnection && connection.State == ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
|
||||
}
|
||||
if (ownConnection && connection is IDisposable d) d.Dispose(); // Mejorar dispose
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> CheckIfSaldosExistForEmpresaAsync(int idEmpresa)
|
||||
{
|
||||
var sql = "SELECT COUNT(1) FROM dbo.cue_Saldos WHERE Id_Empresa = @IdEmpresa";
|
||||
try
|
||||
@@ -130,5 +135,58 @@ namespace GestionIntegral.Api.Data.Repositories.Contables
|
||||
// O podrías devolver true para ser más conservador si la verificación es crítica.
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Saldo>> GetSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter)
|
||||
{
|
||||
var sqlBuilder = new StringBuilder("SELECT Id_Saldo AS IdSaldo, Destino, Id_Destino AS IdDestino, Monto, Id_Empresa AS IdEmpresa, FechaUltimaModificacion FROM dbo.cue_Saldos WHERE 1=1");
|
||||
var parameters = new DynamicParameters();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(destinoFilter))
|
||||
{
|
||||
sqlBuilder.Append(" AND Destino = @Destino");
|
||||
parameters.Add("Destino", destinoFilter);
|
||||
}
|
||||
if (idDestinoFilter.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(" AND Id_Destino = @IdDestino");
|
||||
parameters.Add("IdDestino", idDestinoFilter.Value);
|
||||
}
|
||||
if (idEmpresaFilter.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(" AND Id_Empresa = @IdEmpresa");
|
||||
parameters.Add("IdEmpresa", idEmpresaFilter.Value);
|
||||
}
|
||||
sqlBuilder.Append(" ORDER BY Destino, Id_Empresa, Id_Destino;");
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
return await connection.QueryAsync<Saldo>(sqlBuilder.ToString(), parameters);
|
||||
}
|
||||
|
||||
public async Task<Saldo?> GetSaldoAsync(string destino, int idDestino, int idEmpresa, IDbTransaction? transaction = null)
|
||||
{
|
||||
const string sql = "SELECT Id_Saldo AS IdSaldo, Destino, Id_Destino AS IdDestino, Monto, Id_Empresa AS IdEmpresa, FechaUltimaModificacion FROM dbo.cue_Saldos WHERE Destino = @Destino AND Id_Destino = @IdDestino AND Id_Empresa = @IdEmpresa;";
|
||||
var conn = transaction?.Connection ?? _connectionFactory.CreateConnection();
|
||||
if (transaction == null && conn.State != ConnectionState.Open) { if (conn is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else conn.Open(); }
|
||||
|
||||
try
|
||||
{
|
||||
return await conn.QuerySingleOrDefaultAsync<Saldo>(sql, new { Destino = destino, IdDestino = idDestino, IdEmpresa = idEmpresa }, transaction);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (transaction == null && conn.State == ConnectionState.Open) { if (conn is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else conn.Close(); }
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateSaldoAjusteHistorialAsync(SaldoAjusteHistorial historialEntry, IDbTransaction transaction)
|
||||
{
|
||||
const string sql = @"
|
||||
INSERT INTO dbo.cue_SaldoAjustesHistorial
|
||||
(Destino, Id_Destino, Id_Empresa, MontoAjuste, SaldoAnterior, SaldoNuevo, Justificacion, FechaAjuste, Id_UsuarioAjuste)
|
||||
VALUES
|
||||
(@Destino, @IdDestino, @IdEmpresa, @MontoAjuste, @SaldoAnterior, @SaldoNuevo, @Justificacion, @FechaAjuste, @IdUsuarioAjuste);";
|
||||
|
||||
await transaction.Connection!.ExecuteAsync(sql, historialEntry, transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,51 +21,56 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<(Canilla Canilla, string NombreZona, string NombreEmpresa)>> GetAllAsync(string? nomApeFilter, int? legajoFilter, bool? soloActivos)
|
||||
public async Task<IEnumerable<(Canilla Canilla, string? NombreZona, string? NombreEmpresa)>> GetAllAsync(
|
||||
string? nomApeFilter,
|
||||
int? legajoFilter,
|
||||
bool? esAccionista,
|
||||
bool? soloActivos) // <<-- Parámetro aquí
|
||||
{
|
||||
var sqlBuilder = new StringBuilder(@"
|
||||
SELECT
|
||||
c.Id_Canilla AS IdCanilla, c.Legajo, c.NomApe, c.Parada, c.Id_Zona AS IdZona,
|
||||
c.Accionista, c.Obs, c.Empresa, c.Baja, c.FechaBaja,
|
||||
z.Nombre AS NombreZona,
|
||||
ISNULL(e.Nombre, 'N/A (Accionista)') AS NombreEmpresa
|
||||
FROM dbo.dist_dtCanillas c
|
||||
INNER JOIN dbo.dist_dtZonas z ON c.Id_Zona = z.Id_Zona
|
||||
LEFT JOIN dbo.dist_dtEmpresas e ON c.Empresa = e.Id_Empresa
|
||||
WHERE 1=1");
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
var sqlBuilder = new System.Text.StringBuilder(@"
|
||||
SELECT c.Id_Canilla AS IdCanilla, c.Legajo, c.NomApe, c.Parada, c.Id_Zona AS IdZona,
|
||||
c.Accionista, c.Obs, c.Empresa, c.Baja, c.FechaBaja,
|
||||
z.Nombre AS NombreZona,
|
||||
e.Nombre AS NombreEmpresa
|
||||
FROM dbo.dist_dtCanillas c
|
||||
LEFT JOIN dbo.dist_dtZonas z ON c.Id_Zona = z.Id_Zona
|
||||
LEFT JOIN dbo.dist_dtEmpresas e ON c.Empresa = e.Id_Empresa
|
||||
WHERE 1=1 "); // Cláusula base para añadir AND fácilmente
|
||||
|
||||
var parameters = new DynamicParameters();
|
||||
|
||||
if (soloActivos.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(soloActivos.Value ? " AND c.Baja = 0" : " AND c.Baja = 1");
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(nomApeFilter))
|
||||
{
|
||||
sqlBuilder.Append(" AND c.NomApe LIKE @NomApeParam");
|
||||
parameters.Add("NomApeParam", $"%{nomApeFilter}%");
|
||||
sqlBuilder.Append(" AND c.NomApe LIKE @NomApeFilter ");
|
||||
parameters.Add("NomApeFilter", $"%{nomApeFilter}%");
|
||||
}
|
||||
if (legajoFilter.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(" AND c.Legajo = @LegajoParam");
|
||||
parameters.Add("LegajoParam", legajoFilter.Value);
|
||||
sqlBuilder.Append(" AND c.Legajo = @LegajoFilter ");
|
||||
parameters.Add("LegajoFilter", legajoFilter.Value);
|
||||
}
|
||||
if (soloActivos.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(" AND c.Baja = @BajaStatus ");
|
||||
parameters.Add("BajaStatus", !soloActivos.Value); // Si soloActivos es true, Baja debe ser false
|
||||
}
|
||||
|
||||
if (esAccionista.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(" AND c.Accionista = @EsAccionista ");
|
||||
parameters.Add("EsAccionista", esAccionista.Value); // true para accionistas, false para no accionistas (canillitas)
|
||||
}
|
||||
|
||||
sqlBuilder.Append(" ORDER BY c.NomApe;");
|
||||
|
||||
try
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
return await connection.QueryAsync<Canilla, string, string, (Canilla, string, string)>(
|
||||
sqlBuilder.ToString(),
|
||||
(canilla, nombreZona, nombreEmpresa) => (canilla, nombreZona, nombreEmpresa),
|
||||
parameters,
|
||||
splitOn: "NombreZona,NombreEmpresa"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener todos los Canillas. Filtros: NomApe='{NomApeFilter}', Legajo='{LegajoFilter}', SoloActivos='{SoloActivos}'", nomApeFilter, legajoFilter, soloActivos);
|
||||
return Enumerable.Empty<(Canilla, string, string)>();
|
||||
}
|
||||
var result = await connection.QueryAsync<Canilla, string, string, (Canilla, string?, string?)>(
|
||||
sqlBuilder.ToString(),
|
||||
(can, zona, emp) => (can, zona, emp),
|
||||
parameters,
|
||||
splitOn: "NombreZona,NombreEmpresa"
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<(Canilla? Canilla, string? NombreZona, string? NombreEmpresa)> GetByIdAsync(int id)
|
||||
@@ -83,12 +88,12 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
try
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
var result = await connection.QueryAsync<Canilla, string, string, (Canilla?, string?, string?)>(
|
||||
sql,
|
||||
(canilla, nombreZona, nombreEmpresa) => (canilla, nombreZona, nombreEmpresa),
|
||||
new { IdParam = id },
|
||||
splitOn: "NombreZona,NombreEmpresa"
|
||||
);
|
||||
var result = await connection.QueryAsync<Canilla, string, string, (Canilla?, string?, string?)>(
|
||||
sql,
|
||||
(canilla, nombreZona, nombreEmpresa) => (canilla, nombreZona, nombreEmpresa),
|
||||
new { IdParam = id },
|
||||
splitOn: "NombreZona,NombreEmpresa"
|
||||
);
|
||||
return result.SingleOrDefault();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -160,9 +165,19 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
|
||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||
{
|
||||
IdCanillaParam = insertedCanilla.IdCanilla, LegajoParam = insertedCanilla.Legajo, NomApeParam = insertedCanilla.NomApe, ParadaParam = insertedCanilla.Parada, IdZonaParam = insertedCanilla.IdZona,
|
||||
AccionistaParam = insertedCanilla.Accionista, ObsParam = insertedCanilla.Obs, EmpresaParam = insertedCanilla.Empresa, BajaParam = insertedCanilla.Baja, FechaBajaParam = insertedCanilla.FechaBaja,
|
||||
Id_UsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Creado"
|
||||
IdCanillaParam = insertedCanilla.IdCanilla,
|
||||
LegajoParam = insertedCanilla.Legajo,
|
||||
NomApeParam = insertedCanilla.NomApe,
|
||||
ParadaParam = insertedCanilla.Parada,
|
||||
IdZonaParam = insertedCanilla.IdZona,
|
||||
AccionistaParam = insertedCanilla.Accionista,
|
||||
ObsParam = insertedCanilla.Obs,
|
||||
EmpresaParam = insertedCanilla.Empresa,
|
||||
BajaParam = insertedCanilla.Baja,
|
||||
FechaBajaParam = insertedCanilla.FechaBaja,
|
||||
Id_UsuarioParam = idUsuario,
|
||||
FechaModParam = DateTime.Now,
|
||||
TipoModParam = "Creado"
|
||||
}, transaction);
|
||||
return insertedCanilla;
|
||||
}
|
||||
@@ -173,7 +188,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
var canillaActual = await connection.QuerySingleOrDefaultAsync<Canilla>(
|
||||
@"SELECT Id_Canilla AS IdCanilla, Legajo, NomApe, Parada, Id_Zona AS IdZona,
|
||||
Accionista, Obs, Empresa, Baja, FechaBaja
|
||||
FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanillaParam",
|
||||
FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanillaParam",
|
||||
new { IdCanillaParam = canillaAActualizar.IdCanilla }, transaction);
|
||||
if (canillaActual == null) throw new KeyNotFoundException("Canilla no encontrado para actualizar.");
|
||||
|
||||
@@ -187,13 +202,21 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
(Id_Canilla, Legajo, NomApe, Parada, Id_Zona, Accionista, Obs, Empresa, Baja, FechaBaja, Id_Usuario, FechaMod, TipoMod)
|
||||
VALUES (@IdCanillaParam, @LegajoParam, @NomApeParam, @ParadaParam, @IdZonaParam, @AccionistaParam, @ObsParam, @EmpresaParam, @BajaParam, @FechaBajaParam, @Id_UsuarioParam, @FechaModParam, @TipoModParam);";
|
||||
|
||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||
{
|
||||
IdCanillaParam = canillaActual.IdCanilla,
|
||||
LegajoParam = canillaActual.Legajo, NomApeParam = canillaActual.NomApe, ParadaParam = canillaActual.Parada, IdZonaParam = canillaActual.IdZona,
|
||||
AccionistaParam = canillaActual.Accionista, ObsParam = canillaActual.Obs, EmpresaParam = canillaActual.Empresa,
|
||||
BajaParam = canillaActual.Baja, FechaBajaParam = canillaActual.FechaBaja,
|
||||
Id_UsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModParam = "Actualizado"
|
||||
LegajoParam = canillaActual.Legajo,
|
||||
NomApeParam = canillaActual.NomApe,
|
||||
ParadaParam = canillaActual.Parada,
|
||||
IdZonaParam = canillaActual.IdZona,
|
||||
AccionistaParam = canillaActual.Accionista,
|
||||
ObsParam = canillaActual.Obs,
|
||||
EmpresaParam = canillaActual.Empresa,
|
||||
BajaParam = canillaActual.Baja,
|
||||
FechaBajaParam = canillaActual.FechaBaja,
|
||||
Id_UsuarioParam = idUsuario,
|
||||
FechaModParam = DateTime.Now,
|
||||
TipoModParam = "Actualizado"
|
||||
}, transaction);
|
||||
|
||||
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, canillaAActualizar, transaction);
|
||||
@@ -206,7 +229,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
var canillaActual = await connection.QuerySingleOrDefaultAsync<Canilla>(
|
||||
@"SELECT Id_Canilla AS IdCanilla, Legajo, NomApe, Parada, Id_Zona AS IdZona,
|
||||
Accionista, Obs, Empresa, Baja, FechaBaja
|
||||
FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanillaParam",
|
||||
FROM dbo.dist_dtCanillas WHERE Id_Canilla = @IdCanillaParam",
|
||||
new { IdCanillaParam = id }, transaction);
|
||||
if (canillaActual == null) throw new KeyNotFoundException("Canilla no encontrado para dar de baja/alta.");
|
||||
|
||||
@@ -218,10 +241,19 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
|
||||
await connection.ExecuteAsync(sqlInsertHistorico, new
|
||||
{
|
||||
IdCanillaParam = canillaActual.IdCanilla, LegajoParam = canillaActual.Legajo, NomApeParam = canillaActual.NomApe, ParadaParam = canillaActual.Parada, IdZonaParam = canillaActual.IdZona,
|
||||
AccionistaParam = canillaActual.Accionista, ObsParam = canillaActual.Obs, EmpresaParam = canillaActual.Empresa,
|
||||
BajaNuevaParam = darDeBaja, FechaBajaNuevaParam = (darDeBaja ? fechaBaja : null),
|
||||
Id_UsuarioParam = idUsuario, FechaModParam = DateTime.Now, TipoModHistParam = (darDeBaja ? "Baja" : "Alta")
|
||||
IdCanillaParam = canillaActual.IdCanilla,
|
||||
LegajoParam = canillaActual.Legajo,
|
||||
NomApeParam = canillaActual.NomApe,
|
||||
ParadaParam = canillaActual.Parada,
|
||||
IdZonaParam = canillaActual.IdZona,
|
||||
AccionistaParam = canillaActual.Accionista,
|
||||
ObsParam = canillaActual.Obs,
|
||||
EmpresaParam = canillaActual.Empresa,
|
||||
BajaNuevaParam = darDeBaja,
|
||||
FechaBajaNuevaParam = (darDeBaja ? fechaBaja : null),
|
||||
Id_UsuarioParam = idUsuario,
|
||||
FechaModParam = DateTime.Now,
|
||||
TipoModHistParam = (darDeBaja ? "Baja" : "Alta")
|
||||
}, transaction);
|
||||
|
||||
var rowsAffected = await connection.ExecuteAsync(sqlUpdate, new { BajaParam = darDeBaja, FechaBajaParam = (darDeBaja ? fechaBaja : null), IdCanillaParam = id }, transaction);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Dapper;
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
using GestionIntegral.Api.Models.Distribucion;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System; // Añadido para Exception
|
||||
@@ -61,6 +62,30 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
return Enumerable.Empty<(Distribuidor, string?)>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DistribuidorDropdownDto?>> GetAllDropdownAsync()
|
||||
{
|
||||
var sqlBuilder = new StringBuilder(@"
|
||||
SELECT
|
||||
Id_Distribuidor AS IdDistribuidor, Nombre
|
||||
FROM dbo.dist_dtDistribuidores
|
||||
WHERE 1=1");
|
||||
var parameters = new DynamicParameters();
|
||||
sqlBuilder.Append(" ORDER BY Nombre;");
|
||||
try
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
return await connection.QueryAsync<DistribuidorDropdownDto>(
|
||||
sqlBuilder.ToString(),
|
||||
parameters
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener todos los Distribuidores.");
|
||||
return Enumerable.Empty<DistribuidorDropdownDto>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(Distribuidor? Distribuidor, string? NombreZona)> GetByIdAsync(int id)
|
||||
{
|
||||
@@ -90,6 +115,25 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id)
|
||||
{
|
||||
const string sql = @"
|
||||
SELECT
|
||||
Id_Distribuidor AS IdDistribuidor, Nombre
|
||||
FROM dbo.dist_dtDistribuidores
|
||||
WHERE Id_Distribuidor = @IdParam";
|
||||
try
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
return await connection.QuerySingleOrDefaultAsync<DistribuidorLookupDto>(sql, new { IdParam = id });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener Distribuidor por ID: {IdDistribuidor}", id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Distribuidor?> GetByIdSimpleAsync(int id)
|
||||
{
|
||||
const string sql = @"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Dapper;
|
||||
using GestionIntegral.Api.Data.Repositories;
|
||||
using GestionIntegral.Api.Dtos.Empresas;
|
||||
using GestionIntegral.Api.Models.Distribucion;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
@@ -52,6 +53,25 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EmpresaDropdownDto>> GetAllDropdownAsync()
|
||||
{
|
||||
var sqlBuilder = new StringBuilder("SELECT Id_Empresa AS IdEmpresa, Nombre FROM dbo.dist_dtEmpresas WHERE 1=1");
|
||||
var parameters = new DynamicParameters();
|
||||
sqlBuilder.Append(" ORDER BY Nombre;");
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
return await connection.QueryAsync<EmpresaDropdownDto>(sqlBuilder.ToString(), parameters);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener todas las Empresas.");
|
||||
return Enumerable.Empty<EmpresaDropdownDto>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Empresa?> GetByIdAsync(int id)
|
||||
{
|
||||
var sql = "SELECT Id_Empresa AS IdEmpresa, Nombre, Detalle FROM dbo.dist_dtEmpresas WHERE Id_Empresa = @Id";
|
||||
@@ -69,6 +89,23 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Empresa?> ObtenerLookupPorIdAsync(int id)
|
||||
{
|
||||
var sql = "SELECT Id_Empresa AS IdEmpresa, Nombre FROM dbo.dist_dtEmpresas WHERE Id_Empresa = @Id";
|
||||
try
|
||||
{
|
||||
using (var connection = _connectionFactory.CreateConnection())
|
||||
{
|
||||
return await connection.QuerySingleOrDefaultAsync<Empresa>(sql, new { Id = id });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener Empresa por ID: {IdEmpresa}", id);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsByNameAsync(string nombre, int? excludeId = null)
|
||||
{
|
||||
var sqlBuilder = new StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtEmpresas WHERE Nombre = @Nombre");
|
||||
@@ -144,7 +181,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
}
|
||||
|
||||
// Insertar en historial
|
||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new {
|
||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||
{
|
||||
IdEmpresa = insertedEmpresa.IdEmpresa,
|
||||
insertedEmpresa.Nombre,
|
||||
insertedEmpresa.Detalle,
|
||||
@@ -172,7 +210,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
VALUES (@IdEmpresa, @NombreActual, @DetalleActual, @IdUsuario, @FechaMod, @TipoMod);";
|
||||
|
||||
// Insertar en historial (estado anterior)
|
||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new {
|
||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||
{
|
||||
IdEmpresa = empresaActual.IdEmpresa,
|
||||
NombreActual = empresaActual.Nombre,
|
||||
DetalleActual = empresaActual.Detalle,
|
||||
@@ -182,7 +221,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
}, transaction: transaction);
|
||||
|
||||
// Actualizar principal
|
||||
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new {
|
||||
var rowsAffected = await transaction.Connection!.ExecuteAsync(sqlUpdate, new
|
||||
{
|
||||
empresaAActualizar.Nombre,
|
||||
empresaAActualizar.Detalle,
|
||||
empresaAActualizar.IdEmpresa
|
||||
@@ -202,7 +242,8 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
VALUES (@IdEmpresa, @Nombre, @Detalle, @IdUsuario, @FechaMod, @TipoMod);";
|
||||
|
||||
// Insertar en historial (estado antes de borrar)
|
||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new {
|
||||
await transaction.Connection!.ExecuteAsync(sqlInsertHistorico, new
|
||||
{
|
||||
IdEmpresa = empresaActual.IdEmpresa,
|
||||
empresaActual.Nombre,
|
||||
empresaActual.Detalle,
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
{
|
||||
public interface ICanillaRepository
|
||||
{
|
||||
Task<IEnumerable<(Canilla Canilla, string NombreZona, string NombreEmpresa)>> GetAllAsync(string? nomApeFilter, int? legajoFilter, bool? soloActivos);
|
||||
Task<IEnumerable<(Canilla Canilla, string? NombreZona, string? NombreEmpresa)>> GetAllAsync(string? nomApeFilter, int? legajoFilter, bool? soloActivos, bool? esAccionista);
|
||||
Task<(Canilla? Canilla, string? NombreZona, string? NombreEmpresa)> GetByIdAsync(int id);
|
||||
Task<Canilla?> GetByIdSimpleAsync(int id); // Para obtener solo la entidad Canilla
|
||||
Task<Canilla?> CreateAsync(Canilla nuevoCanilla, int idUsuario, IDbTransaction transaction);
|
||||
|
||||
@@ -2,6 +2,7 @@ using GestionIntegral.Api.Models.Distribucion;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Data;
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
|
||||
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
{
|
||||
@@ -16,5 +17,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
Task<bool> ExistsByNroDocAsync(string nroDoc, int? excludeIdDistribuidor = null);
|
||||
Task<bool> ExistsByNameAsync(string nombre, int? excludeIdDistribuidor = null);
|
||||
Task<bool> IsInUseAsync(int id); // Verificar en dist_EntradasSalidas, cue_PagosDistribuidor, dist_PorcPago
|
||||
Task<IEnumerable<DistribuidorDropdownDto?>> GetAllDropdownAsync();
|
||||
Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using GestionIntegral.Api.Models.Distribucion;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Data; // Para IDbTransaction
|
||||
using System.Data;
|
||||
using GestionIntegral.Api.Dtos.Empresas; // Para IDbTransaction
|
||||
|
||||
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
{
|
||||
@@ -14,5 +15,7 @@ namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
Task<bool> DeleteAsync(int id, int idUsuario, IDbTransaction transaction); // Necesita transacción
|
||||
Task<bool> ExistsByNameAsync(string nombre, int? excludeId = null);
|
||||
Task<bool> IsInUseAsync(int id);
|
||||
Task<IEnumerable<EmpresaDropdownDto>> GetAllDropdownAsync();
|
||||
Task<Empresa?> ObtenerLookupPorIdAsync(int id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using GestionIntegral.Api.Dtos.Reportes;
|
||||
using GestionIntegral.Api.Models.Distribucion;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data; // Para IDbTransaction
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
{
|
||||
public interface INovedadCanillaRepository
|
||||
{
|
||||
// Para obtener novedades y el nombre del canillita
|
||||
Task<IEnumerable<(NovedadCanilla Novedad, string NombreCanilla)>> GetByCanillaAsync(int idCanilla, DateTime? fechaDesde, DateTime? fechaHasta);
|
||||
Task<NovedadCanilla?> GetByIdAsync(int idNovedad);
|
||||
Task<NovedadCanilla?> CreateAsync(NovedadCanilla novedad, int idUsuario, IDbTransaction? transaction = null);
|
||||
Task<bool> UpdateAsync(NovedadCanilla novedad, int idUsuario, IDbTransaction? transaction = null);
|
||||
Task<bool> DeleteAsync(int idNovedad, int idUsuario, IDbTransaction? transaction = null);
|
||||
// Podrías añadir un método para verificar si existe una novedad para un canillita en una fecha específica si es necesario
|
||||
Task<bool> ExistsByCanillaAndFechaAsync(int idCanilla, DateTime fecha, int? excludeIdNovedad = null);
|
||||
Task<IEnumerable<NovedadesCanillasReporteDto>> GetReporteNovedadesAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta);
|
||||
Task<IEnumerable<CanillaGananciaReporteDto>> GetReporteGananciasAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
using Dapper;
|
||||
using GestionIntegral.Api.Models.Distribucion;
|
||||
using Microsoft.Extensions.Configuration; // Para IConfiguration
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using Microsoft.Data.SqlClient; // O el proveedor de tu BD
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using GestionIntegral.Api.Dtos.Reportes;
|
||||
|
||||
namespace GestionIntegral.Api.Data.Repositories.Distribucion
|
||||
{
|
||||
public class NovedadCanillaRepository : INovedadCanillaRepository
|
||||
{
|
||||
private readonly DbConnectionFactory _connectionFactory; // Inyecta tu DbConnectionFactory
|
||||
private readonly ILogger<NovedadCanillaRepository> _logger;
|
||||
|
||||
public NovedadCanillaRepository(DbConnectionFactory connectionFactory, ILogger<NovedadCanillaRepository> logger)
|
||||
{
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private async Task LogHistorialAsync(NovedadCanilla novedadOriginal, int idUsuario, string tipoMod, IDbConnection connection, IDbTransaction? transaction)
|
||||
{
|
||||
var historial = new NovedadCanillaHistorial
|
||||
{
|
||||
IdNovedad = novedadOriginal.IdNovedad,
|
||||
IdCanilla = novedadOriginal.IdCanilla,
|
||||
Fecha = novedadOriginal.Fecha,
|
||||
Detalle = novedadOriginal.Detalle,
|
||||
IdUsuario = idUsuario,
|
||||
FechaMod = DateTime.Now,
|
||||
TipoMod = tipoMod
|
||||
};
|
||||
var sqlHistorial = @"
|
||||
INSERT INTO dbo.dist_dtNovedadesCanillas_H
|
||||
(Id_Novedad, Id_Canilla, Fecha, Detalle, Id_Usuario, FechaMod, TipoMod)
|
||||
VALUES
|
||||
(@IdNovedad, @IdCanilla, @Fecha, @Detalle, @IdUsuario, @FechaMod, @TipoMod);";
|
||||
await connection.ExecuteAsync(sqlHistorial, historial, transaction);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<(NovedadCanilla Novedad, string NombreCanilla)>> GetByCanillaAsync(int idCanilla, DateTime? fechaDesde, DateTime? fechaHasta)
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
var sqlBuilder = new System.Text.StringBuilder(@"
|
||||
SELECT
|
||||
n.Id_Novedad AS IdNovedad,
|
||||
n.Id_Canilla AS IdCanilla,
|
||||
n.Fecha,
|
||||
n.Detalle,
|
||||
c.NomApe AS NombreCanilla
|
||||
FROM dbo.dist_dtNovedadesCanillas n
|
||||
JOIN dbo.dist_dtCanillas c ON n.Id_Canilla = c.Id_Canilla
|
||||
WHERE n.Id_Canilla = @IdCanilla");
|
||||
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("IdCanilla", idCanilla);
|
||||
|
||||
if (fechaDesde.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(" AND n.Fecha >= @FechaDesde");
|
||||
parameters.Add("FechaDesde", fechaDesde.Value.Date); // Solo fecha, sin hora
|
||||
}
|
||||
if (fechaHasta.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(" AND n.Fecha <= @FechaHasta");
|
||||
// Para incluir todo el día de fechaHasta
|
||||
parameters.Add("FechaHasta", fechaHasta.Value.Date.AddDays(1).AddTicks(-1));
|
||||
}
|
||||
sqlBuilder.Append(" ORDER BY n.Fecha DESC, n.Id_Novedad DESC;");
|
||||
|
||||
var result = await connection.QueryAsync<NovedadCanilla, string, (NovedadCanilla, string)>(
|
||||
sqlBuilder.ToString(),
|
||||
(novedad, nombreCanilla) => (novedad, nombreCanilla),
|
||||
parameters,
|
||||
splitOn: "NombreCanilla"
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public async Task<NovedadCanilla?> GetByIdAsync(int idNovedad)
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
var sql = "SELECT Id_Novedad AS IdNovedad, Id_Canilla AS IdCanilla, Fecha, Detalle FROM dbo.dist_dtNovedadesCanillas WHERE Id_Novedad = @IdNovedad;";
|
||||
return await connection.QuerySingleOrDefaultAsync<NovedadCanilla>(sql, new { IdNovedad = idNovedad });
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsByCanillaAndFechaAsync(int idCanilla, DateTime fecha, int? excludeIdNovedad = null)
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
var sqlBuilder = new System.Text.StringBuilder("SELECT COUNT(1) FROM dbo.dist_dtNovedadesCanillas WHERE Id_Canilla = @IdCanilla AND Fecha = @Fecha");
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("IdCanilla", idCanilla);
|
||||
parameters.Add("Fecha", fecha.Date); // Comparar solo la fecha
|
||||
|
||||
if (excludeIdNovedad.HasValue)
|
||||
{
|
||||
sqlBuilder.Append(" AND Id_Novedad != @ExcludeIdNovedad");
|
||||
parameters.Add("ExcludeIdNovedad", excludeIdNovedad.Value);
|
||||
}
|
||||
|
||||
var count = await connection.ExecuteScalarAsync<int>(sqlBuilder.ToString(), parameters);
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
public async Task<NovedadCanilla?> CreateAsync(NovedadCanilla novedad, int idUsuario, IDbTransaction? transaction = null)
|
||||
{
|
||||
var sql = @"
|
||||
INSERT INTO dbo.dist_dtNovedadesCanillas (Id_Canilla, Fecha, Detalle)
|
||||
VALUES (@IdCanilla, @Fecha, @Detalle);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
|
||||
IDbConnection conn = transaction?.Connection ?? _connectionFactory.CreateConnection();
|
||||
bool manageConnection = transaction == null; // Solo gestionar si no hay transacción externa
|
||||
|
||||
try
|
||||
{
|
||||
if (manageConnection && conn.State != ConnectionState.Open) await (conn as SqlConnection)!.OpenAsync();
|
||||
|
||||
var newId = await conn.QuerySingleAsync<int>(sql, novedad, transaction);
|
||||
novedad.IdNovedad = newId;
|
||||
await LogHistorialAsync(novedad, idUsuario, "Insertada", conn, transaction);
|
||||
return novedad;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al crear NovedadCanilla para Canilla ID: {IdCanilla}", novedad.IdCanilla);
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (manageConnection && conn.State == ConnectionState.Open) conn.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAsync(NovedadCanilla novedad, int idUsuario, IDbTransaction? transaction = null)
|
||||
{
|
||||
var novedadOriginal = await GetByIdAsync(novedad.IdNovedad); // Necesitamos el estado original para el log
|
||||
if (novedadOriginal == null) return false; // No se encontró
|
||||
|
||||
var sql = @"
|
||||
UPDATE dbo.dist_dtNovedadesCanillas SET
|
||||
Detalle = @Detalle
|
||||
-- No se permite cambiar IdCanilla ni Fecha de una novedad existente
|
||||
WHERE Id_Novedad = @IdNovedad;";
|
||||
|
||||
IDbConnection conn = transaction?.Connection ?? _connectionFactory.CreateConnection();
|
||||
bool manageConnection = transaction == null;
|
||||
|
||||
try
|
||||
{
|
||||
if (manageConnection && conn.State != ConnectionState.Open) await (conn as SqlConnection)!.OpenAsync();
|
||||
|
||||
await LogHistorialAsync(novedadOriginal, idUsuario, "Modificada", conn, transaction); // Log con datos ANTES de actualizar
|
||||
var affectedRows = await conn.ExecuteAsync(sql, novedad, transaction);
|
||||
return affectedRows > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al actualizar NovedadCanilla ID: {IdNovedad}", novedad.IdNovedad);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (manageConnection && conn.State == ConnectionState.Open) conn.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(int idNovedad, int idUsuario, IDbTransaction? transaction = null)
|
||||
{
|
||||
var novedadOriginal = await GetByIdAsync(idNovedad);
|
||||
if (novedadOriginal == null) return false;
|
||||
|
||||
var sql = "DELETE FROM dbo.dist_dtNovedadesCanillas WHERE Id_Novedad = @IdNovedad;";
|
||||
|
||||
IDbConnection conn = transaction?.Connection ?? _connectionFactory.CreateConnection();
|
||||
bool manageConnection = transaction == null;
|
||||
|
||||
try
|
||||
{
|
||||
if (manageConnection && conn.State != ConnectionState.Open) await (conn as SqlConnection)!.OpenAsync();
|
||||
|
||||
await LogHistorialAsync(novedadOriginal, idUsuario, "Eliminada", conn, transaction);
|
||||
var affectedRows = await conn.ExecuteAsync(sql, new { IdNovedad = idNovedad }, transaction);
|
||||
return affectedRows > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al eliminar NovedadCanilla ID: {IdNovedad}", idNovedad);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (manageConnection && conn.State == ConnectionState.Open) conn.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<NovedadesCanillasReporteDto>> GetReporteNovedadesAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta)
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
var parameters = new
|
||||
{
|
||||
idEmpresa,
|
||||
fechaDesde = fechaDesde.Date, // Enviar solo la fecha
|
||||
fechaHasta = fechaHasta.Date.AddDays(1).AddTicks(-1) // Para incluir todo el día hasta las 23:59:59.999...
|
||||
};
|
||||
// El nombre del SP en el archivo es SP_DistCanillasNovedades
|
||||
return await connection.QueryAsync<NovedadesCanillasReporteDto>(
|
||||
"dbo.SP_DistCanillasNovedades", // Asegúrate que el nombre del SP sea exacto
|
||||
parameters,
|
||||
commandType: CommandType.StoredProcedure
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CanillaGananciaReporteDto>> GetReporteGananciasAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta)
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
var parameters = new
|
||||
{
|
||||
idEmpresa,
|
||||
fechaDesde = fechaDesde.Date,
|
||||
fechaHasta = fechaHasta.Date // El SP SP_DistCanillasGanancias maneja el rango inclusivo directamente
|
||||
};
|
||||
return await connection.QueryAsync<CanillaGananciaReporteDto>(
|
||||
"dbo.SP_DistCanillasGanancias", // Nombre del SP
|
||||
parameters,
|
||||
commandType: CommandType.StoredProcedure
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,5 +43,7 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes
|
||||
Task<(IEnumerable<ListadoDistribucionDistSimpleDto> Simple, IEnumerable<ListadoDistribucionDistPromedioDiaDto> Promedios, string? Error)> ObtenerListadoDistribucionDistribuidoresAsync(int idDistribuidor, int idPublicacion, DateTime fechaDesde, DateTime fechaHasta);
|
||||
Task<IEnumerable<LiquidacionCanillaDetalleDto>> GetLiquidacionCanillaDetalleAsync(DateTime fecha, int idCanilla);
|
||||
Task<IEnumerable<LiquidacionCanillaGananciaDto>> GetLiquidacionCanillaGananciasAsync(DateTime fecha, int idCanilla);
|
||||
Task<IEnumerable<ListadoDistCanMensualDiariosDto>> GetReporteMensualDiariosAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista);
|
||||
Task<IEnumerable<ListadoDistCanMensualPubDto>> GetReporteMensualPorPublicacionAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista);
|
||||
}
|
||||
}
|
||||
@@ -481,39 +481,71 @@ namespace GestionIntegral.Api.Data.Repositories.Reportes
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LiquidacionCanillaDetalleDto>> GetLiquidacionCanillaDetalleAsync(DateTime fecha, int idCanilla)
|
||||
{
|
||||
const string spName = "dbo.SP_DistCanillasLiquidacion";
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@fecha", fecha, DbType.DateTime);
|
||||
parameters.Add("@idCanilla", idCanilla, DbType.Int32);
|
||||
try
|
||||
{
|
||||
using var connection = _dbConnectionFactory.CreateConnection();
|
||||
return await connection.QueryAsync<LiquidacionCanillaDetalleDto>(spName, parameters, commandType: CommandType.StoredProcedure);
|
||||
{
|
||||
const string spName = "dbo.SP_DistCanillasLiquidacion";
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@fecha", fecha, DbType.DateTime);
|
||||
parameters.Add("@idCanilla", idCanilla, DbType.Int32);
|
||||
try
|
||||
{
|
||||
using var connection = _dbConnectionFactory.CreateConnection();
|
||||
return await connection.QueryAsync<LiquidacionCanillaDetalleDto>(spName, parameters, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error SP {SPName} para Liquidacion Canilla Detalle. Fecha: {Fecha}, Canilla: {IdCanilla}", spName, fecha, idCanilla);
|
||||
return Enumerable.Empty<LiquidacionCanillaDetalleDto>();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error SP {SPName} para Liquidacion Canilla Detalle. Fecha: {Fecha}, Canilla: {IdCanilla}", spName, fecha, idCanilla);
|
||||
return Enumerable.Empty<LiquidacionCanillaDetalleDto>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LiquidacionCanillaGananciaDto>> GetLiquidacionCanillaGananciasAsync(DateTime fecha, int idCanilla)
|
||||
{
|
||||
const string spName = "dbo.SP_DistCanillasLiquidacionGanancias";
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@fecha", fecha, DbType.DateTime);
|
||||
parameters.Add("@idCanilla", idCanilla, DbType.Int32);
|
||||
try
|
||||
{
|
||||
using var connection = _dbConnectionFactory.CreateConnection();
|
||||
return await connection.QueryAsync<LiquidacionCanillaGananciaDto>(spName, parameters, commandType: CommandType.StoredProcedure);
|
||||
public async Task<IEnumerable<LiquidacionCanillaGananciaDto>> GetLiquidacionCanillaGananciasAsync(DateTime fecha, int idCanilla)
|
||||
{
|
||||
const string spName = "dbo.SP_DistCanillasLiquidacionGanancias";
|
||||
var parameters = new DynamicParameters();
|
||||
parameters.Add("@fecha", fecha, DbType.DateTime);
|
||||
parameters.Add("@idCanilla", idCanilla, DbType.Int32);
|
||||
try
|
||||
{
|
||||
using var connection = _dbConnectionFactory.CreateConnection();
|
||||
return await connection.QueryAsync<LiquidacionCanillaGananciaDto>(spName, parameters, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error SP {SPName} para Liquidacion Canilla Ganancias. Fecha: {Fecha}, Canilla: {IdCanilla}", spName, fecha, idCanilla);
|
||||
return Enumerable.Empty<LiquidacionCanillaGananciaDto>();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error SP {SPName} para Liquidacion Canilla Ganancias. Fecha: {Fecha}, Canilla: {IdCanilla}", spName, fecha, idCanilla);
|
||||
return Enumerable.Empty<LiquidacionCanillaGananciaDto>();
|
||||
|
||||
public async Task<IEnumerable<ListadoDistCanMensualDiariosDto>> GetReporteMensualDiariosAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista)
|
||||
{
|
||||
using var connection = _dbConnectionFactory.CreateConnection();
|
||||
var parameters = new
|
||||
{
|
||||
fechaDesde = fechaDesde.Date,
|
||||
fechaHasta = fechaHasta.Date, // El SP parece manejar el rango incluyendo el último día
|
||||
accionista = esAccionista
|
||||
};
|
||||
return await connection.QueryAsync<ListadoDistCanMensualDiariosDto>(
|
||||
"dbo.SP_DistCanillasAccConImporteEntreFechasDiarios",
|
||||
parameters,
|
||||
commandType: CommandType.StoredProcedure
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ListadoDistCanMensualPubDto>> GetReporteMensualPorPublicacionAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista)
|
||||
{
|
||||
using var connection = _dbConnectionFactory.CreateConnection();
|
||||
var parameters = new
|
||||
{
|
||||
fechaDesde = fechaDesde.Date,
|
||||
fechaHasta = fechaHasta.Date,
|
||||
accionista = esAccionista
|
||||
};
|
||||
return await connection.QueryAsync<ListadoDistCanMensualPubDto>(
|
||||
"dbo.SP_DistCanillasAccConImporteEntreFechas",
|
||||
parameters,
|
||||
commandType: CommandType.StoredProcedure
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Backend/GestionIntegral.Api/Models/Contables/Saldo.cs
Normal file
9
Backend/GestionIntegral.Api/Models/Contables/Saldo.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
public class Saldo
|
||||
{
|
||||
public int IdSaldo { get; set; }
|
||||
public string Destino { get; set; } = string.Empty;
|
||||
public int IdDestino { get; set; }
|
||||
public decimal Monto { get; set; }
|
||||
public int IdEmpresa { get; set; }
|
||||
public DateTime FechaUltimaModificacion { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace GestionIntegral.Api.Models.Contables
|
||||
{
|
||||
public class SaldoAjusteHistorial
|
||||
{
|
||||
public int IdSaldoAjusteHist { get; set; } // PK, Identity
|
||||
public string Destino { get; set; } = string.Empty;
|
||||
public int IdDestino { get; set; }
|
||||
public int IdEmpresa { get; set; }
|
||||
public decimal MontoAjuste { get; set; } // El monto que se sumó/restó
|
||||
public decimal SaldoAnterior { get; set; }
|
||||
public decimal SaldoNuevo { get; set; }
|
||||
public string Justificacion { get; set; } = string.Empty;
|
||||
public DateTime FechaAjuste { get; set; }
|
||||
public int IdUsuarioAjuste { get; set; }
|
||||
// Podrías añadir NombreUsuarioAjuste si quieres desnormalizar o hacer un JOIN al consultar
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GestionIntegral.Api.Models.Distribucion
|
||||
{
|
||||
public class NovedadCanilla
|
||||
{
|
||||
public int IdNovedad { get; set; }
|
||||
public int IdCanilla { get; set; }
|
||||
public DateTime Fecha { get; set; }
|
||||
public string? Detalle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace GestionIntegral.Api.Models.Distribucion
|
||||
{
|
||||
public class NovedadCanillaHistorial
|
||||
{
|
||||
// No tiene un ID propio, es una tabla de historial puro
|
||||
public int IdNovedad { get; set; } // FK a la novedad original
|
||||
public int IdCanilla { get; set; }
|
||||
public DateTime Fecha { get; set; }
|
||||
public string? Detalle { get; set; }
|
||||
public int IdUsuario { get; set; } // Quién hizo el cambio
|
||||
public DateTime FechaMod { get; set; } // Cuándo se hizo el cambio
|
||||
public required string TipoMod { get; set; } // "Insertada", "Modificada", "Eliminada"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace GestionIntegral.Api.Dtos.Contables
|
||||
{
|
||||
public class AjusteSaldoRequestDto
|
||||
{
|
||||
[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 ID del destinatario es obligatorio.")]
|
||||
[Range(1, int.MaxValue, ErrorMessage = "ID de Destinatario inválido.")]
|
||||
public int IdDestino { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "El ID de la empresa es obligatorio.")]
|
||||
[Range(1, int.MaxValue, ErrorMessage = "ID de Empresa inválido.")]
|
||||
public int IdEmpresa { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "El monto del ajuste es obligatorio.")]
|
||||
// Permitir montos negativos para disminuir deuda o positivos para aumentarla
|
||||
// No se usa Range aquí para permitir ambos signos. La validación de que no sea cero se puede hacer en el servicio.
|
||||
public decimal MontoAjuste { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "La justificación del ajuste es obligatoria.")]
|
||||
[StringLength(250, MinimumLength = 5, ErrorMessage = "La justificación debe tener entre 5 y 250 caracteres.")]
|
||||
public string Justificacion { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace GestionIntegral.Api.Dtos.Contables
|
||||
{
|
||||
public class SaldoGestionDto
|
||||
{
|
||||
public int IdSaldo { get; set; }
|
||||
public string Destino { get; set; } = string.Empty;
|
||||
public int IdDestino { get; set; }
|
||||
public string NombreDestinatario { get; set; } = string.Empty;
|
||||
public int IdEmpresa { get; set; }
|
||||
public string NombreEmpresa { get; set; } = string.Empty;
|
||||
public decimal Monto { get; set; }
|
||||
public DateTime FechaUltimaModificacion { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||
{
|
||||
public class CreateNovedadCanillaDto
|
||||
{
|
||||
// IdCanilla se tomará de la ruta o de un campo oculto si el POST es a un endpoint genérico.
|
||||
// Por ahora, lo incluimos si el endpoint es /api/novedadescanilla
|
||||
[Required]
|
||||
public int IdCanilla { get; set; }
|
||||
|
||||
[Required]
|
||||
public DateTime Fecha { get; set; }
|
||||
|
||||
[MaxLength(250)]
|
||||
public string? Detalle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||
{
|
||||
public class DistribuidorDropdownDto
|
||||
{
|
||||
public int IdDistribuidor { get; set; }
|
||||
public string Nombre { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||
{
|
||||
public class DistribuidorLookupDto
|
||||
{
|
||||
public int IdDistribuidor { get; set; }
|
||||
public string Nombre { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace GestionIntegral.Api.Dtos.Empresas
|
||||
{
|
||||
public class EmpresaDropdownDto
|
||||
{
|
||||
public int IdEmpresa { get; set; }
|
||||
public string Nombre { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace GestionIntegral.Api.Dtos.Empresas
|
||||
{
|
||||
public class EmpresaLookupDto
|
||||
{
|
||||
public int IdEmpresa { get; set; }
|
||||
public string Nombre { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||
{
|
||||
public class NovedadCanillaDto
|
||||
{
|
||||
public int IdNovedad { get; set; }
|
||||
public int IdCanilla { get; set; }
|
||||
public string NombreCanilla { get; set; } = string.Empty; // Para mostrar en UI
|
||||
public DateTime Fecha { get; set; }
|
||||
public string? Detalle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace GestionIntegral.Api.Dtos.Distribucion
|
||||
{
|
||||
public class UpdateNovedadCanillaDto
|
||||
{
|
||||
// No se permite cambiar IdCanilla ni Fecha
|
||||
[MaxLength(250)]
|
||||
public string? Detalle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace GestionIntegral.Api.Dtos.Reportes
|
||||
{
|
||||
public class CanillaGananciaReporteDto // Nuevo nombre para el DTO
|
||||
{
|
||||
public string Canilla { get; set; } = string.Empty; // NomApe del canillita
|
||||
public int? Legajo { get; set; }
|
||||
public int? Francos { get; set; }
|
||||
public int? Faltas { get; set; }
|
||||
public decimal? TotalRendir { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace GestionIntegral.Api.Dtos.Reportes
|
||||
{
|
||||
public class ListadoDistCanMensualDiariosDto
|
||||
{
|
||||
public string Canilla { get; set; } = string.Empty;
|
||||
public int? ElDia { get; set; } // Cantidad
|
||||
public int? ElPlata { get; set; } // Cantidad
|
||||
public int? Vendidos { get; set; } // Suma de ElDia y ElPlata (cantidades)
|
||||
public decimal? ImporteElDia { get; set; }
|
||||
public decimal? ImporteElPlata { get; set; }
|
||||
public decimal? ImporteTotal { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace GestionIntegral.Api.Dtos.Reportes
|
||||
{
|
||||
public class ListadoDistCanMensualPubDto
|
||||
{
|
||||
public string Publicacion { get; set; } = string.Empty;
|
||||
public string Canilla { get; set; } = string.Empty; // NomApe
|
||||
public int? TotalCantSalida { get; set; }
|
||||
public int? TotalCantEntrada { get; set; }
|
||||
public decimal? TotalRendir { get; set; }
|
||||
// No es necesario 'Vendidos' ya que el SP no lo devuelve directamente para esta variante,
|
||||
// pero se puede calcular en el frontend si es necesario (Llevados - Devueltos).
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace GestionIntegral.Api.Dtos.Reportes
|
||||
{
|
||||
public class NovedadesCanillasReporteDto
|
||||
{
|
||||
public string NomApe { get; set; } = string.Empty; // Nombre del Canillita
|
||||
public DateTime Fecha { get; set; }
|
||||
public string? Detalle { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,10 @@ builder.Services.AddScoped<IRitmoService, RitmoService>();
|
||||
builder.Services.AddScoped<ICancionRepository, CancionRepository>();
|
||||
builder.Services.AddScoped<ICancionService, CancionService>();
|
||||
builder.Services.AddScoped<IRadioListaService, RadioListaService>();
|
||||
builder.Services.AddScoped<INovedadCanillaRepository, NovedadCanillaRepository>();
|
||||
builder.Services.AddScoped<INovedadCanillaService, NovedadCanillaService>();
|
||||
// Servicio de Saldos
|
||||
builder.Services.AddScoped<ISaldoService, SaldoService>();
|
||||
// Repositorios de Reportes
|
||||
builder.Services.AddScoped<IReportesRepository, ReportesRepository>();
|
||||
// Servicios de Reportes
|
||||
@@ -199,7 +203,7 @@ if (app.Environment.IsDevelopment())
|
||||
});
|
||||
}
|
||||
|
||||
// ¡¡¡NO USAR UseHttpsRedirection si tu API corre en HTTP!!!
|
||||
// ¡¡¡NO USAR UseHttpsRedirection si la API corre en HTTP!!!
|
||||
// Comenta o elimina la siguiente línea si SÓLO usas http://localhost:5183
|
||||
// app.UseHttpsRedirection();
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using GestionIntegral.Api.Dtos.Contables;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Contables
|
||||
{
|
||||
public interface ISaldoService
|
||||
{
|
||||
Task<IEnumerable<SaldoGestionDto>> ObtenerSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter);
|
||||
Task<(bool Exito, string? Error, SaldoGestionDto? SaldoActualizado)> RealizarAjusteManualSaldoAsync(AjusteSaldoRequestDto ajusteDto, int idUsuarioAjuste);
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
}
|
||||
else if (nota.Destino == "Canillas")
|
||||
{
|
||||
var canData = await _canillaRepo.GetByIdAsync(nota.IdDestino); // Asumiendo que GetByIdAsync devuelve una tupla
|
||||
var canData = await _canillaRepo.GetByIdAsync(nota.IdDestino);
|
||||
nombreDestinatario = canData.Canilla?.NomApe ?? "Canillita Desconocido";
|
||||
}
|
||||
|
||||
@@ -95,7 +95,6 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
|
||||
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)
|
||||
@@ -103,7 +102,7 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
}
|
||||
else if (createDto.Destino == "Canillas")
|
||||
{
|
||||
if (await _canillaRepo.GetByIdSimpleAsync(createDto.IdDestino) == null) // Asumiendo GetByIdSimpleAsync en ICanillaRepository
|
||||
if (await _canillaRepo.GetByIdSimpleAsync(createDto.IdDestino) == null)
|
||||
return (null, "El canillita especificado no existe.");
|
||||
}
|
||||
else { return (null, "Tipo de destino inválido."); }
|
||||
@@ -124,19 +123,29 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
};
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
IDbTransaction? transaction = null;
|
||||
try
|
||||
{
|
||||
if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
}
|
||||
transaction = connection.BeginTransaction();
|
||||
|
||||
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;
|
||||
decimal montoParaSaldo;
|
||||
if (createDto.Tipo == "Credito")
|
||||
{
|
||||
montoParaSaldo = -createDto.Monto;
|
||||
}
|
||||
else
|
||||
{
|
||||
montoParaSaldo = createDto.Monto;
|
||||
}
|
||||
|
||||
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaCreada.Destino, notaCreada.IdDestino, notaCreada.IdEmpresa, montoAjusteSaldo, transaction);
|
||||
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync(notaCreada.Destino, notaCreada.IdDestino, notaCreada.IdEmpresa, montoParaSaldo, transaction);
|
||||
if (!saldoActualizado) throw new DataException($"Error al actualizar el saldo para {notaCreada.Destino} ID {notaCreada.IdDestino}.");
|
||||
|
||||
transaction.Commit();
|
||||
@@ -145,32 +154,57 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de CrearAsync NotaCreditoDebito."); }
|
||||
_logger.LogError(ex, "Error CrearAsync NotaCreditoDebito.");
|
||||
return (null, $"Error interno: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
IDbTransaction? transaction = null;
|
||||
try
|
||||
{
|
||||
var notaExistente = await _notaRepo.GetByIdAsync(idNota);
|
||||
if (notaExistente == null) return (false, "Nota no encontrada.");
|
||||
if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
}
|
||||
transaction = connection.BeginTransaction();
|
||||
|
||||
var notaExistente = await _notaRepo.GetByIdAsync(idNota);
|
||||
if (notaExistente == null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
return (false, "Nota no encontrada.");
|
||||
}
|
||||
|
||||
decimal impactoOriginalSaldo = notaExistente.Tipo == "Credito" ? -notaExistente.Monto : notaExistente.Monto;
|
||||
decimal impactoNuevoSaldo = notaExistente.Tipo == "Credito" ? -updateDto.Monto : updateDto.Monto;
|
||||
decimal diferenciaAjusteSaldo = impactoNuevoSaldo - impactoOriginalSaldo;
|
||||
|
||||
// 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;
|
||||
var notaParaActualizarEnRepo = new NotaCreditoDebito
|
||||
{
|
||||
IdNota = notaExistente.IdNota,
|
||||
Destino = notaExistente.Destino,
|
||||
IdDestino = notaExistente.IdDestino,
|
||||
Referencia = notaExistente.Referencia,
|
||||
Tipo = notaExistente.Tipo,
|
||||
Fecha = notaExistente.Fecha,
|
||||
Monto = updateDto.Monto,
|
||||
Observaciones = updateDto.Observaciones,
|
||||
IdEmpresa = notaExistente.IdEmpresa
|
||||
};
|
||||
|
||||
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.");
|
||||
var actualizado = await _notaRepo.UpdateAsync(notaParaActualizarEnRepo, idUsuario, transaction);
|
||||
if (!actualizado) throw new DataException("Error al actualizar la nota en la base de datos.");
|
||||
|
||||
if (diferenciaAjusteSaldo != 0)
|
||||
{
|
||||
@@ -182,30 +216,45 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
_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 (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync NotaCreditoDebito (KeyNotFound)."); } return (false, "Nota no encontrada."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error ActualizarAsync NotaC/D ID: {Id}", idNota);
|
||||
try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync NotaCreditoDebito."); }
|
||||
_logger.LogError(ex, "Error ActualizarAsync Nota C/D ID: {Id}", idNota);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
IDbTransaction? transaction = null;
|
||||
try
|
||||
{
|
||||
var notaExistente = await _notaRepo.GetByIdAsync(idNota);
|
||||
if (notaExistente == null) return (false, "Nota no encontrada.");
|
||||
if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
}
|
||||
transaction = connection.BeginTransaction();
|
||||
|
||||
// Revertir el efecto en el saldo
|
||||
decimal montoReversion = notaExistente.Tipo == "Credito" ? -notaExistente.Monto : notaExistente.Monto;
|
||||
var notaExistente = await _notaRepo.GetByIdAsync(idNota);
|
||||
if (notaExistente == null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
return (false, "Nota no encontrada.");
|
||||
}
|
||||
|
||||
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.");
|
||||
if (!eliminado) throw new DataException("Error al eliminar la nota de la base de datos.");
|
||||
|
||||
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.");
|
||||
@@ -214,13 +263,20 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
_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 (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync NotaCreditoDebito (KeyNotFound)."); } return (false, "Nota no encontrada."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync NotaCreditoDebito."); }
|
||||
_logger.LogError(ex, "Error EliminarAsync NotaC/D ID: {Id}", idNota);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,6 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
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,
|
||||
@@ -109,19 +108,29 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
};
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
IDbTransaction? transaction = null; // Declarar fuera para el finally
|
||||
try
|
||||
{
|
||||
if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
}
|
||||
transaction = connection.BeginTransaction();
|
||||
|
||||
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;
|
||||
decimal montoParaSaldo;
|
||||
if (createDto.TipoMovimiento == "Recibido")
|
||||
{
|
||||
montoParaSaldo = -createDto.Monto;
|
||||
}
|
||||
else
|
||||
{
|
||||
montoParaSaldo = createDto.Monto;
|
||||
}
|
||||
|
||||
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoCreado.IdDistribuidor, pagoCreado.IdEmpresa, montoAjusteSaldo, transaction);
|
||||
bool saldoActualizado = await _saldoRepo.ModificarSaldoAsync("Distribuidores", pagoCreado.IdDistribuidor, pagoCreado.IdEmpresa, montoParaSaldo, transaction);
|
||||
if (!saldoActualizado) throw new DataException("Error al actualizar el saldo del distribuidor.");
|
||||
|
||||
transaction.Commit();
|
||||
@@ -130,37 +139,63 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de CrearAsync PagoDistribuidor."); }
|
||||
_logger.LogError(ex, "Error CrearAsync PagoDistribuidor.");
|
||||
return (null, $"Error interno: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
IDbTransaction? transaction = null;
|
||||
try
|
||||
{
|
||||
var pagoExistente = await _pagoRepo.GetByIdAsync(idPago);
|
||||
if (pagoExistente == null) return (false, "Pago no encontrado.");
|
||||
if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
}
|
||||
transaction = connection.BeginTransaction();
|
||||
|
||||
var pagoExistente = await _pagoRepo.GetByIdAsync(idPago);
|
||||
if (pagoExistente == null)
|
||||
{
|
||||
transaction.Rollback(); // Rollback si no se encuentra
|
||||
return (false, "Pago no encontrado.");
|
||||
}
|
||||
|
||||
if (await _tipoPagoRepo.GetByIdAsync(updateDto.IdTipoPago) == null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
return (false, "Tipo de pago no válido.");
|
||||
}
|
||||
|
||||
decimal impactoOriginalSaldo = pagoExistente.TipoMovimiento == "Recibido" ? -pagoExistente.Monto : pagoExistente.Monto;
|
||||
decimal impactoNuevoSaldo = pagoExistente.TipoMovimiento == "Recibido" ? -updateDto.Monto : updateDto.Monto;
|
||||
decimal diferenciaAjusteSaldo = impactoNuevoSaldo - impactoOriginalSaldo;
|
||||
|
||||
// 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;
|
||||
var pagoParaActualizarEnRepo = new PagoDistribuidor
|
||||
{
|
||||
IdPago = pagoExistente.IdPago,
|
||||
IdDistribuidor = pagoExistente.IdDistribuidor,
|
||||
Fecha = pagoExistente.Fecha,
|
||||
TipoMovimiento = pagoExistente.TipoMovimiento,
|
||||
Recibo = pagoExistente.Recibo,
|
||||
Monto = updateDto.Monto,
|
||||
IdTipoPago = updateDto.IdTipoPago,
|
||||
Detalle = updateDto.Detalle,
|
||||
IdEmpresa = pagoExistente.IdEmpresa
|
||||
};
|
||||
|
||||
// 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.");
|
||||
var actualizado = await _pagoRepo.UpdateAsync(pagoParaActualizarEnRepo, idUsuario, transaction);
|
||||
if (!actualizado) throw new DataException("Error al actualizar el pago en la base de datos.");
|
||||
|
||||
if (diferenciaAjusteSaldo != 0)
|
||||
{
|
||||
@@ -172,32 +207,45 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
_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 (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync PagoDistribuidor (KeyNotFound)."); } return (false, "Pago no encontrado."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de ActualizarAsync PagoDistribuidor."); }
|
||||
_logger.LogError(ex, "Error ActualizarAsync PagoDistribuidor ID: {Id}", idPago);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
IDbTransaction? transaction = null;
|
||||
try
|
||||
{
|
||||
if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
}
|
||||
transaction = connection.BeginTransaction();
|
||||
|
||||
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;
|
||||
if (pagoExistente == null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
return (false, "Pago no encontrado.");
|
||||
}
|
||||
|
||||
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.");
|
||||
if (!eliminado) throw new DataException("Error al eliminar el pago de la base de datos.");
|
||||
|
||||
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.");
|
||||
@@ -206,13 +254,20 @@ namespace GestionIntegral.Api.Services.Contables
|
||||
_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 (KeyNotFoundException) { try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync PagoDistribuidor (KeyNotFound)."); } return (false, "Pago no encontrado."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
try { transaction?.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback de EliminarAsync PagoDistribuidor."); }
|
||||
_logger.LogError(ex, "Error EliminarAsync PagoDistribuidor ID: {Id}", idPago);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open)
|
||||
{
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
164
Backend/GestionIntegral.Api/Services/Contables/SaldoService.cs
Normal file
164
Backend/GestionIntegral.Api/Services/Contables/SaldoService.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Contables;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion; // Para IDistribuidorRepository, ICanillaRepository
|
||||
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 SaldoService : ISaldoService
|
||||
{
|
||||
private readonly ISaldoRepository _saldoRepo;
|
||||
private readonly IDistribuidorRepository _distribuidorRepo; // Para nombres
|
||||
private readonly ICanillaRepository _canillaRepo; // Para nombres
|
||||
private readonly IEmpresaRepository _empresaRepo; // Para nombres
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<SaldoService> _logger;
|
||||
|
||||
public SaldoService(
|
||||
ISaldoRepository saldoRepo,
|
||||
IDistribuidorRepository distribuidorRepo,
|
||||
ICanillaRepository canillaRepo,
|
||||
IEmpresaRepository empresaRepo,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<SaldoService> logger)
|
||||
{
|
||||
_saldoRepo = saldoRepo;
|
||||
_distribuidorRepo = distribuidorRepo;
|
||||
_canillaRepo = canillaRepo;
|
||||
_empresaRepo = empresaRepo;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private async Task<SaldoGestionDto> MapToGestionDto(Saldo saldo)
|
||||
{
|
||||
if (saldo == null) return null!;
|
||||
|
||||
string nombreDestinatario = "N/A";
|
||||
if (saldo.Destino == "Distribuidores")
|
||||
{
|
||||
var distData = await _distribuidorRepo.GetByIdAsync(saldo.IdDestino);
|
||||
nombreDestinatario = distData.Distribuidor?.Nombre ?? $"Dist. ID {saldo.IdDestino}";
|
||||
}
|
||||
else if (saldo.Destino == "Canillas")
|
||||
{
|
||||
var canData = await _canillaRepo.GetByIdAsync(saldo.IdDestino);
|
||||
nombreDestinatario = canData.Canilla?.NomApe ?? $"Can. ID {saldo.IdDestino}";
|
||||
}
|
||||
|
||||
var empresa = await _empresaRepo.GetByIdAsync(saldo.IdEmpresa);
|
||||
|
||||
return new SaldoGestionDto
|
||||
{
|
||||
IdSaldo = saldo.IdSaldo,
|
||||
Destino = saldo.Destino,
|
||||
IdDestino = saldo.IdDestino,
|
||||
NombreDestinatario = nombreDestinatario,
|
||||
IdEmpresa = saldo.IdEmpresa,
|
||||
NombreEmpresa = empresa?.Nombre ?? $"Emp. ID {saldo.IdEmpresa}",
|
||||
Monto = saldo.Monto,
|
||||
FechaUltimaModificacion = saldo.FechaUltimaModificacion
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SaldoGestionDto>> ObtenerSaldosParaGestionAsync(string? destinoFilter, int? idDestinoFilter, int? idEmpresaFilter)
|
||||
{
|
||||
var saldos = await _saldoRepo.GetSaldosParaGestionAsync(destinoFilter, idDestinoFilter, idEmpresaFilter);
|
||||
var dtos = new List<SaldoGestionDto>();
|
||||
foreach (var saldo in saldos)
|
||||
{
|
||||
dtos.Add(await MapToGestionDto(saldo));
|
||||
}
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error, SaldoGestionDto? SaldoActualizado)> RealizarAjusteManualSaldoAsync(AjusteSaldoRequestDto ajusteDto, int idUsuarioAjuste)
|
||||
{
|
||||
if (ajusteDto.MontoAjuste == 0)
|
||||
return (false, "El monto de ajuste no puede ser cero.", null);
|
||||
|
||||
// Validar existencia de Destino y Empresa
|
||||
if (ajusteDto.Destino == "Distribuidores")
|
||||
{
|
||||
if (await _distribuidorRepo.GetByIdSimpleAsync(ajusteDto.IdDestino) == null)
|
||||
return (false, "El distribuidor especificado no existe.", null);
|
||||
}
|
||||
else if (ajusteDto.Destino == "Canillas")
|
||||
{
|
||||
if (await _canillaRepo.GetByIdSimpleAsync(ajusteDto.IdDestino) == null)
|
||||
return (false, "El canillita especificado no existe.", null);
|
||||
} else {
|
||||
return (false, "Tipo de destino inválido.", null);
|
||||
}
|
||||
if (await _empresaRepo.GetByIdAsync(ajusteDto.IdEmpresa) == null)
|
||||
return (false, "La empresa especificada no existe.", null);
|
||||
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection.State != ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open(); }
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var saldoActual = await _saldoRepo.GetSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, transaction);
|
||||
if (saldoActual == null)
|
||||
{
|
||||
// Podríamos crear el saldo aquí si no existe y se quiere permitir un ajuste sobre un saldo nuevo.
|
||||
// O devolver error. Por ahora, error.
|
||||
transaction.Rollback();
|
||||
return (false, "No se encontró un saldo existente para el destinatario y empresa especificados.", null);
|
||||
}
|
||||
|
||||
decimal saldoAnterior = saldoActual.Monto;
|
||||
|
||||
bool modificado = await _saldoRepo.ModificarSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, ajusteDto.MontoAjuste, transaction);
|
||||
if (!modificado)
|
||||
{
|
||||
throw new DataException("No se pudo modificar el saldo principal.");
|
||||
}
|
||||
|
||||
// Obtener el saldo después de la modificación para el historial
|
||||
var saldoDespuesDeModificacion = await _saldoRepo.GetSaldoAsync(ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, transaction);
|
||||
if(saldoDespuesDeModificacion == null) throw new DataException("No se pudo obtener el saldo después de la modificación.");
|
||||
|
||||
|
||||
var historial = new SaldoAjusteHistorial
|
||||
{
|
||||
Destino = ajusteDto.Destino,
|
||||
IdDestino = ajusteDto.IdDestino,
|
||||
IdEmpresa = ajusteDto.IdEmpresa,
|
||||
MontoAjuste = ajusteDto.MontoAjuste,
|
||||
SaldoAnterior = saldoAnterior,
|
||||
SaldoNuevo = saldoDespuesDeModificacion.Monto, // saldoActual.Monto + ajusteDto.MontoAjuste,
|
||||
Justificacion = ajusteDto.Justificacion,
|
||||
FechaAjuste = DateTime.Now, // O UtcNow
|
||||
IdUsuarioAjuste = idUsuarioAjuste
|
||||
};
|
||||
await _saldoRepo.CreateSaldoAjusteHistorialAsync(historial, transaction);
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Ajuste manual de saldo realizado para {Destino} ID {IdDestino}, Empresa ID {IdEmpresa} por Usuario ID {IdUsuarioAjuste}. Monto: {MontoAjuste}",
|
||||
ajusteDto.Destino, ajusteDto.IdDestino, ajusteDto.IdEmpresa, idUsuarioAjuste, ajusteDto.MontoAjuste);
|
||||
|
||||
var saldoDtoActualizado = await MapToGestionDto(saldoDespuesDeModificacion);
|
||||
return (true, null, saldoDtoActualizado);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx){ _logger.LogError(rbEx, "Error en Rollback de RealizarAjusteManualSaldoAsync."); }
|
||||
_logger.LogError(ex, "Error en RealizarAjusteManualSaldoAsync.");
|
||||
return (false, $"Error interno al realizar el ajuste: {ex.Message}", null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open) { if (connection is System.Data.Common.DbConnection dbConn) await dbConn.CloseAsync(); else connection.Close(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,11 +54,11 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CanillaDto>> ObtenerTodosAsync(string? nomApeFilter, int? legajoFilter, bool? soloActivos)
|
||||
public async Task<IEnumerable<CanillaDto>> ObtenerTodosAsync(string? nomApeFilter, int? legajoFilter, bool? esAccionista, bool? soloActivos)
|
||||
{
|
||||
var canillasData = await _canillaRepository.GetAllAsync(nomApeFilter, legajoFilter, soloActivos);
|
||||
var data = await _canillaRepository.GetAllAsync(nomApeFilter, legajoFilter, soloActivos, esAccionista);
|
||||
// Filtrar nulos y asegurar al compilador que no hay nulos en la lista final
|
||||
return canillasData.Select(MapToDto).Where(dto => dto != null).Select(dto => dto!);
|
||||
return data.Select(MapToDto).Where(dto => dto != null).Select(dto => dto!);
|
||||
}
|
||||
|
||||
public async Task<CanillaDto?> ObtenerPorIdAsync(int id)
|
||||
@@ -81,11 +81,11 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
}
|
||||
if (createDto.Empresa != 0) // Solo validar empresa si no es 0
|
||||
{
|
||||
var empresa = await _empresaRepository.GetByIdAsync(createDto.Empresa);
|
||||
if(empresa == null)
|
||||
{
|
||||
var empresa = await _empresaRepository.GetByIdAsync(createDto.Empresa);
|
||||
if (empresa == null)
|
||||
{
|
||||
return (null, "La empresa seleccionada no es válida.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CORREGIDO: Usar directamente el valor booleano
|
||||
@@ -122,7 +122,7 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
if (canillaCreado == null) throw new DataException("Error al crear el canillita.");
|
||||
|
||||
transaction.Commit();
|
||||
|
||||
|
||||
// Para el DTO de respuesta, necesitamos NombreZona y NombreEmpresa
|
||||
string nombreEmpresaParaDto = "N/A (Accionista)";
|
||||
if (canillaCreado.Empresa != 0)
|
||||
@@ -131,12 +131,20 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
nombreEmpresaParaDto = empresaData?.Nombre ?? "Empresa Desconocida";
|
||||
}
|
||||
|
||||
var dtoCreado = new CanillaDto {
|
||||
IdCanilla = canillaCreado.IdCanilla, Legajo = canillaCreado.Legajo, NomApe = canillaCreado.NomApe,
|
||||
Parada = canillaCreado.Parada, IdZona = canillaCreado.IdZona, NombreZona = zona.Nombre, // Usar nombre de zona ya obtenido
|
||||
Accionista = canillaCreado.Accionista, Obs = canillaCreado.Obs, Empresa = canillaCreado.Empresa,
|
||||
NombreEmpresa = nombreEmpresaParaDto,
|
||||
Baja = canillaCreado.Baja, FechaBaja = null
|
||||
var dtoCreado = new CanillaDto
|
||||
{
|
||||
IdCanilla = canillaCreado.IdCanilla,
|
||||
Legajo = canillaCreado.Legajo,
|
||||
NomApe = canillaCreado.NomApe,
|
||||
Parada = canillaCreado.Parada,
|
||||
IdZona = canillaCreado.IdZona,
|
||||
NombreZona = zona.Nombre, // Usar nombre de zona ya obtenido
|
||||
Accionista = canillaCreado.Accionista,
|
||||
Obs = canillaCreado.Obs,
|
||||
Empresa = canillaCreado.Empresa,
|
||||
NombreEmpresa = nombreEmpresaParaDto,
|
||||
Baja = canillaCreado.Baja,
|
||||
FechaBaja = null
|
||||
};
|
||||
|
||||
_logger.LogInformation("Canilla ID {IdCanilla} creado por Usuario ID {IdUsuario}.", canillaCreado.IdCanilla, idUsuario);
|
||||
@@ -144,7 +152,7 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch {}
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error CrearAsync Canilla: {NomApe}", createDto.NomApe);
|
||||
return (null, $"Error interno al crear el canillita: {ex.Message}");
|
||||
}
|
||||
@@ -165,11 +173,11 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
}
|
||||
if (updateDto.Empresa != 0) // Solo validar empresa si no es 0
|
||||
{
|
||||
var empresa = await _empresaRepository.GetByIdAsync(updateDto.Empresa);
|
||||
if(empresa == null)
|
||||
{
|
||||
var empresa = await _empresaRepository.GetByIdAsync(updateDto.Empresa);
|
||||
if (empresa == null)
|
||||
{
|
||||
return (false, "La empresa seleccionada no es válida.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usar directamente el valor booleano para Accionista
|
||||
@@ -200,18 +208,19 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
try
|
||||
{
|
||||
var actualizado = await _canillaRepository.UpdateAsync(canillaExistente, idUsuario, transaction);
|
||||
if (!actualizado) throw new DataException("Error al actualizar el canillita.");
|
||||
if (!actualizado) throw new DataException("Error al actualizar el canillita.");
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Canilla ID {IdCanilla} actualizado por Usuario ID {IdUsuario}.", id, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) {
|
||||
try { transaction.Rollback(); } catch {}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
return (false, "Canillita no encontrado durante la actualización.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch {}
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error ActualizarAsync Canilla ID: {IdCanilla}", id);
|
||||
return (false, $"Error interno al actualizar el canillita: {ex.Message}");
|
||||
}
|
||||
@@ -240,13 +249,14 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
_logger.LogInformation("Estado de baja cambiado a {EstadoBaja} para Canilla ID {IdCanilla} por Usuario ID {IdUsuario}.", darDeBaja, id, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) {
|
||||
try { transaction.Rollback(); } catch {}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
return (false, "Canillita no encontrado durante el cambio de estado de baja.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch {}
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error ToggleBajaAsync Canilla ID: {IdCanilla}", id);
|
||||
return (false, $"Error interno al cambiar estado de baja: {ex.Message}");
|
||||
}
|
||||
|
||||
@@ -66,11 +66,31 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
return data.Select(MapToDto).Where(dto => dto != null).Select(dto => dto!);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DistribuidorDropdownDto>> GetAllDropdownAsync()
|
||||
{
|
||||
var data = await _distribuidorRepository.GetAllDropdownAsync();
|
||||
// Asegurar que el resultado no sea nulo y no contiene elementos nulos
|
||||
if (data == null)
|
||||
{
|
||||
return new List<DistribuidorDropdownDto>
|
||||
{
|
||||
new DistribuidorDropdownDto { IdDistribuidor = 0, Nombre = "No hay distribuidores disponibles" }
|
||||
};
|
||||
}
|
||||
return data.Where(x => x != null)!;
|
||||
}
|
||||
|
||||
public async Task<DistribuidorDto?> ObtenerPorIdAsync(int id)
|
||||
{
|
||||
var data = await _distribuidorRepository.GetByIdAsync(id);
|
||||
// MapToDto ahora devuelve DistribuidorDto?
|
||||
return MapToDto(data);
|
||||
}
|
||||
|
||||
public async Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id)
|
||||
{
|
||||
var data = await _distribuidorRepository.ObtenerLookupPorIdAsync(id);
|
||||
return data;
|
||||
}
|
||||
|
||||
public async Task<(DistribuidorDto? Distribuidor, string? Error)> CrearAsync(CreateDistribuidorDto createDto, int idUsuario)
|
||||
|
||||
@@ -42,10 +42,22 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
Detalle = e.Detalle
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EmpresaDropdownDto>> ObtenerParaDropdown()
|
||||
{
|
||||
// El repositorio ya devuelve solo las activas si es necesario
|
||||
var empresas = await _empresaRepository.GetAllDropdownAsync();
|
||||
// Mapeo Entidad -> DTO
|
||||
return empresas.Select(e => new EmpresaDropdownDto
|
||||
{
|
||||
IdEmpresa = e.IdEmpresa,
|
||||
Nombre = e.Nombre
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<EmpresaDto?> ObtenerPorIdAsync(int id)
|
||||
{
|
||||
// El repositorio ya devuelve solo las activas si es necesario
|
||||
// El repositorio ya devuelve solo las activas si es necesario
|
||||
var empresa = await _empresaRepository.GetByIdAsync(id);
|
||||
if (empresa == null) return null;
|
||||
// Mapeo Entidad -> DTO
|
||||
@@ -57,6 +69,19 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<EmpresaLookupDto?> ObtenerLookupPorIdAsync(int id)
|
||||
{
|
||||
// El repositorio ya devuelve solo las activas si es necesario
|
||||
var empresa = await _empresaRepository.ObtenerLookupPorIdAsync(id);
|
||||
if (empresa == null) return null;
|
||||
// Mapeo Entidad -> DTO
|
||||
return new EmpresaLookupDto
|
||||
{
|
||||
IdEmpresa = empresa.IdEmpresa,
|
||||
Nombre = empresa.Nombre
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<(EmpresaDto? Empresa, string? Error)> CrearAsync(CreateEmpresaDto createDto, int idUsuario)
|
||||
{
|
||||
// Validación de negocio: Nombre duplicado
|
||||
@@ -234,5 +259,5 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
}
|
||||
// --- Fin Transacción ---
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public interface ICanillaService
|
||||
{
|
||||
Task<IEnumerable<CanillaDto>> ObtenerTodosAsync(string? nomApeFilter, int? legajoFilter, bool? soloActivos);
|
||||
Task<IEnumerable<CanillaDto>> ObtenerTodosAsync(string? nomApeFilter, int? legajoFilter, bool? esAccionista, bool? soloActivos);
|
||||
Task<CanillaDto?> ObtenerPorIdAsync(int id);
|
||||
Task<(CanillaDto? Canilla, string? Error)> CrearAsync(CreateCanillaDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateCanillaDto updateDto, int idUsuario);
|
||||
|
||||
@@ -11,5 +11,7 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
Task<(DistribuidorDto? Distribuidor, string? Error)> CrearAsync(CreateDistribuidorDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateDistribuidorDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
|
||||
Task<IEnumerable<DistribuidorDropdownDto>> GetAllDropdownAsync();
|
||||
Task<DistribuidorLookupDto?> ObtenerLookupPorIdAsync(int id);
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,7 @@ namespace GestionIntegral.Api.Services.Distribucion
|
||||
Task<(EmpresaDto? Empresa, string? Error)> CrearAsync(CreateEmpresaDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateEmpresaDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario);
|
||||
Task<IEnumerable<EmpresaDropdownDto>> ObtenerParaDropdown();
|
||||
Task<EmpresaLookupDto?> ObtenerLookupPorIdAsync(int id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
using GestionIntegral.Api.Dtos.Reportes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public interface INovedadCanillaService
|
||||
{
|
||||
Task<IEnumerable<NovedadCanillaDto>> ObtenerPorCanillaAsync(int idCanilla, DateTime? fechaDesde, DateTime? fechaHasta);
|
||||
Task<NovedadCanillaDto?> ObtenerPorIdAsync(int idNovedad);
|
||||
Task<(NovedadCanillaDto? Novedad, string? Error)> CrearAsync(CreateNovedadCanillaDto createDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> ActualizarAsync(int idNovedad, UpdateNovedadCanillaDto updateDto, int idUsuario);
|
||||
Task<(bool Exito, string? Error)> EliminarAsync(int idNovedad, int idUsuario);
|
||||
Task<IEnumerable<NovedadesCanillasReporteDto>> ObtenerReporteNovedadesAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta);
|
||||
Task<IEnumerable<CanillaGananciaReporteDto>> ObtenerReporteGananciasAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
// En Services/Distribucion (o donde corresponda)
|
||||
using GestionIntegral.Api.Data; // Para DbConnectionFactory
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
using GestionIntegral.Api.Dtos.Reportes;
|
||||
using GestionIntegral.Api.Models.Distribucion; // Asegúrate que el modelo Canilla tenga NomApe
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data; // Para IDbTransaction
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public class NovedadCanillaService : INovedadCanillaService
|
||||
{
|
||||
private readonly INovedadCanillaRepository _novedadRepository;
|
||||
private readonly ICanillaRepository _canillaRepository;
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<NovedadCanillaService> _logger;
|
||||
|
||||
public NovedadCanillaService(
|
||||
INovedadCanillaRepository novedadRepository,
|
||||
ICanillaRepository canillaRepository,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<NovedadCanillaService> logger)
|
||||
{
|
||||
_novedadRepository = novedadRepository;
|
||||
_canillaRepository = canillaRepository;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private NovedadCanillaDto MapToDto((NovedadCanilla Novedad, string NombreCanilla) data)
|
||||
{
|
||||
return new NovedadCanillaDto
|
||||
{
|
||||
IdNovedad = data.Novedad.IdNovedad,
|
||||
IdCanilla = data.Novedad.IdCanilla,
|
||||
NombreCanilla = data.NombreCanilla, // Viene de la tupla en GetByCanillaAsync
|
||||
Fecha = data.Novedad.Fecha,
|
||||
Detalle = data.Novedad.Detalle
|
||||
};
|
||||
}
|
||||
private NovedadCanillaDto MapToDto(NovedadCanilla data, string nombreCanilla)
|
||||
{
|
||||
return new NovedadCanillaDto
|
||||
{
|
||||
IdNovedad = data.IdNovedad,
|
||||
IdCanilla = data.IdCanilla,
|
||||
NombreCanilla = nombreCanilla,
|
||||
Fecha = data.Fecha,
|
||||
Detalle = data.Detalle
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<NovedadCanillaDto>> ObtenerPorCanillaAsync(int idCanilla, DateTime? fechaDesde, DateTime? fechaHasta)
|
||||
{
|
||||
var data = await _novedadRepository.GetByCanillaAsync(idCanilla, fechaDesde, fechaHasta);
|
||||
return data.Select(MapToDto);
|
||||
}
|
||||
|
||||
public async Task<NovedadCanillaDto?> ObtenerPorIdAsync(int idNovedad)
|
||||
{
|
||||
var novedad = await _novedadRepository.GetByIdAsync(idNovedad);
|
||||
if (novedad == null) return null;
|
||||
|
||||
// Asumiendo que _canillaRepository.GetByIdAsync devuelve una tupla (Canilla? Canilla, ...)
|
||||
// O un DTO CanillaDto que tiene NomApe
|
||||
var canillaDataResult = await _canillaRepository.GetByIdAsync(novedad.IdCanilla);
|
||||
|
||||
// Ajusta esto según lo que realmente devuelva GetByIdAsync
|
||||
// Si devuelve CanillaDto:
|
||||
// string nombreCanilla = canillaDataResult?.NomApe ?? "Desconocido";
|
||||
// Si devuelve la tupla (Canilla? Canilla, string? NombreZona, string? NombreEmpresa):
|
||||
string nombreCanilla = canillaDataResult.Canilla?.NomApe ?? "Desconocido";
|
||||
|
||||
return MapToDto(novedad, nombreCanilla);
|
||||
}
|
||||
|
||||
public async Task<(NovedadCanillaDto? Novedad, string? Error)> CrearAsync(CreateNovedadCanillaDto createDto, int idUsuario)
|
||||
{
|
||||
// Asegúrate que GetByIdSimpleAsync devuelva un objeto Canilla o algo con NomApe
|
||||
var canilla = await _canillaRepository.GetByIdSimpleAsync(createDto.IdCanilla);
|
||||
if (canilla == null)
|
||||
{
|
||||
return (null, "El canillita especificado no existe.");
|
||||
}
|
||||
|
||||
var nuevaNovedad = new NovedadCanilla
|
||||
{
|
||||
IdCanilla = createDto.IdCanilla,
|
||||
Fecha = createDto.Fecha.Date,
|
||||
Detalle = createDto.Detalle
|
||||
};
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
// Abre la conexión explícitamente si no se usa una transacción externa
|
||||
if (connection is System.Data.Common.DbConnection dbConn && connection.State != ConnectionState.Open)
|
||||
{
|
||||
await dbConn.OpenAsync();
|
||||
}
|
||||
else if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var creada = await _novedadRepository.CreateAsync(nuevaNovedad, idUsuario, transaction);
|
||||
if (creada == null)
|
||||
{
|
||||
transaction.Rollback();
|
||||
return (null, "Error al guardar la novedad en la base de datos.");
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Novedad ID {IdNovedad} para Canilla ID {IdCanilla} creada por Usuario ID {UserId}.", creada.IdNovedad, creada.IdCanilla, idUsuario);
|
||||
// Asegúrate que 'canilla.NomApe' sea accesible. Si GetByIdSimpleAsync devuelve la entidad Canilla, esto está bien.
|
||||
return (MapToDto(creada, canilla.NomApe ?? "Canilla sin nombre"), null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error durante Rollback en CrearAsync NovedadCanilla."); }
|
||||
_logger.LogError(ex, "Error CrearAsync NovedadCanilla para Canilla ID: {IdCanilla}", createDto.IdCanilla);
|
||||
return (null, $"Error interno al crear la novedad: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open) connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idNovedad, UpdateNovedadCanillaDto updateDto, int idUsuario)
|
||||
{
|
||||
var existente = await _novedadRepository.GetByIdAsync(idNovedad);
|
||||
if (existente == null)
|
||||
{
|
||||
return (false, "Novedad no encontrada.");
|
||||
}
|
||||
|
||||
existente.Detalle = updateDto.Detalle;
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn && connection.State != ConnectionState.Open)
|
||||
{
|
||||
await dbConn.OpenAsync();
|
||||
}
|
||||
else if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var actualizado = await _novedadRepository.UpdateAsync(existente, idUsuario, transaction);
|
||||
if (!actualizado)
|
||||
{
|
||||
transaction.Rollback();
|
||||
return (false, "Error al actualizar la novedad en la base de datos.");
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Novedad ID {IdNovedad} actualizada por Usuario ID {UserId}.", idNovedad, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error durante Rollback en ActualizarAsync NovedadCanilla."); }
|
||||
_logger.LogError(ex, "Error ActualizarAsync NovedadCanilla ID: {IdNovedad}", idNovedad);
|
||||
return (false, $"Error interno al actualizar la novedad: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open) connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarAsync(int idNovedad, int idUsuario)
|
||||
{
|
||||
var existente = await _novedadRepository.GetByIdAsync(idNovedad);
|
||||
if (existente == null)
|
||||
{
|
||||
return (false, "Novedad no encontrada.");
|
||||
}
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn && connection.State != ConnectionState.Open)
|
||||
{
|
||||
await dbConn.OpenAsync();
|
||||
}
|
||||
else if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var eliminado = await _novedadRepository.DeleteAsync(idNovedad, idUsuario, transaction);
|
||||
if (!eliminado)
|
||||
{
|
||||
transaction.Rollback();
|
||||
return (false, "Error al eliminar la novedad de la base de datos.");
|
||||
}
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Novedad ID {IdNovedad} eliminada por Usuario ID {UserId}.", idNovedad, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error durante Rollback en EliminarAsync NovedadCanilla."); }
|
||||
_logger.LogError(ex, "Error EliminarAsync NovedadCanilla ID: {IdNovedad}", idNovedad);
|
||||
return (false, $"Error interno al eliminar la novedad: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open) connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<NovedadesCanillasReporteDto>> ObtenerReporteNovedadesAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta)
|
||||
{
|
||||
// Podría añadir validaciones o lógica de negocio adicional si fuera necesario
|
||||
// antes de llamar al repositorio. Por ahora, es una llamada directa.
|
||||
try
|
||||
{
|
||||
return await _novedadRepository.GetReporteNovedadesAsync(idEmpresa, fechaDesde, fechaHasta);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener datos para el reporte de novedades de canillitas. Empresa: {IdEmpresa}, Desde: {FechaDesde}, Hasta: {FechaHasta}", idEmpresa, fechaDesde, fechaHasta);
|
||||
// Podría relanzar o devolver una lista vacía con un mensaje de error,
|
||||
// dependiendo de cómo quiera manejar los errores en la capa de servicio.
|
||||
// Por simplicidad, relanzamos para que el controlador lo maneje.
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CanillaGananciaReporteDto>> ObtenerReporteGananciasAsync(int idEmpresa, DateTime fechaDesde, DateTime fechaHasta)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _novedadRepository.GetReporteGananciasAsync(idEmpresa, fechaDesde, fechaHasta);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener datos para el reporte de ganancias de canillitas. Empresa: {IdEmpresa}, Desde: {FechaDesde}, Hasta: {FechaHasta}", idEmpresa, fechaDesde, fechaHasta);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,5 +69,8 @@ namespace GestionIntegral.Api.Services.Reportes
|
||||
IEnumerable<LiquidacionCanillaGananciaDto> Ganancias,
|
||||
string? Error
|
||||
)> ObtenerDatosTicketLiquidacionAsync(DateTime fecha, int idCanilla);
|
||||
|
||||
Task<(IEnumerable<ListadoDistCanMensualDiariosDto> Data, string? Error)> ObtenerReporteMensualDiariosAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista);
|
||||
Task<(IEnumerable<ListadoDistCanMensualPubDto> Data, string? Error)> ObtenerReporteMensualPorPublicacionAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista);
|
||||
}
|
||||
}
|
||||
@@ -490,5 +490,33 @@ namespace GestionIntegral.Api.Services.Reportes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<ListadoDistCanMensualDiariosDto> Data, string? Error)> ObtenerReporteMensualDiariosAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await _reportesRepository.GetReporteMensualDiariosAsync(fechaDesde, fechaHasta, esAccionista);
|
||||
return (data, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener reporte mensual canillitas (diarios).");
|
||||
return (Enumerable.Empty<ListadoDistCanMensualDiariosDto>(), "Error al obtener datos del reporte (diarios).");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<ListadoDistCanMensualPubDto> Data, string? Error)> ObtenerReporteMensualPorPublicacionAsync(DateTime fechaDesde, DateTime fechaHasta, bool esAccionista)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = await _reportesRepository.GetReporteMensualPorPublicacionAsync(fechaDesde, fechaHasta, esAccionista);
|
||||
return (data, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener reporte mensual canillitas (por publicación).");
|
||||
return (Enumerable.Empty<ListadoDistCanMensualPubDto>(), "Error al obtener datos del reporte (por publicación).");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+062cc05fd00a484e43f8b4ff022e53ac49670a78")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+8fb94f8cefc3b498397ffcbb9b9a2e66c13b25b9")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","k3qzLxTWHeeJhAuWKMdta6j24bmJ9BMRMjuFEEVCRu0=","x/sHyso3gy4zVCu3ljpnTYCqu8IGZNRok1JoXiabIP8=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","1I2C2FVhJyFRbvyuGXnropbTYN\u002BqpCoTcHfxWbfWF10="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"C9goqBDGh4B0L1HpPwpJHjfbRNoIuzqnU7zFMHk1LhM=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","k3qzLxTWHeeJhAuWKMdta6j24bmJ9BMRMjuFEEVCRu0=","x/sHyso3gy4zVCu3ljpnTYCqu8IGZNRok1JoXiabIP8=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","ko3Qcj1qg4o0KikPIBL6WHcUA8sCBGtBIyzr8DuluqQ="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","k3qzLxTWHeeJhAuWKMdta6j24bmJ9BMRMjuFEEVCRu0=","x/sHyso3gy4zVCu3ljpnTYCqu8IGZNRok1JoXiabIP8=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","1I2C2FVhJyFRbvyuGXnropbTYN\u002BqpCoTcHfxWbfWF10="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"w3MBbMV9Msh0YEq9AW/8s16bzXJ93T9lMVXKPm/r6es=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["V/5slELlkFDzZ8iiVKV8Jt0Ia8AL5AZxPCWo9apx5lQ=","bxlPVWHR7EivQofjz9PzA8dMpKpZqCfOZ\u002BHD\u002Bf1Ew9Y=","\u002BzMwu5DIAA49kPmSydn2WMzj\u002Bdcf0MC3YakKoR6HwYg=","FUb20tYUiusFv5/KhAPdh2OB4ArUWiGApXbQJdx8tX0=","pTWqrhLBwEeWg1GsRlTKzfOAnT1JEklZ8F1/EYlc1Nk=","Hu0oNH4YYNcbnR5Ts4qd5yzC5j5JbY2kEDXces8V1vs=","TKMARE0bLM2dm9NOqxxWztnuqao5IvCh24TEHCtht6I=","84UEEMEbmmNwHVXD5Iw3dtKHTZC0Zqbk3rIRO\u002BxOq4o=","qfTzsJ\u002B5ilLyrc6EhNm61KkSH37yRi85MtgW1\u002BUD2Vo=","4ayt/JAApEOfr0yjg9szkYMPzSs6x2k3QEwmrK5RZVY=","d0weYwKWe3mH5R2BURuNLkAyytO/viA6zivv9AcIBtQ=","Ssyx6SvSGgWMOzhc9pQpk6f6\u002BmVbKQNKeDJbvVA2tjs=","FSqDybxILZmKXw160ANhj76usnM83geRrbPvJxr89OA=","k3qzLxTWHeeJhAuWKMdta6j24bmJ9BMRMjuFEEVCRu0=","x/sHyso3gy4zVCu3ljpnTYCqu8IGZNRok1JoXiabIP8=","fdI2RZZ9M9QOVHCYU5cE\u002BgVVuT7ssRbMzdXvX8rHofc=","8ePFhqKT0OT9nEg3b5T7COC81U\u002BQBcf\u002BindBGyMy6z0=","/ghcduGmSd1I25YtYli\u002BqxF0xuscxc4cTDkbEC6XYVA=","/a3YEu0oBUeA5Qr2VMdppqLuz4CQPWJt2JfBl2dtUwA=","jEO/q4IO3UFTWxlyFwRr7kbGWcTIiS\u002BClxx3kahX/Fk=","4iYOCKYvhsROdGkA1hINVBejb6r8IkwFj9SNMKub3DM=","CeDswsZIn5a7t\u002BKeHJA222yhFvDVVEW1ky98Xxnxebc=","50j34YXOc950QSqaQBMtgezD3tV5mWWR9c5qZcYQoz4=","W/aX9jIKpjNEVoGrU6RXFOY8SDJVT6XB4Rg4QCaeQkQ=","16IbB\u002B3zYHZvsWbCQK6hBFmKJ6Z28SecBn2jm8R3w8I=","COJtHNQqycTJqXkFv2hhpLUT\u002B/AD4IWyQlmxkUVQPNk=","cp6a5bdvkLnUn3x47KQODzPycnx57RmWO\u002B9q8MuoGQo=","oKZRNhIQRaZrETEa3L6JiwIp0\u002BmjzJo193EWBoCuVUg=","sjwbCAEQX51sEWhYVGBihWUNBxniUKZALVJIGK\u002BYgsk=","A4m4kVcox60bvdkJ1CswoZADAT70WPcs4TAKdpMoUjM=","zSzyOuNcK0NQJLwK8Yg4sH4EflX7RPf65Fl2CZUWIGs=","ko3Qcj1qg4o0KikPIBL6WHcUA8sCBGtBIyzr8DuluqQ="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
Reference in New Issue
Block a user