230 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			230 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | 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.Globalization; | ||
|  | using System.Linq; | ||
|  | using System.Threading.Tasks; | ||
|  | 
 | ||
|  | namespace GestionIntegral.Api.Services.Distribucion | ||
|  | { | ||
|  |     public class PrecioService : IPrecioService | ||
|  |     { | ||
|  |         private readonly IPrecioRepository _precioRepository; | ||
|  |         private readonly IPublicacionRepository _publicacionRepository; // Para validar IdPublicacion | ||
|  |         private readonly DbConnectionFactory _connectionFactory; | ||
|  |         private readonly ILogger<PrecioService> _logger; | ||
|  | 
 | ||
|  |         public PrecioService( | ||
|  |             IPrecioRepository precioRepository, | ||
|  |             IPublicacionRepository publicacionRepository, | ||
|  |             DbConnectionFactory connectionFactory, | ||
|  |             ILogger<PrecioService> logger) | ||
|  |         { | ||
|  |             _precioRepository = precioRepository; | ||
|  |             _publicacionRepository = publicacionRepository; | ||
|  |             _connectionFactory = connectionFactory; | ||
|  |             _logger = logger; | ||
|  |         } | ||
|  | 
 | ||
|  |         private PrecioDto MapToDto(Precio precio) => new PrecioDto | ||
|  |         { | ||
|  |             IdPrecio = precio.IdPrecio, | ||
|  |             IdPublicacion = precio.IdPublicacion, | ||
|  |             VigenciaD = precio.VigenciaD.ToString("yyyy-MM-dd"), | ||
|  |             VigenciaH = precio.VigenciaH?.ToString("yyyy-MM-dd"), | ||
|  |             Lunes = precio.Lunes, Martes = precio.Martes, Miercoles = precio.Miercoles, | ||
|  |             Jueves = precio.Jueves, Viernes = precio.Viernes, Sabado = precio.Sabado, Domingo = precio.Domingo | ||
|  |         }; | ||
|  | 
 | ||
|  |         public async Task<IEnumerable<PrecioDto>> ObtenerPorPublicacionIdAsync(int idPublicacion) | ||
|  |         { | ||
|  |             var precios = await _precioRepository.GetByPublicacionIdAsync(idPublicacion); | ||
|  |             return precios.Select(MapToDto); | ||
|  |         } | ||
|  |         public async Task<PrecioDto?> ObtenerPorIdAsync(int idPrecio) | ||
|  |         { | ||
|  |             var precio = await _precioRepository.GetByIdAsync(idPrecio); | ||
|  |             return precio == null ? null : MapToDto(precio); | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         public async Task<(PrecioDto? Precio, string? Error)> CrearAsync(CreatePrecioDto createDto, int idUsuario) | ||
|  |         { | ||
|  |             if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null) | ||
|  |                 return (null, "La publicación especificada no existe."); | ||
|  | 
 | ||
|  |             // Validar que no haya solapamiento de fechas o que VigenciaD sea lógica | ||
|  |             // Un precio no puede empezar antes que el VigenciaH de un precio anterior no cerrado. | ||
|  |             // Y no puede empezar en una fecha donde ya existe un precio activo para esa publicación. | ||
|  |             var precioActivoEnFecha = await _precioRepository.GetActiveByPublicacionAndDateAsync(createDto.IdPublicacion, createDto.VigenciaD); | ||
|  |             if (precioActivoEnFecha != null) | ||
|  |             { | ||
|  |                 return (null, $"Ya existe un período de precios activo para esta publicación en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Primero debe cerrar el período anterior."); | ||
|  |             } | ||
|  | 
 | ||
|  | 
 | ||
|  |             var nuevoPrecio = new Precio | ||
|  |             { | ||
|  |                 IdPublicacion = createDto.IdPublicacion, | ||
|  |                 VigenciaD = createDto.VigenciaD.Date, // Asegurar que solo sea fecha | ||
|  |                 VigenciaH = null, // Se establece al crear el siguiente o al cerrar manualmente | ||
|  |                 Lunes = createDto.Lunes ?? 0, Martes = createDto.Martes ?? 0, Miercoles = createDto.Miercoles ?? 0, | ||
|  |                 Jueves = createDto.Jueves ?? 0, Viernes = createDto.Viernes ?? 0, Sabado = createDto.Sabado ?? 0, Domingo = createDto.Domingo ?? 0 | ||
|  |             }; | ||
|  | 
 | ||
|  |             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 | ||
|  |             { | ||
|  |                 // 1. Buscar el precio anterior activo para esta publicación que no tenga VigenciaH | ||
|  |                 var precioAnterior = await _precioRepository.GetPreviousActivePriceAsync(createDto.IdPublicacion, nuevoPrecio.VigenciaD, transaction); | ||
|  | 
 | ||
|  |                 if (precioAnterior != null) | ||
|  |                 { | ||
|  |                     if (precioAnterior.VigenciaD >= nuevoPrecio.VigenciaD) | ||
|  |                     { | ||
|  |                         transaction.Rollback(); | ||
|  |                         return (null, $"La fecha de inicio del nuevo precio ({nuevoPrecio.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último precio vigente ({precioAnterior.VigenciaD:dd/MM/yyyy})."); | ||
|  |                     } | ||
|  |                     // 2. Si existe, actualizar su VigenciaH al día anterior de la VigenciaD del nuevo precio | ||
|  |                     precioAnterior.VigenciaH = nuevoPrecio.VigenciaD.AddDays(-1); | ||
|  |                     bool actualizado = await _precioRepository.UpdateAsync(precioAnterior, idUsuario, transaction); // Usar un idUsuario de sistema/auditoría para esta acción automática si es necesario | ||
|  |                     if (!actualizado) | ||
|  |                     { | ||
|  |                         throw new DataException("No se pudo cerrar el período de precio anterior."); | ||
|  |                     } | ||
|  |                      _logger.LogInformation("Precio anterior ID {IdPrecioAnterior} cerrado con VigenciaH {VigenciaH}.", precioAnterior.IdPrecio, precioAnterior.VigenciaH); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 // 3. Crear el nuevo registro de precio | ||
|  |                 var precioCreado = await _precioRepository.CreateAsync(nuevoPrecio, idUsuario, transaction); | ||
|  |                 if (precioCreado == null) throw new DataException("Error al crear el nuevo precio."); | ||
|  | 
 | ||
|  |                 transaction.Commit(); | ||
|  |                 _logger.LogInformation("Precio ID {IdPrecio} creado para Publicación ID {IdPublicacion} por Usuario ID {IdUsuario}.", precioCreado.IdPrecio, precioCreado.IdPublicacion, idUsuario); | ||
|  |                 return (MapToDto(precioCreado), null); | ||
|  |             } | ||
|  |             catch (Exception ex) | ||
|  |             { | ||
|  |                 try { transaction.Rollback(); } catch { } | ||
|  |                 _logger.LogError(ex, "Error CrearAsync Precio para Publicación ID {IdPublicacion}", createDto.IdPublicacion); | ||
|  |                 return (null, $"Error interno al crear el precio: {ex.Message}"); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPrecio, UpdatePrecioDto 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 precioExistente = await _precioRepository.GetByIdAsync(idPrecio); // Obtener dentro de la TX por si acaso | ||
|  |                 if (precioExistente == null) return (false, "Período de precio no encontrado."); | ||
|  | 
 | ||
|  |                 // En una actualización, principalmente actualizamos los montos de los días. | ||
|  |                 // VigenciaH se puede actualizar para cerrar un período explícitamente. | ||
|  |                 // No se permite cambiar IdPublicacion ni VigenciaD aquí. | ||
|  |                 // Si VigenciaH se establece, debe ser >= VigenciaD | ||
|  |                 if (updateDto.VigenciaH.HasValue && updateDto.VigenciaH.Value.Date < precioExistente.VigenciaD.Date) | ||
|  |                 { | ||
|  |                     return (false, "La Vigencia Hasta no puede ser anterior a la Vigencia Desde."); | ||
|  |                 } | ||
|  |                 // Adicional: si se establece VigenciaH, verificar que no haya precios posteriores que se solapen | ||
|  |                 if (updateDto.VigenciaH.HasValue) | ||
|  |                 { | ||
|  |                     var preciosPosteriores = await _precioRepository.GetByPublicacionIdAsync(precioExistente.IdPublicacion); | ||
|  |                     if (preciosPosteriores.Any(p => p.IdPrecio != idPrecio && p.VigenciaD.Date <= updateDto.VigenciaH.Value.Date && p.VigenciaD.Date > precioExistente.VigenciaD.Date )) | ||
|  |                     { | ||
|  |                          return (false, "No se puede cerrar este período porque existen períodos de precios posteriores que se solaparían. Elimine o ajuste los períodos posteriores primero."); | ||
|  |                     } | ||
|  |                 } | ||
|  | 
 | ||
|  | 
 | ||
|  |                 precioExistente.Lunes = updateDto.Lunes ?? precioExistente.Lunes; | ||
|  |                 precioExistente.Martes = updateDto.Martes ?? precioExistente.Martes; | ||
|  |                 precioExistente.Miercoles = updateDto.Miercoles ?? precioExistente.Miercoles; | ||
|  |                 precioExistente.Jueves = updateDto.Jueves ?? precioExistente.Jueves; | ||
|  |                 precioExistente.Viernes = updateDto.Viernes ?? precioExistente.Viernes; | ||
|  |                 precioExistente.Sabado = updateDto.Sabado ?? precioExistente.Sabado; | ||
|  |                 precioExistente.Domingo = updateDto.Domingo ?? precioExistente.Domingo; | ||
|  |                 if (updateDto.VigenciaH.HasValue) // Solo actualizar VigenciaH si se proporciona | ||
|  |                 { | ||
|  |                     precioExistente.VigenciaH = updateDto.VigenciaH.Value.Date; | ||
|  |                 } | ||
|  | 
 | ||
|  | 
 | ||
|  |                 var actualizado = await _precioRepository.UpdateAsync(precioExistente, idUsuario, transaction); | ||
|  |                 if (!actualizado) throw new DataException("Error al actualizar el período de precio."); | ||
|  | 
 | ||
|  |                 transaction.Commit(); | ||
|  |                 _logger.LogInformation("Precio ID {IdPrecio} actualizado por Usuario ID {IdUsuario}.", idPrecio, idUsuario); | ||
|  |                 return (true, null); | ||
|  |             } | ||
|  |             catch (KeyNotFoundException) { try { transaction.Rollback(); } catch {} return (false, "Período de precio no encontrado.");} | ||
|  |             catch (Exception ex) | ||
|  |             { | ||
|  |                 try { transaction.Rollback(); } catch {} | ||
|  |                 _logger.LogError(ex, "Error ActualizarAsync Precio ID: {IdPrecio}", idPrecio); | ||
|  |                 return (false, $"Error interno al actualizar el período de precio: {ex.Message}"); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public async Task<(bool Exito, string? Error)> EliminarAsync(int idPrecio, 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 precioAEliminar = await _precioRepository.GetByIdAsync(idPrecio); | ||
|  |                 if (precioAEliminar == null) return (false, "Período de precio no encontrado."); | ||
|  | 
 | ||
|  |                 // Lógica de ajuste de VigenciaH del período anterior si este que se elimina era el "último" abierto. | ||
|  |                 // Si el precio a eliminar tiene un VigenciaH == null (estaba activo indefinidamente) | ||
|  |                 // Y existe un precio anterior para la misma publicación. | ||
|  |                 // Entonces, el VigenciaH de ese precio anterior debe volver a ser NULL. | ||
|  |                 if (precioAEliminar.VigenciaH == null) | ||
|  |                 { | ||
|  |                     var todosLosPreciosPub = (await _precioRepository.GetByPublicacionIdAsync(precioAEliminar.IdPublicacion)) | ||
|  |                                             .OrderByDescending(p => p.VigenciaD).ToList(); | ||
|  |                      | ||
|  |                     var indiceActual = todosLosPreciosPub.FindIndex(p=> p.IdPrecio == idPrecio); | ||
|  |                     if(indiceActual != -1 && (indiceActual + 1) < todosLosPreciosPub.Count) | ||
|  |                     { | ||
|  |                         var precioAnteriorDirecto = todosLosPreciosPub[indiceActual + 1]; | ||
|  |                         // Solo si el precioAnteriorDirecto fue cerrado por este que se elimina | ||
|  |                         if(precioAnteriorDirecto.VigenciaH.HasValue && precioAnteriorDirecto.VigenciaH.Value.Date == precioAEliminar.VigenciaD.AddDays(-1).Date) | ||
|  |                         { | ||
|  |                             precioAnteriorDirecto.VigenciaH = null; | ||
|  |                             await _precioRepository.UpdateAsync(precioAnteriorDirecto, idUsuario, transaction); // Usar un ID de auditoría adecuado | ||
|  |                              _logger.LogInformation("Precio anterior ID {IdPrecioAnterior} reabierto (VigenciaH a NULL) tras eliminación de Precio ID {IdPrecioEliminado}.", precioAnteriorDirecto.IdPrecio, idPrecio); | ||
|  |                         } | ||
|  |                     } | ||
|  |                 } | ||
|  | 
 | ||
|  | 
 | ||
|  |                 var eliminado = await _precioRepository.DeleteAsync(idPrecio, idUsuario, transaction); | ||
|  |                 if (!eliminado) throw new DataException("Error al eliminar el período de precio."); | ||
|  | 
 | ||
|  |                 transaction.Commit(); | ||
|  |                 _logger.LogInformation("Precio ID {IdPrecio} eliminado por Usuario ID {IdUsuario}.", idPrecio, idUsuario); | ||
|  |                 return (true, null); | ||
|  |             } | ||
|  |             catch (KeyNotFoundException) { try { transaction.Rollback(); } catch {} return (false, "Período de precio no encontrado."); } | ||
|  |             catch (Exception ex) | ||
|  |             { | ||
|  |                 try { transaction.Rollback(); } catch {} | ||
|  |                 _logger.LogError(ex, "Error EliminarAsync Precio ID: {IdPrecio}", idPrecio); | ||
|  |                 return (false, $"Error interno al eliminar el período de precio: {ex.Message}"); | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | } |