Files
GestionIntegralWeb/Backend/GestionIntegral.Api/Services/Distribucion/EntradaSalidaCanillaService.cs

481 lines
26 KiB
C#
Raw Normal View History

using GestionIntegral.Api.Data;
using GestionIntegral.Api.Data.Repositories.Distribucion;
using GestionIntegral.Api.Data.Repositories.Usuarios;
using GestionIntegral.Api.Dtos.Distribucion;
using GestionIntegral.Api.Models.Distribucion;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using System.Security.Claims;
namespace GestionIntegral.Api.Services.Distribucion
{
public class EntradaSalidaCanillaService : IEntradaSalidaCanillaService
{
private readonly IEntradaSalidaCanillaRepository _esCanillaRepository;
private readonly IPublicacionRepository _publicacionRepository;
private readonly ICanillaRepository _canillaRepository;
private readonly IPrecioRepository _precioRepository;
private readonly IRecargoZonaRepository _recargoZonaRepository;
private readonly IPorcMonCanillaRepository _porcMonCanillaRepository;
private readonly IUsuarioRepository _usuarioRepository;
private readonly DbConnectionFactory _connectionFactory;
private readonly ILogger<EntradaSalidaCanillaService> _logger;
public EntradaSalidaCanillaService(
IEntradaSalidaCanillaRepository esCanillaRepository,
IPublicacionRepository publicacionRepository,
ICanillaRepository canillaRepository,
IPrecioRepository precioRepository,
IRecargoZonaRepository recargoZonaRepository,
IPorcMonCanillaRepository porcMonCanillaRepository,
IUsuarioRepository usuarioRepository,
DbConnectionFactory connectionFactory,
ILogger<EntradaSalidaCanillaService> logger)
{
_esCanillaRepository = esCanillaRepository;
_publicacionRepository = publicacionRepository;
_canillaRepository = canillaRepository;
_precioRepository = precioRepository;
_recargoZonaRepository = recargoZonaRepository;
_porcMonCanillaRepository = porcMonCanillaRepository;
_usuarioRepository = usuarioRepository;
_connectionFactory = connectionFactory;
_logger = logger;
}
private async Task<EntradaSalidaCanillaDto?> MapToDto(EntradaSalidaCanilla? es)
{
if (es == null) return null;
var publicacionDataResult = await _publicacionRepository.GetByIdAsync(es.IdPublicacion);
var canillaDataResult = await _canillaRepository.GetByIdAsync(es.IdCanilla); // Devuelve tupla
Publicacion? publicacionEntity = publicacionDataResult.Publicacion; // Componente nullable de la tupla
Canilla? canillaEntity = canillaDataResult.Canilla; // Componente nullable de la tupla
var usuarioLiq = es.UserLiq.HasValue ? await _usuarioRepository.GetByIdAsync(es.UserLiq.Value) : null;
decimal montoARendir = await CalcularMontoARendir(es, canillaEntity, publicacionEntity);
return new EntradaSalidaCanillaDto
{
IdParte = es.IdParte,
IdPublicacion = es.IdPublicacion,
NombrePublicacion = publicacionEntity?.Nombre ?? "Pub. Desconocida",
IdCanilla = es.IdCanilla,
NomApeCanilla = canillaEntity?.NomApe ?? "Can. Desconocido",
CanillaEsAccionista = canillaEntity?.Accionista ?? false,
Fecha = es.Fecha.ToString("yyyy-MM-dd"),
CantSalida = es.CantSalida,
CantEntrada = es.CantEntrada,
Vendidos = es.CantSalida - es.CantEntrada,
Observacion = es.Observacion,
Liquidado = es.Liquidado,
FechaLiquidado = es.FechaLiquidado?.ToString("yyyy-MM-dd"),
UserLiq = es.UserLiq,
NombreUserLiq = usuarioLiq != null ? $"{usuarioLiq.Nombre} {usuarioLiq.Apellido}" : null,
MontoARendir = montoARendir,
PrecioUnitarioAplicado = (await _precioRepository.GetByIdAsync(es.IdPrecio))?.Lunes ?? 0,
RecargoAplicado = es.IdRecargo > 0 ? (await _recargoZonaRepository.GetByIdAsync(es.IdRecargo))?.Valor ?? 0 : 0,
PorcentajeOMontoCanillaAplicado = es.IdPorcMon > 0 ? (await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon))?.PorcMon ?? 0 : 0,
EsPorcentajeCanilla = es.IdPorcMon > 0 ? (await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon))?.EsPorcentaje ?? false : false
};
}
private async Task<decimal> CalcularMontoARendir(EntradaSalidaCanilla es, Canilla? canilla, Publicacion? publicacion) // Acepta nullable Canilla y Publicacion
{
if (es.CantSalida - es.CantEntrada <= 0) return 0;
var precioConfig = await _precioRepository.GetByIdAsync(es.IdPrecio);
if (precioConfig == null)
{
_logger.LogError("Configuración de precio ID {IdPrecio} no encontrada para movimiento ID {IdParte}.", es.IdPrecio, es.IdParte);
throw new InvalidOperationException($"Configuración de precio ID {es.IdPrecio} no encontrada para el movimiento.");
}
decimal precioDia = 0;
DayOfWeek diaSemana = es.Fecha.DayOfWeek;
switch (diaSemana)
{
case DayOfWeek.Monday: precioDia = precioConfig.Lunes ?? 0; break;
case DayOfWeek.Tuesday: precioDia = precioConfig.Martes ?? 0; break;
case DayOfWeek.Wednesday: precioDia = precioConfig.Miercoles ?? 0; break;
case DayOfWeek.Thursday: precioDia = precioConfig.Jueves ?? 0; break;
case DayOfWeek.Friday: precioDia = precioConfig.Viernes ?? 0; break;
case DayOfWeek.Saturday: precioDia = precioConfig.Sabado ?? 0; break;
case DayOfWeek.Sunday: precioDia = precioConfig.Domingo ?? 0; break;
}
decimal valorRecargo = 0;
if (es.IdRecargo > 0)
{
var recargoConfig = await _recargoZonaRepository.GetByIdAsync(es.IdRecargo);
if (recargoConfig != null) valorRecargo = recargoConfig.Valor;
}
decimal precioFinalUnitario = precioDia + valorRecargo;
int cantidadVendida = es.CantSalida - es.CantEntrada;
if (canilla != null && canilla.Accionista && es.IdPorcMon > 0) // Check null para canilla
{
var porcMonConfig = await _porcMonCanillaRepository.GetByIdAsync(es.IdPorcMon);
if (porcMonConfig != null)
{
if (porcMonConfig.EsPorcentaje)
{
return Math.Round((precioFinalUnitario * cantidadVendida * porcMonConfig.PorcMon) / 100, 2);
}
else
{
return Math.Round(cantidadVendida * porcMonConfig.PorcMon, 2);
}
}
}
return Math.Round(precioFinalUnitario * cantidadVendida, 2);
}
public async Task<IEnumerable<EntradaSalidaCanillaDto>> ObtenerTodosAsync(
DateTime? fechaDesde, DateTime? fechaHasta,
int? idPublicacion, int? idCanilla, bool? liquidados, bool? incluirNoLiquidados)
{
bool? filtroLiquidadoFinal = null;
if (liquidados.HasValue)
{
filtroLiquidadoFinal = liquidados.Value;
}
else
{
if (incluirNoLiquidados.HasValue && !incluirNoLiquidados.Value)
{
filtroLiquidadoFinal = true;
}
}
var movimientos = await _esCanillaRepository.GetAllAsync(fechaDesde, fechaHasta, idPublicacion, idCanilla, filtroLiquidadoFinal);
var dtos = new List<EntradaSalidaCanillaDto>();
foreach (var mov in movimientos)
{
var dto = await MapToDto(mov);
if (dto != null) dtos.Add(dto);
}
return dtos;
}
public async Task<EntradaSalidaCanillaDto?> ObtenerPorIdAsync(int idParte)
{
var movimiento = await _esCanillaRepository.GetByIdAsync(idParte);
return await MapToDto(movimiento);
}
public async Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaCanillaDto updateDto, int idUsuario)
{
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
using var transaction = connection.BeginTransaction();
try
{
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte);
if (esExistente == null) return (false, "Movimiento no encontrado.");
if (esExistente.Liquidado) return (false, "No se puede modificar un movimiento ya liquidado.");
esExistente.CantSalida = updateDto.CantSalida;
esExistente.CantEntrada = updateDto.CantEntrada;
esExistente.Observacion = updateDto.Observacion;
var actualizado = await _esCanillaRepository.UpdateAsync(esExistente, idUsuario, transaction);
if (!actualizado) throw new DataException("Error al actualizar el movimiento.");
transaction.Commit();
_logger.LogInformation("Movimiento Canillita ID {Id} actualizado por Usuario ID {UserId}.", idParte, idUsuario);
return (true, null);
}
catch (KeyNotFoundException) { if (transaction.Connection != null) try { transaction.Rollback(); } catch { } return (false, "Movimiento no encontrado."); }
catch (Exception ex)
{
if (transaction.Connection != null) try { transaction.Rollback(); } catch { }
_logger.LogError(ex, "Error ActualizarMovimientoAsync Canillita ID: {Id}", idParte);
return (false, $"Error interno: {ex.Message}");
}
finally
{
if (connection?.State == ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
}
}
}
public async Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario, ClaimsPrincipal userPrincipal)
{
// Helper interno para verificar permisos desde el ClaimsPrincipal proporcionado
bool TienePermisoEspecifico(string codAcc) => userPrincipal.IsInRole("SuperAdmin") || userPrincipal.HasClaim(c => c.Type == "permission" && c.Value == codAcc);
using var connection = _connectionFactory.CreateConnection();
IDbTransaction? transaction = null;
try
{
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync();
else if (connection.State == ConnectionState.Closed) connection.Open();
transaction = connection.BeginTransaction();
var esExistente = await _esCanillaRepository.GetByIdAsync(idParte);
if (esExistente == null)
{
if (transaction?.Connection != null) transaction.Rollback();
return (false, "Movimiento no encontrado.");
}
if (esExistente.Liquidado)
{
// Permiso MC006 es para "Eliminar Movimientos de Canillita Liquidados"
if (!TienePermisoEspecifico("MC006"))
{
if (transaction?.Connection != null) transaction.Rollback();
return (false, "No tiene permiso para eliminar movimientos ya liquidados. Se requiere permiso especial (MC006) o ser SuperAdmin.");
}
_logger.LogWarning("Usuario ID {IdUsuario} está eliminando un movimiento LIQUIDADO (IDParte: {IdParte}). Permiso MC006 verificado.", idUsuario, idParte);
}
// Si no está liquidado, el permiso MC004 ya fue verificado en el controlador.
var eliminado = await _esCanillaRepository.DeleteAsync(idParte, idUsuario, transaction);
if (!eliminado)
{
// No es necesario hacer rollback aquí si DeleteAsync lanza una excepción,
// ya que el bloque catch lo manejará. Si DeleteAsync devuelve false sin lanzar,
// entonces sí sería necesario un rollback.
if (transaction?.Connection != null) transaction.Rollback();
throw new DataException("Error al eliminar el movimiento desde el repositorio.");
}
if (transaction?.Connection != null) transaction.Commit();
_logger.LogInformation("Movimiento Canillita ID {IdParte} eliminado por Usuario ID {IdUsuario}.", idParte, idUsuario);
return (true, null);
}
catch (KeyNotFoundException)
{
if (transaction?.Connection != null) try { transaction.Rollback(); } catch (Exception exR) { _logger.LogError(exR, "Rollback fallido KeyNotFoundException."); }
return (false, "Movimiento no encontrado.");
}
catch (Exception ex)
{
if (transaction?.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
_logger.LogError(ex, "Error EliminarMovimientoAsync Canillita ID: {IdParte}", idParte);
return (false, $"Error interno: {ex.Message}");
}
finally
{
if (transaction != null) transaction.Dispose();
if (connection?.State == ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
}
}
}
public async Task<(bool Exito, string? Error)> LiquidarMovimientosAsync(LiquidarMovimientosCanillaRequestDto liquidarDto, int idUsuarioLiquidador)
{
if (liquidarDto.IdsPartesALiquidar == null || !liquidarDto.IdsPartesALiquidar.Any())
return (false, "No se especificaron movimientos para liquidar.");
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
using var transaction = connection.BeginTransaction();
try
{
bool liquidacionExitosa = await _esCanillaRepository.LiquidarAsync(liquidarDto.IdsPartesALiquidar, liquidarDto.FechaLiquidacion.Date, idUsuarioLiquidador, transaction);
if (!liquidacionExitosa)
{
_logger.LogWarning("Liquidación de movimientos de canillita pudo no haber afectado a todos los IDs solicitados. IDs: {Ids}", string.Join(",", liquidarDto.IdsPartesALiquidar));
}
if (transaction.Connection != null) transaction.Commit();
_logger.LogInformation("Movimientos de Canillita liquidados. Ids: {Ids} por Usuario ID {UserId}.", string.Join(",", liquidarDto.IdsPartesALiquidar), idUsuarioLiquidador);
return (true, null);
}
catch (Exception ex)
{
if (transaction.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
_logger.LogError(ex, "Error al liquidar movimientos de canillita.");
return (false, $"Error interno al liquidar movimientos: {ex.Message}");
}
finally
{
if (connection?.State == ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
}
}
}
public async Task<(IEnumerable<EntradaSalidaCanillaDto>? MovimientosCreados, string? Error)> CrearMovimientosEnLoteAsync(CreateBulkEntradaSalidaCanillaDto createBulkDto, int idUsuario)
{
var canillaDataResult = await _canillaRepository.GetByIdAsync(createBulkDto.IdCanilla);
Canilla? canillaActual = canillaDataResult.Canilla;
if (canillaActual == null) return (null, "Canillita no válido.");
if (canillaActual.Baja) return (null, "El canillita está dado de baja.");
List<EntradaSalidaCanilla> movimientosCreadosEntidades = new List<EntradaSalidaCanilla>();
List<string> erroresItems = new List<string>();
using var connection = _connectionFactory.CreateConnection();
if (connection is System.Data.Common.DbConnection dbConnOpen && connection.State == ConnectionState.Closed) await dbConnOpen.OpenAsync(); else if (connection.State == ConnectionState.Closed) connection.Open();
using var transaction = connection.BeginTransaction();
try
{
foreach (var itemDto in createBulkDto.Items.Where(i => i.IdPublicacion > 0))
{
if (await _esCanillaRepository.ExistsByPublicacionCanillaFechaAsync(itemDto.IdPublicacion, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, transaction: transaction))
{
var pubInfo = await _publicacionRepository.GetByIdSimpleAsync(itemDto.IdPublicacion);
erroresItems.Add($"Ya existe un registro para la publicación '{pubInfo?.Nombre ?? itemDto.IdPublicacion.ToString()}' para {canillaActual.NomApe} en la fecha {createBulkDto.Fecha:dd/MM/yyyy}.");
}
}
if (erroresItems.Any())
{
if (transaction.Connection != null) transaction.Rollback();
return (null, string.Join(" ", erroresItems));
}
foreach (var itemDto in createBulkDto.Items)
{
if (itemDto.IdPublicacion == 0 && itemDto.CantSalida == 0 && itemDto.CantEntrada == 0 && string.IsNullOrWhiteSpace(itemDto.Observacion)) continue;
if (itemDto.IdPublicacion == 0)
{
if (itemDto.CantSalida > 0 || itemDto.CantEntrada > 0 || !string.IsNullOrWhiteSpace(itemDto.Observacion))
{
erroresItems.Add($"Falta seleccionar la publicación para una de las líneas con cantidades/observación.");
}
continue;
}
var publicacionItem = await _publicacionRepository.GetByIdSimpleAsync(itemDto.IdPublicacion);
bool noEsValidaONoHabilitada = false;
if (publicacionItem == null)
{
noEsValidaONoHabilitada = true;
}
else
{
// Si Habilitada es bool? y NULL significa que toma el DEFAULT 1 (true) de la BD
// entonces solo consideramos error si es explícitamente false.
if (publicacionItem.Habilitada.HasValue && publicacionItem.Habilitada.Value == false)
{
noEsValidaONoHabilitada = true;
}
// Si publicacionItem.Habilitada es null o true, noEsValidaONoHabilitada permanece false.
}
if (noEsValidaONoHabilitada)
{
erroresItems.Add($"Publicación ID {itemDto.IdPublicacion} no es válida o no está habilitada.");
continue;
}
var precioActivo = await _precioRepository.GetActiveByPublicacionAndDateAsync(itemDto.IdPublicacion, createBulkDto.Fecha.Date, transaction);
if (precioActivo == null)
{
string nombrePubParaError = publicacionItem?.Nombre ?? $"ID {itemDto.IdPublicacion}";
erroresItems.Add($"No hay precio definido para '{nombrePubParaError}' en {createBulkDto.Fecha:dd/MM/yyyy}.");
continue;
}
RecargoZona? recargoActivo = null;
// Aquí usamos canillaActual! porque ya verificamos que no es null al inicio del método.
if (canillaActual!.IdZona > 0)
{
recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(itemDto.IdPublicacion, canillaActual.IdZona, createBulkDto.Fecha.Date, transaction);
}
PorcMonCanilla? porcMonActivo = null;
// Aquí usamos canillaActual! porque ya verificamos que no es null al inicio del método.
if (canillaActual!.Accionista)
{
porcMonActivo = await _porcMonCanillaRepository.GetActiveByPublicacionCanillaAndDateAsync(itemDto.IdPublicacion, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, transaction);
if (porcMonActivo == null)
{
// Dentro de este bloque, canillaActual.NomApe es seguro porque Accionista era true.
string nombreCanParaError = canillaActual.NomApe;
string nombrePubParaError = publicacionItem?.Nombre ?? $"Publicación ID {itemDto.IdPublicacion}";
erroresItems.Add($"'{nombreCanParaError}' es accionista pero no tiene %/monto para '{nombrePubParaError}' en {createBulkDto.Fecha:dd/MM/yyyy}.");
continue;
}
}
var nuevoES = new EntradaSalidaCanilla
{
IdPublicacion = itemDto.IdPublicacion,
IdCanilla = createBulkDto.IdCanilla,
Fecha = createBulkDto.Fecha.Date,
CantSalida = itemDto.CantSalida,
CantEntrada = itemDto.CantEntrada,
Observacion = itemDto.Observacion,
IdPrecio = precioActivo.IdPrecio,
IdRecargo = recargoActivo?.IdRecargo ?? 0,
IdPorcMon = porcMonActivo?.IdPorcMon ?? 0,
Liquidado = false,
FechaLiquidado = null,
UserLiq = null
};
var esCreada = await _esCanillaRepository.CreateAsync(nuevoES, idUsuario, transaction);
if (esCreada == null) throw new DataException($"Error al registrar movimiento para Publicación ID {itemDto.IdPublicacion}.");
movimientosCreadosEntidades.Add(esCreada);
}
if (erroresItems.Any())
{
if (transaction.Connection != null) transaction.Rollback();
return (null, string.Join(" ", erroresItems));
}
// CORRECCIÓN PARA CS0019 (línea 394 original):
bool tieneItemsSignificativos = false;
if (createBulkDto.Items != null) // Checkear si Items es null antes de llamar a Any()
{
tieneItemsSignificativos = createBulkDto.Items.Any(i => i.IdPublicacion > 0 &&
(i.CantSalida > 0 || i.CantEntrada > 0 || !string.IsNullOrWhiteSpace(i.Observacion)));
}
if (!movimientosCreadosEntidades.Any() && tieneItemsSignificativos)
{
if (transaction.Connection != null) transaction.Rollback();
return (null, "No se pudo procesar ningún ítem válido con datos significativos.");
}
if (transaction.Connection != null) transaction.Commit();
_logger.LogInformation("Lote de {Count} movimientos Canillita para Canilla ID {IdCanilla} en Fecha {Fecha} creados por Usuario ID {UserId}.",
movimientosCreadosEntidades.Count, createBulkDto.IdCanilla, createBulkDto.Fecha.Date, idUsuario);
var dtosCreados = new List<EntradaSalidaCanillaDto>();
foreach (var entidad in movimientosCreadosEntidades)
{
var dto = await MapToDto(entidad);
if (dto != null) dtosCreados.Add(dto);
}
return (dtosCreados, null);
}
catch (Exception ex)
{
if (transaction.Connection != null) { try { transaction.Rollback(); } catch (Exception exRollback) { _logger.LogError(exRollback, "Error durante rollback de transacción."); } }
_logger.LogError(ex, "Error CrearMovimientosEnLoteAsync para Canilla ID {IdCanilla}, Fecha {Fecha}", createBulkDto.IdCanilla, createBulkDto.Fecha);
return (null, $"Error interno al procesar el lote: {ex.Message}");
}
finally
{
if (connection?.State == ConnectionState.Open)
{
if (connection is System.Data.Common.DbConnection dbConnClose) await dbConnClose.CloseAsync(); else connection.Close();
}
}
}
}
}