389 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			389 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using GestionIntegral.Api.Data;
 | |
| using GestionIntegral.Api.Data.Repositories.Contables;
 | |
| using GestionIntegral.Api.Data.Repositories.Distribucion;
 | |
| using GestionIntegral.Api.Dtos.Distribucion;
 | |
| using GestionIntegral.Api.Models.Distribucion;
 | |
| using Microsoft.Extensions.Logging;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Data;
 | |
| using System.Globalization;
 | |
| using System.Linq;
 | |
| using System.Threading.Tasks;
 | |
| 
 | |
| namespace GestionIntegral.Api.Services.Distribucion
 | |
| {
 | |
|     public class EntradaSalidaDistService : IEntradaSalidaDistService
 | |
|     {
 | |
|         private readonly IEntradaSalidaDistRepository _esRepository;
 | |
|         private readonly IPublicacionRepository _publicacionRepository;
 | |
|         private readonly IDistribuidorRepository _distribuidorRepository;
 | |
|         private readonly IPrecioRepository _precioRepository;
 | |
|         private readonly IRecargoZonaRepository _recargoZonaRepository;
 | |
|         private readonly IPorcPagoRepository _porcPagoRepository;
 | |
|         private readonly ISaldoRepository _saldoRepository;
 | |
|         private readonly IEmpresaRepository _empresaRepository; // Para obtener IdEmpresa de la publicación
 | |
|         private readonly DbConnectionFactory _connectionFactory;
 | |
|         private readonly ILogger<EntradaSalidaDistService> _logger;
 | |
| 
 | |
|         public EntradaSalidaDistService(
 | |
|             IEntradaSalidaDistRepository esRepository,
 | |
|             IPublicacionRepository publicacionRepository,
 | |
|             IDistribuidorRepository distribuidorRepository,
 | |
|             IPrecioRepository precioRepository,
 | |
|             IRecargoZonaRepository recargoZonaRepository,
 | |
|             IPorcPagoRepository porcPagoRepository,
 | |
|             ISaldoRepository saldoRepository,
 | |
|             IEmpresaRepository empresaRepository,
 | |
|             DbConnectionFactory connectionFactory,
 | |
|             ILogger<EntradaSalidaDistService> logger)
 | |
|         {
 | |
|             _esRepository = esRepository;
 | |
|             _publicacionRepository = publicacionRepository;
 | |
|             _distribuidorRepository = distribuidorRepository;
 | |
|             _precioRepository = precioRepository;
 | |
|             _recargoZonaRepository = recargoZonaRepository;
 | |
|             _porcPagoRepository = porcPagoRepository;
 | |
|             _saldoRepository = saldoRepository;
 | |
|             _empresaRepository = empresaRepository;
 | |
|             _connectionFactory = connectionFactory;
 | |
|             _logger = logger;
 | |
|         }
 | |
| 
 | |
|         private async Task<EntradaSalidaDistDto> MapToDto(EntradaSalidaDist es)
 | |
|         {
 | |
|             if (es == null) return null!;
 | |
| 
 | |
|             var publicacionData = await _publicacionRepository.GetByIdAsync(es.IdPublicacion);
 | |
|             var distribuidorData = await _distribuidorRepository.GetByIdAsync(es.IdDistribuidor);
 | |
|             
 | |
|             // Obtener el valor bruto del movimiento
 | |
|             decimal valorBrutoMovimiento = await CalcularMontoMovimiento(
 | |
|                 es.IdPublicacion, es.IdDistribuidor, es.Fecha, es.Cantidad, 
 | |
|                 es.TipoMovimiento, // Pasamos el tipo de movimiento original aquí
 | |
|                 es.IdPrecio, es.IdRecargo, es.IdPorcentaje, distribuidorData.Distribuidor?.IdZona
 | |
|             );
 | |
| 
 | |
|             // Ajustar para el DTO: si es "Entrada", el monto calculado es un crédito (negativo o positivo según convención)
 | |
|             // Para consistencia con el ajuste de saldo, si es Entrada, el MontoCalculado para el DTO puede ser el valor
 | |
|             // que se le "acredita" al distribuidor (o sea, el valor de la mercadería devuelta).
 | |
|             // La lógica de +/- para el saldo ya está en Crear/Actualizar/Eliminar.
 | |
|             // Aquí solo mostramos el valor del movimiento. Si es entrada, es el valor de lo devuelto.
 | |
|             // Si es salida, es el valor de lo que se le factura.
 | |
|             // El método CalcularMonto ya devuelve el monto que el distribuidor DEBE pagar por una SALIDA.
 | |
|             // Para una ENTRADA (devolución), el valor de esa mercadería es el mismo, pero opera en sentido contrario al saldo.
 | |
| 
 | |
|             decimal montoCalculadoParaDto = valorBrutoMovimiento;
 | |
|             // Si queremos que el DTO muestre las entradas como un valor que "reduce la deuda",
 | |
|             // podría ser positivo. Si queremos que refleje el impacto directo en la factura (salidas suman, entradas restan),
 | |
|             // podríamos hacerlo negativo.
 | |
|             // Por ahora, dejaremos que CalcularMontoMovimiento devuelva el valor de una "Salida",
 | |
|             // y si es "Entrada", este mismo valor es el que se acredita.
 | |
|             // La columna `MontoCalculado` en el DTO representará el valor de la transacción.
 | |
| 
 | |
|             return new EntradaSalidaDistDto
 | |
|             {
 | |
|                 IdParte = es.IdParte,
 | |
|                 IdPublicacion = es.IdPublicacion,
 | |
|                 NombrePublicacion = publicacionData.Publicacion?.Nombre ?? "N/A",
 | |
|                 IdEmpresaPublicacion = publicacionData.Publicacion?.IdEmpresa ?? 0,
 | |
|                 NombreEmpresaPublicacion = publicacionData.NombreEmpresa ?? "N/A",
 | |
|                 IdDistribuidor = es.IdDistribuidor,
 | |
|                 NombreDistribuidor = distribuidorData.Distribuidor?.Nombre ?? "N/A",
 | |
|                 Fecha = es.Fecha.ToString("yyyy-MM-dd"),
 | |
|                 TipoMovimiento = es.TipoMovimiento,
 | |
|                 Cantidad = es.Cantidad,
 | |
|                 Remito = es.Remito,
 | |
|                 Observacion = es.Observacion,
 | |
|                 MontoCalculado = montoCalculadoParaDto // Representa el valor de los N ejemplares
 | |
|             };
 | |
|         }
 | |
|         
 | |
|         private async Task<decimal> CalcularMontoMovimiento(int idPublicacion, int idDistribuidor, DateTime fecha, int cantidad, string tipoMovimiento,
 | |
|                                                             int idPrecio, int idRecargo, int idPorcentaje, int? idZonaDistribuidor)
 | |
|         {
 | |
|             // YA NO SE DEVUELVE 0 PARA ENTRADA AQUÍ
 | |
|             // if (tipoMovimiento == "Entrada") return 0; 
 | |
| 
 | |
|             var precioConfig = await _precioRepository.GetByIdAsync(idPrecio);
 | |
|             // Es crucial que idPrecio sea válido y se haya determinado correctamente antes de llamar aquí.
 | |
|             // Si precioConfig es null, se lanzará una excepción abajo, lo cual está bien si es un estado inesperado.
 | |
|             if (precioConfig == null)
 | |
|             {
 | |
|                 _logger.LogError("Configuración de precio ID {IdPrecio} no encontrada al calcular monto para Pub {IdPublicacion}, Dist {IdDistribuidor}, Fecha {Fecha}", idPrecio, idPublicacion, idDistribuidor, fecha);
 | |
|                 // Dependiendo de la regla de negocio, podrías devolver 0 o lanzar una excepción.
 | |
|                 // Si un precio es OBLIGATORIO para cualquier movimiento, lanzar excepción es más apropiado.
 | |
|                 // Si puede haber movimientos sin precio (ej. cortesía que no se factura), entonces 0.
 | |
|                 // En este contexto, un precio es fundamental para el cálculo.
 | |
|                 throw new InvalidOperationException($"Configuración de precio ID {idPrecio} no encontrada. No se puede calcular el monto.");
 | |
|             }
 | |
| 
 | |
| 
 | |
|             decimal precioDia = 0;
 | |
|             DayOfWeek diaSemana = fecha.DayOfWeek;
 | |
|             switch (diaSemana)
 | |
|             {
 | |
|                 case DayOfWeek.Monday: precioDia = precioConfig.Lunes ?? 0; break;
 | |
|                 case DayOfWeek.Tuesday: precioDia = precioConfig.Martes ?? 0; break;
 | |
|                 case DayOfWeek.Wednesday: precioDia = precioConfig.Miercoles ?? 0; break;
 | |
|                 case DayOfWeek.Thursday: precioDia = precioConfig.Jueves ?? 0; break;
 | |
|                 case DayOfWeek.Friday: precioDia = precioConfig.Viernes ?? 0; break;
 | |
|                 case DayOfWeek.Saturday: precioDia = precioConfig.Sabado ?? 0; break;
 | |
|                 case DayOfWeek.Sunday: precioDia = precioConfig.Domingo ?? 0; break;
 | |
|             }
 | |
| 
 | |
|             decimal valorRecargo = 0;
 | |
|             if (idRecargo > 0 && idZonaDistribuidor.HasValue)
 | |
|             {
 | |
|                  var recargoConfig = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(idPublicacion, idZonaDistribuidor.Value, fecha);
 | |
|                 if (recargoConfig != null)
 | |
|                 {
 | |
|                     valorRecargo = recargoConfig.Valor;
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             decimal precioConRecargo = precioDia + valorRecargo;
 | |
|             decimal montoBase = precioConRecargo * cantidad;
 | |
| 
 | |
|             if (idPorcentaje > 0)
 | |
|             {
 | |
|                 var porcConfig = await _porcPagoRepository.GetByIdAsync(idPorcentaje);
 | |
|                 if (porcConfig != null)
 | |
|                 {
 | |
|                     // El porcentaje de pago del distribuidor es lo que ÉL PAGA a la editorial.
 | |
|                     // Entonces, el monto es (precio_tapa_con_recargo * cantidad) * (porcentaje_pago_dist / 100)
 | |
|                     return (montoBase * porcConfig.Porcentaje) / 100;
 | |
|                 }
 | |
|             }
 | |
|             // Si no hay porcentaje de pago específico, se asume que el distribuidor paga el 100% del monto base.
 | |
|             return montoBase;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         public async Task<IEnumerable<EntradaSalidaDistDto>> ObtenerTodosAsync(DateTime? fechaDesde, DateTime? fechaHasta, int? idPublicacion, int? idDistribuidor, string? tipoMovimiento)
 | |
|         {
 | |
|             var movimientos = await _esRepository.GetAllAsync(fechaDesde, fechaHasta, idPublicacion, idDistribuidor, tipoMovimiento);
 | |
|             var dtos = new List<EntradaSalidaDistDto>();
 | |
|             foreach (var mov in movimientos)
 | |
|             {
 | |
|                 dtos.Add(await MapToDto(mov));
 | |
|             }
 | |
|             return dtos;
 | |
|         }
 | |
| 
 | |
|         public async Task<EntradaSalidaDistDto?> ObtenerPorIdAsync(int idParte)
 | |
|         {
 | |
|             var movimiento = await _esRepository.GetByIdAsync(idParte);
 | |
|             return movimiento == null ? null : await MapToDto(movimiento);
 | |
|         }
 | |
| 
 | |
|         public async Task<(EntradaSalidaDistDto? Movimiento, string? Error)> CrearMovimientoAsync(CreateEntradaSalidaDistDto createDto, int idUsuario)
 | |
|         {
 | |
|             var publicacion = await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion);
 | |
|             if (publicacion == null || publicacion.Habilitada != true) return (null, "Publicación no válida o no habilitada.");
 | |
| 
 | |
|             var distribuidor = await _distribuidorRepository.GetByIdSimpleAsync(createDto.IdDistribuidor);
 | |
|             if (distribuidor == null) return (null, "Distribuidor no válido.");
 | |
| 
 | |
|             if (await _esRepository.ExistsByRemitoAndTipoForPublicacionAsync(createDto.Remito, createDto.TipoMovimiento, createDto.IdPublicacion))
 | |
|             {
 | |
|                 return (null, $"Ya existe un movimiento de '{createDto.TipoMovimiento}' con el remito N°{createDto.Remito} para esta publicación.");
 | |
|             }
 | |
| 
 | |
|             // Determinar IDs de Precio, Recargo y Porcentaje activos en la fecha del movimiento
 | |
|             var precioActivo = await _precioRepository.GetActiveByPublicacionAndDateAsync(createDto.IdPublicacion, createDto.Fecha.Date);
 | |
|             if (precioActivo == null) return (null, $"No hay un precio definido para la publicación '{publicacion.Nombre}' en la fecha {createDto.Fecha:dd/MM/yyyy}.");
 | |
| 
 | |
|             RecargoZona? recargoActivo = null;
 | |
|             if (distribuidor.IdZona.HasValue)
 | |
|             {
 | |
|                  recargoActivo = await _recargoZonaRepository.GetActiveByPublicacionZonaAndDateAsync(createDto.IdPublicacion, distribuidor.IdZona.Value, createDto.Fecha.Date);
 | |
|             }
 | |
| 
 | |
|             var porcPagoActivo = await _porcPagoRepository.GetActiveByPublicacionDistribuidorAndDateAsync(createDto.IdPublicacion, createDto.IdDistribuidor, createDto.Fecha.Date);
 | |
| 
 | |
|             var nuevoES = new EntradaSalidaDist
 | |
|             {
 | |
|                 IdPublicacion = createDto.IdPublicacion,
 | |
|                 IdDistribuidor = createDto.IdDistribuidor,
 | |
|                 Fecha = createDto.Fecha.Date,
 | |
|                 TipoMovimiento = createDto.TipoMovimiento,
 | |
|                 Cantidad = createDto.Cantidad,
 | |
|                 Remito = createDto.Remito,
 | |
|                 Observacion = createDto.Observacion,
 | |
|                 IdPrecio = precioActivo.IdPrecio,
 | |
|                 IdRecargo = recargoActivo?.IdRecargo ?? 0, // 0 si no aplica
 | |
|                 IdPorcentaje = porcPagoActivo?.IdPorcentaje ?? 0 // 0 si no aplica
 | |
|             };
 | |
| 
 | |
|             using var connection = _connectionFactory.CreateConnection();
 | |
|             if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
 | |
|             using var transaction = connection.BeginTransaction();
 | |
|             try
 | |
|             {
 | |
|                 var esCreada = await _esRepository.CreateAsync(nuevoES, idUsuario, transaction);
 | |
|                 if (esCreada == null) throw new DataException("Error al registrar el movimiento.");
 | |
| 
 | |
|                 // Afectar Saldo
 | |
|                 decimal montoAfectacion = await CalcularMontoMovimiento(
 | |
|                     esCreada.IdPublicacion, esCreada.IdDistribuidor, esCreada.Fecha, esCreada.Cantidad, esCreada.TipoMovimiento,
 | |
|                     esCreada.IdPrecio, esCreada.IdRecargo, esCreada.IdPorcentaje, distribuidor.IdZona);
 | |
|                 
 | |
|                 // Si es Salida, montoAfectacion es positivo (aumenta deuda). Si es Entrada, es 0 por CalcularMontoMovimiento,
 | |
|                 // pero para el saldo, una Entrada (devolución) debería restar del saldo deudor.
 | |
|                 // Por lo tanto, el monto real a restar del saldo si es Entrada sería el valor de esos ejemplares devueltos.
 | |
|                 if(esCreada.TipoMovimiento == "Entrada")
 | |
|                 {
 | |
|                      // Recalcular como si fuera salida para obtener el valor a acreditar/restar del saldo
 | |
|                     montoAfectacion = await CalcularMontoMovimiento(
 | |
|                         esCreada.IdPublicacion, esCreada.IdDistribuidor, esCreada.Fecha, esCreada.Cantidad, "Salida", // Forzar tipo Salida para cálculo de valor
 | |
|                         esCreada.IdPrecio, esCreada.IdRecargo, esCreada.IdPorcentaje, distribuidor.IdZona);
 | |
|                     montoAfectacion *= -1; // Hacerlo negativo para restar del saldo
 | |
|                 }
 | |
| 
 | |
| 
 | |
|                 bool saldoActualizado = await _saldoRepository.ModificarSaldoAsync("Distribuidores", esCreada.IdDistribuidor, publicacion.IdEmpresa, montoAfectacion, transaction);
 | |
|                 if (!saldoActualizado) throw new DataException("Error al actualizar el saldo del distribuidor.");
 | |
| 
 | |
|                 transaction.Commit();
 | |
|                 _logger.LogInformation("Movimiento ID {Id} creado y saldo afectado por Usuario ID {UserId}.", esCreada.IdParte, idUsuario);
 | |
|                 return (await MapToDto(esCreada), null);
 | |
|             }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 try { transaction.Rollback(); } catch { }
 | |
|                 _logger.LogError(ex, "Error CrearMovimientoAsync para Pub ID {IdPub}", createDto.IdPublicacion);
 | |
|                 return (null, $"Error interno: {ex.Message}");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public async Task<(bool Exito, string? Error)> ActualizarMovimientoAsync(int idParte, UpdateEntradaSalidaDistDto updateDto, int idUsuario)
 | |
|         {
 | |
|              // La actualización de un movimiento que afecta saldos es compleja.
 | |
|              // Si cambia la cantidad, el monto original y el nuevo deben calcularse,
 | |
|              // y la diferencia debe aplicarse al saldo.
 | |
|              // Por ahora, este DTO solo permite cambiar Cantidad y Observacion.
 | |
|              // Cambiar otros campos como Fecha, Publicacion, Distribuidor implicaría recalcular todo
 | |
|              // y posiblemente anular el movimiento original y crear uno nuevo.
 | |
| 
 | |
|             using var connection = _connectionFactory.CreateConnection();
 | |
|             if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
 | |
|             using var transaction = connection.BeginTransaction();
 | |
|             try
 | |
|             {
 | |
|                 var esExistente = await _esRepository.GetByIdAsync(idParte);
 | |
|                 if (esExistente == null) return (false, "Movimiento no encontrado.");
 | |
| 
 | |
|                 var publicacion = await _publicacionRepository.GetByIdSimpleAsync(esExistente.IdPublicacion);
 | |
|                  if (publicacion == null) return (false, "Publicación asociada no encontrada."); // Muy raro
 | |
|                 var distribuidor = await _distribuidorRepository.GetByIdSimpleAsync(esExistente.IdDistribuidor);
 | |
|                  if (distribuidor == null) return (false, "Distribuidor asociado no encontrado.");
 | |
| 
 | |
| 
 | |
|                 // 1. Calcular monto del movimiento original (antes de la actualización)
 | |
|                 decimal montoOriginal = await CalcularMontoMovimiento(
 | |
|                     esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, esExistente.TipoMovimiento,
 | |
|                     esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona);
 | |
|                 if(esExistente.TipoMovimiento == "Entrada") montoOriginal *= -1; // Para revertir
 | |
| 
 | |
|                 // 2. Actualizar la entidad en la BD (esto también guarda en historial)
 | |
|                 var esParaActualizar = new EntradaSalidaDist
 | |
|                 {
 | |
|                     IdParte = esExistente.IdParte,
 | |
|                     IdPublicacion = esExistente.IdPublicacion, // No cambia
 | |
|                     IdDistribuidor = esExistente.IdDistribuidor, // No cambia
 | |
|                     Fecha = esExistente.Fecha, // No cambia
 | |
|                     TipoMovimiento = esExistente.TipoMovimiento, // No cambia
 | |
|                     Cantidad = updateDto.Cantidad, // Nuevo valor
 | |
|                     Remito = esExistente.Remito, // No cambia
 | |
|                     Observacion = updateDto.Observacion, // Nuevo valor
 | |
|                     IdPrecio = esExistente.IdPrecio, // No cambia
 | |
|                     IdRecargo = esExistente.IdRecargo, // No cambia
 | |
|                     IdPorcentaje = esExistente.IdPorcentaje // No cambia
 | |
|                 };
 | |
|                 var actualizado = await _esRepository.UpdateAsync(esParaActualizar, idUsuario, transaction);
 | |
|                 if (!actualizado) throw new DataException("Error al actualizar el movimiento.");
 | |
| 
 | |
|                 // 3. Calcular monto del movimiento nuevo (después de la actualización)
 | |
|                 decimal montoNuevo = await CalcularMontoMovimiento(
 | |
|                     esParaActualizar.IdPublicacion, esParaActualizar.IdDistribuidor, esParaActualizar.Fecha, esParaActualizar.Cantidad, esParaActualizar.TipoMovimiento,
 | |
|                     esParaActualizar.IdPrecio, esParaActualizar.IdRecargo, esParaActualizar.IdPorcentaje, distribuidor.IdZona);
 | |
|                 if(esParaActualizar.TipoMovimiento == "Entrada") montoNuevo *= -1;
 | |
| 
 | |
| 
 | |
|                 // 4. Ajustar saldo con la diferencia
 | |
|                 decimal diferenciaAjusteSaldo = montoNuevo - montoOriginal;
 | |
|                 if (diferenciaAjusteSaldo != 0)
 | |
|                 {
 | |
|                     bool saldoActualizado = await _saldoRepository.ModificarSaldoAsync("Distribuidores", esExistente.IdDistribuidor, publicacion.IdEmpresa, diferenciaAjusteSaldo, transaction);
 | |
|                     if (!saldoActualizado) throw new DataException("Error al ajustar el saldo del distribuidor tras la actualización.");
 | |
|                 }
 | |
| 
 | |
|                 transaction.Commit();
 | |
|                 _logger.LogInformation("Movimiento ID {Id} actualizado y saldo ajustado por Usuario ID {UserId}.", idParte, idUsuario);
 | |
|                 return (true, null);
 | |
|             }
 | |
|             catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Movimiento no encontrado."); }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 try { transaction.Rollback(); } catch { }
 | |
|                 _logger.LogError(ex, "Error ActualizarMovimientoAsync ID: {Id}", idParte);
 | |
|                 return (false, $"Error interno: {ex.Message}");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public async Task<(bool Exito, string? Error)> EliminarMovimientoAsync(int idParte, int idUsuario)
 | |
|         {
 | |
|             using var connection = _connectionFactory.CreateConnection();
 | |
|             if (connection is System.Data.Common.DbConnection dbConn) await dbConn.OpenAsync(); else connection.Open();
 | |
|             using var transaction = connection.BeginTransaction();
 | |
|             try
 | |
|             {
 | |
|                 var esExistente = await _esRepository.GetByIdAsync(idParte);
 | |
|                 if (esExistente == null) return (false, "Movimiento no encontrado.");
 | |
| 
 | |
|                 var publicacion = await _publicacionRepository.GetByIdSimpleAsync(esExistente.IdPublicacion);
 | |
|                 if (publicacion == null) return (false, "Publicación asociada no encontrada.");
 | |
|                 var distribuidor = await _distribuidorRepository.GetByIdSimpleAsync(esExistente.IdDistribuidor);
 | |
|                 if (distribuidor == null) return (false, "Distribuidor asociado no encontrado.");
 | |
| 
 | |
|                 // 1. Calcular el monto del movimiento a eliminar para revertir el saldo
 | |
|                 decimal montoReversion = await CalcularMontoMovimiento(
 | |
|                     esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, esExistente.TipoMovimiento,
 | |
|                     esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona);
 | |
|                 
 | |
|                 // Si es Salida, el monto es positivo, al revertir restamos.
 | |
|                 // Si es Entrada, el monto es 0 (por Calcular...), pero su efecto en saldo fue negativo, al revertir sumamos el valor.
 | |
|                 if(esExistente.TipoMovimiento == "Salida") {
 | |
|                     montoReversion *= -1; // Para restar del saldo lo que se sumó
 | |
|                 } else if (esExistente.TipoMovimiento == "Entrada") {
 | |
|                     // Recalcular el valor como si fuera salida para saber cuánto se restó del saldo
 | |
|                      montoReversion = await CalcularMontoMovimiento(
 | |
|                         esExistente.IdPublicacion, esExistente.IdDistribuidor, esExistente.Fecha, esExistente.Cantidad, "Salida",
 | |
|                         esExistente.IdPrecio, esExistente.IdRecargo, esExistente.IdPorcentaje, distribuidor.IdZona);
 | |
|                     // No se multiplica por -1 aquí, porque el saldo ya lo tiene restado, al eliminar revertimos sumando.
 | |
|                 }
 | |
| 
 | |
| 
 | |
|                 // 2. Eliminar el registro (esto también guarda en historial)
 | |
|                 var eliminado = await _esRepository.DeleteAsync(idParte, idUsuario, transaction);
 | |
|                 if (!eliminado) throw new DataException("Error al eliminar el movimiento.");
 | |
| 
 | |
|                 // 3. Ajustar Saldo
 | |
|                 bool saldoActualizado = await _saldoRepository.ModificarSaldoAsync("Distribuidores", esExistente.IdDistribuidor, publicacion.IdEmpresa, montoReversion, transaction);
 | |
|                 if (!saldoActualizado) throw new DataException("Error al revertir el saldo del distribuidor tras la eliminación.");
 | |
| 
 | |
|                 transaction.Commit();
 | |
|                 _logger.LogInformation("Movimiento ID {Id} eliminado y saldo revertido por Usuario ID {UserId}.", idParte, idUsuario);
 | |
|                 return (true, null);
 | |
|             }
 | |
|             catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Movimiento no encontrado."); }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 try { transaction.Rollback(); } catch { }
 | |
|                 _logger.LogError(ex, "Error EliminarMovimientoAsync ID: {Id}", idParte);
 | |
|                 return (false, $"Error interno: {ex.Message}");
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| } |