feat: Implementación de Secciones, Recargos, Porc. Pago Dist. y backend E/S Dist.
Backend API:
- Recargos por Zona (`dist_RecargoZona`):
- CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/recargos`.
- Lógica de negocio para vigencias (cierre/reapertura de períodos).
- Auditoría en `dist_RecargoZona_H`.
- Porcentajes de Pago Distribuidores (`dist_PorcPago`):
- CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/porcentajespago`.
- Lógica de negocio para vigencias.
- Auditoría en `dist_PorcPago_H`.
- Porcentajes/Montos Pago Canillitas (`dist_PorcMonPagoCanilla`):
- CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/porcentajesmoncanilla`.
- Lógica de negocio para vigencias.
- Auditoría en `dist_PorcMonPagoCanilla_H`.
- Secciones de Publicación (`dist_dtPubliSecciones`):
- CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/secciones`.
- Auditoría en `dist_dtPubliSecciones_H`.
- Entradas/Salidas Distribuidores (`dist_EntradasSalidas`):
- Implementado backend (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Lógica para determinar precios/recargos/porcentajes aplicables.
- Cálculo de monto y afectación de saldos de distribuidores en `cue_Saldos`.
- Auditoría en `dist_EntradasSalidas_H`.
- Correcciones de Mapeo Dapper:
- Aplicados alias explícitos en repositorios de RecargoZona, PorcPago, PorcMonCanilla, PubliSeccion,
Canilla, Distribuidor y Precio para asegurar mapeo correcto de IDs y columnas.
Frontend React:
- Recargos por Zona:
- `recargoZonaService.ts`.
- `RecargoZonaFormModal.tsx` para crear/editar períodos de recargos.
- `GestionarRecargosPublicacionPage.tsx` para listar y gestionar recargos por publicación.
- Porcentajes de Pago Distribuidores:
- `porcPagoService.ts`.
- `PorcPagoFormModal.tsx`.
- `GestionarPorcentajesPagoPage.tsx`.
- Porcentajes/Montos Pago Canillitas:
- `porcMonCanillaService.ts`.
- `PorcMonCanillaFormModal.tsx`.
- `GestionarPorcMonCanillaPage.tsx`.
- Secciones de Publicación:
- `publiSeccionService.ts`.
- `PubliSeccionFormModal.tsx`.
- `GestionarSeccionesPublicacionPage.tsx`.
- Navegación:
- Actualizadas rutas y menús para acceder a la gestión de recargos, porcentajes (dist. y canillita) y secciones desde la vista de una publicación.
- Layout:
- Uso consistente de `Box` con Flexbox en lugar de `Grid` en nuevos modales y páginas para evitar errores de tipo.
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
using GestionIntegral.Api.Data;
|
||||
using GestionIntegral.Api.Data.Repositories.Distribucion;
|
||||
using GestionIntegral.Api.Dtos.Distribucion;
|
||||
using GestionIntegral.Api.Models.Distribucion;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GestionIntegral.Api.Services.Distribucion
|
||||
{
|
||||
public class RecargoZonaService : IRecargoZonaService
|
||||
{
|
||||
private readonly IRecargoZonaRepository _recargoZonaRepository;
|
||||
private readonly IPublicacionRepository _publicacionRepository;
|
||||
private readonly IZonaRepository _zonaRepository;
|
||||
private readonly DbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<RecargoZonaService> _logger;
|
||||
|
||||
public RecargoZonaService(
|
||||
IRecargoZonaRepository recargoZonaRepository,
|
||||
IPublicacionRepository publicacionRepository,
|
||||
IZonaRepository zonaRepository,
|
||||
DbConnectionFactory connectionFactory,
|
||||
ILogger<RecargoZonaService> logger)
|
||||
{
|
||||
_recargoZonaRepository = recargoZonaRepository;
|
||||
_publicacionRepository = publicacionRepository;
|
||||
_zonaRepository = zonaRepository;
|
||||
_connectionFactory = connectionFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private RecargoZonaDto MapToDto((RecargoZona Recargo, string NombreZona) data) => new RecargoZonaDto
|
||||
{
|
||||
IdRecargo = data.Recargo.IdRecargo,
|
||||
IdPublicacion = data.Recargo.IdPublicacion,
|
||||
IdZona = data.Recargo.IdZona,
|
||||
NombreZona = data.NombreZona,
|
||||
VigenciaD = data.Recargo.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = data.Recargo.VigenciaH?.ToString("yyyy-MM-dd"),
|
||||
Valor = data.Recargo.Valor
|
||||
};
|
||||
|
||||
// Helper para mapear cuando solo tenemos el RecargoZona y necesitamos buscar NombreZona
|
||||
private async Task<RecargoZonaDto> MapToDtoWithZonaLookup(RecargoZona recargo)
|
||||
{
|
||||
var zona = await _zonaRepository.GetByIdAsync(recargo.IdZona); // zonaRepository.GetByIdAsync devuelve ZonaDto
|
||||
return new RecargoZonaDto
|
||||
{
|
||||
IdRecargo = recargo.IdRecargo,
|
||||
IdPublicacion = recargo.IdPublicacion,
|
||||
IdZona = recargo.IdZona,
|
||||
NombreZona = zona?.Nombre ?? "Zona Desconocida/Inactiva", // Usar la propiedad Nombre del ZonaDto
|
||||
VigenciaD = recargo.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = recargo.VigenciaH?.ToString("yyyy-MM-dd"),
|
||||
Valor = recargo.Valor
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<RecargoZonaDto>> ObtenerPorPublicacionIdAsync(int idPublicacion)
|
||||
{
|
||||
var recargosData = await _recargoZonaRepository.GetByPublicacionIdAsync(idPublicacion);
|
||||
return recargosData
|
||||
.Select(rd => MapToDto(rd)) // MapToDto ahora devuelve RecargoZonaDto?
|
||||
.Where(dto => dto != null)
|
||||
.Select(dto => dto!); // Cast no nulo
|
||||
}
|
||||
|
||||
public async Task<RecargoZonaDto?> ObtenerPorIdAsync(int idRecargo)
|
||||
{
|
||||
var recargo = await _recargoZonaRepository.GetByIdAsync(idRecargo);
|
||||
if (recargo == null) return null;
|
||||
|
||||
var zona = await _zonaRepository.GetByIdAsync(recargo.IdZona); // Obtiene ZonaDto
|
||||
return new RecargoZonaDto {
|
||||
IdRecargo = recargo.IdRecargo,
|
||||
IdPublicacion = recargo.IdPublicacion,
|
||||
IdZona = recargo.IdZona,
|
||||
NombreZona = zona?.Nombre ?? "Zona Desconocida/Inactiva",
|
||||
VigenciaD = recargo.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = recargo.VigenciaH?.ToString("yyyy-MM-dd"),
|
||||
Valor = recargo.Valor
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<(RecargoZonaDto? Recargo, string? Error)> CrearAsync(CreateRecargoZonaDto createDto, int idUsuario)
|
||||
{
|
||||
if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null)
|
||||
return (null, "La publicación especificada no existe.");
|
||||
var zona = await _zonaRepository.GetByIdAsync(createDto.IdZona); // Devuelve ZonaDto
|
||||
if (zona == null)
|
||||
return (null, "La zona especificada no existe o no está activa.");
|
||||
|
||||
// Usar createDto.VigenciaD directamente que ya es DateTime
|
||||
var recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(createDto.IdPublicacion, createDto.IdZona, createDto.VigenciaD.Date);
|
||||
if (recargoActivo != null)
|
||||
{
|
||||
return (null, $"Ya existe un recargo activo para esta publicación y zona en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Primero debe cerrar el período anterior.");
|
||||
}
|
||||
|
||||
var nuevoRecargo = new RecargoZona
|
||||
{
|
||||
IdPublicacion = createDto.IdPublicacion,
|
||||
IdZona = createDto.IdZona,
|
||||
VigenciaD = createDto.VigenciaD.Date,
|
||||
VigenciaH = null,
|
||||
Valor = createDto.Valor
|
||||
};
|
||||
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var recargoAnterior = await _recargoZonaRepository.GetPreviousActiveRecargoAsync(createDto.IdPublicacion, createDto.IdZona, nuevoRecargo.VigenciaD, transaction);
|
||||
if (recargoAnterior != null)
|
||||
{
|
||||
if (recargoAnterior.VigenciaD.Date >= nuevoRecargo.VigenciaD.Date) // Comparar solo fechas
|
||||
{
|
||||
transaction.Rollback();
|
||||
return (null, $"La fecha de inicio del nuevo recargo ({nuevoRecargo.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último recargo vigente ({recargoAnterior.VigenciaD:dd/MM/yyyy}) para esta zona.");
|
||||
}
|
||||
recargoAnterior.VigenciaH = nuevoRecargo.VigenciaD.AddDays(-1);
|
||||
await _recargoZonaRepository.UpdateAsync(recargoAnterior, idUsuario, transaction);
|
||||
}
|
||||
|
||||
var recargoCreado = await _recargoZonaRepository.CreateAsync(nuevoRecargo, idUsuario, transaction);
|
||||
if (recargoCreado == null) throw new DataException("Error al crear el recargo.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Recargo ID {Id} creado por Usuario ID {UserId}.", recargoCreado.IdRecargo, idUsuario);
|
||||
// Pasar el nombre de la zona ya obtenido
|
||||
return (new RecargoZonaDto {
|
||||
IdRecargo = recargoCreado.IdRecargo, IdPublicacion = recargoCreado.IdPublicacion, IdZona = recargoCreado.IdZona,
|
||||
NombreZona = zona.Nombre, VigenciaD = recargoCreado.VigenciaD.ToString("yyyy-MM-dd"),
|
||||
VigenciaH = recargoCreado.VigenciaH?.ToString("yyyy-MM-dd"), Valor = recargoCreado.Valor
|
||||
}, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error CrearAsync RecargoZona para Pub ID {IdPub}, Zona ID {IdZona}", createDto.IdPublicacion, createDto.IdZona);
|
||||
return (null, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> ActualizarAsync(int idRecargo, UpdateRecargoZonaDto updateDto, int idUsuario)
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var recargoExistente = await _recargoZonaRepository.GetByIdAsync(idRecargo);
|
||||
if (recargoExistente == null) return (false, "Recargo por zona no encontrado.");
|
||||
|
||||
if (updateDto.VigenciaH.HasValue && updateDto.VigenciaH.Value.Date < recargoExistente.VigenciaD.Date)
|
||||
return (false, "Vigencia Hasta no puede ser anterior a Vigencia Desde.");
|
||||
|
||||
if (updateDto.VigenciaH.HasValue)
|
||||
{
|
||||
var recargosDeLaPublicacion = await _recargoZonaRepository.GetByPublicacionIdAsync(recargoExistente.IdPublicacion);
|
||||
var recargosPosteriores = recargosDeLaPublicacion
|
||||
.Where(r => r.Recargo.IdZona == recargoExistente.IdZona &&
|
||||
r.Recargo.IdRecargo != idRecargo &&
|
||||
r.Recargo.VigenciaD.Date <= updateDto.VigenciaH.Value.Date &&
|
||||
r.Recargo.VigenciaD.Date > recargoExistente.VigenciaD.Date);
|
||||
if(recargosPosteriores.Any())
|
||||
{
|
||||
return (false, "No se puede cerrar este período porque existen recargos posteriores para esta zona que se solaparían.");
|
||||
}
|
||||
}
|
||||
|
||||
recargoExistente.Valor = updateDto.Valor;
|
||||
if (updateDto.VigenciaH.HasValue)
|
||||
{
|
||||
recargoExistente.VigenciaH = updateDto.VigenciaH.Value.Date;
|
||||
}
|
||||
|
||||
var actualizado = await _recargoZonaRepository.UpdateAsync(recargoExistente, idUsuario, transaction);
|
||||
if (!actualizado) throw new DataException("Error al actualizar recargo.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Recargo ID {Id} actualizado por Usuario ID {UserId}.", idRecargo, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Recargo no encontrado."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error ActualizarAsync RecargoZona ID: {Id}", idRecargo);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(bool Exito, string? Error)> EliminarAsync(int idRecargo, int idUsuario)
|
||||
{
|
||||
using var connection = _connectionFactory.CreateConnection();
|
||||
if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var recargoAEliminar = await _recargoZonaRepository.GetByIdAsync(idRecargo);
|
||||
if (recargoAEliminar == null) return (false, "Recargo no encontrado.");
|
||||
|
||||
if (recargoAEliminar.VigenciaH == null)
|
||||
{
|
||||
var todosRecargosPubZonaData = await _recargoZonaRepository.GetByPublicacionIdAsync(recargoAEliminar.IdPublicacion);
|
||||
var todosRecargosPubZona = todosRecargosPubZonaData
|
||||
.Where(r => r.Recargo.IdZona == recargoAEliminar.IdZona)
|
||||
.Select(r => r.Recargo)
|
||||
.OrderByDescending(r => r.VigenciaD).ToList();
|
||||
|
||||
var indiceActual = todosRecargosPubZona.FindIndex(r => r.IdRecargo == idRecargo);
|
||||
if(indiceActual != -1 && (indiceActual + 1) < todosRecargosPubZona.Count)
|
||||
{
|
||||
var recargoAnteriorDirecto = todosRecargosPubZona[indiceActual + 1];
|
||||
if(recargoAnteriorDirecto.VigenciaH.HasValue && recargoAnteriorDirecto.VigenciaH.Value.Date == recargoAEliminar.VigenciaD.AddDays(-1).Date)
|
||||
{
|
||||
recargoAnteriorDirecto.VigenciaH = null;
|
||||
await _recargoZonaRepository.UpdateAsync(recargoAnteriorDirecto, idUsuario, transaction);
|
||||
_logger.LogInformation("Recargo anterior ID {IdRecargoAnterior} reabierto tras eliminación de Recargo ID {IdRecargoEliminado}.", recargoAnteriorDirecto.IdRecargo, idRecargo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var eliminado = await _recargoZonaRepository.DeleteAsync(idRecargo, idUsuario, transaction);
|
||||
if (!eliminado) throw new DataException("Error al eliminar recargo.");
|
||||
|
||||
transaction.Commit();
|
||||
_logger.LogInformation("Recargo ID {Id} eliminado por Usuario ID {UserId}.", idRecargo, idUsuario);
|
||||
return (true, null);
|
||||
}
|
||||
catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Recargo no encontrado."); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { transaction.Rollback(); } catch { }
|
||||
_logger.LogError(ex, "Error EliminarAsync RecargoZona ID: {Id}", idRecargo);
|
||||
return (false, $"Error interno: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user