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 PorcMonCanillaService : IPorcMonCanillaService { private readonly IPorcMonCanillaRepository _porcMonCanillaRepository; private readonly IPublicacionRepository _publicacionRepository; private readonly ICanillaRepository _canillaRepository; // Para validar IdCanilla y obtener nombre private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public PorcMonCanillaService( IPorcMonCanillaRepository porcMonCanillaRepository, IPublicacionRepository publicacionRepository, ICanillaRepository canillaRepository, DbConnectionFactory connectionFactory, ILogger logger) { _porcMonCanillaRepository = porcMonCanillaRepository; _publicacionRepository = publicacionRepository; _canillaRepository = canillaRepository; _connectionFactory = connectionFactory; _logger = logger; } private PorcMonCanillaDto MapToDto((PorcMonCanilla Item, string NomApeCanilla) data) => new PorcMonCanillaDto { IdPorcMon = data.Item.IdPorcMon, IdPublicacion = data.Item.IdPublicacion, IdCanilla = data.Item.IdCanilla, NomApeCanilla = data.NomApeCanilla, VigenciaD = data.Item.VigenciaD.ToString("yyyy-MM-dd"), VigenciaH = data.Item.VigenciaH?.ToString("yyyy-MM-dd"), PorcMon = data.Item.PorcMon, EsPorcentaje = data.Item.EsPorcentaje }; private async Task MapToDtoWithLookup(PorcMonCanilla? item) { if (item == null) return null; // Si el item es null, devuelve null var canillaData = await _canillaRepository.GetByIdAsync(item.IdCanilla); return new PorcMonCanillaDto { IdPorcMon = item.IdPorcMon, IdPublicacion = item.IdPublicacion, IdCanilla = item.IdCanilla, NomApeCanilla = canillaData.Canilla?.NomApe ?? "Canillita Desconocido", VigenciaD = item.VigenciaD.ToString("yyyy-MM-dd"), VigenciaH = item.VigenciaH?.ToString("yyyy-MM-dd"), PorcMon = item.PorcMon, EsPorcentaje = item.EsPorcentaje }; } public async Task> ObtenerPorPublicacionIdAsync(int idPublicacion) { var data = await _porcMonCanillaRepository.GetByPublicacionIdAsync(idPublicacion); // Filtrar los nulos que MapToDto podría devolver (aunque no debería en este caso si GetAllWithProfileNameAsync no devuelve usuarios nulos en la tupla) return data.Select(MapToDto).Where(dto => dto != null).Select(dto => dto!); } public async Task ObtenerPorIdAsync(int idPorcMon) { var item = await _porcMonCanillaRepository.GetByIdAsync(idPorcMon); return await MapToDtoWithLookup(item); } public async Task<(PorcMonCanillaDto? Item, string? Error)> CrearAsync(CreatePorcMonCanillaDto createDto, int idUsuario) { if (await _publicacionRepository.GetByIdSimpleAsync(createDto.IdPublicacion) == null) return (null, "La publicación especificada no existe."); var canillaData = await _canillaRepository.GetByIdAsync(createDto.IdCanilla); if (canillaData.Canilla == null) // GetByIdAsync devuelve una tupla return (null, "El canillita especificado no existe o no está activo."); // Validar que solo canillitas accionistas pueden tener porcentaje/monto (o la regla de negocio que aplique) // Por ejemplo, si solo los Accionistas pueden tener esta configuración: // if (!canillaData.Canilla.Accionista) { // return (null, "Solo los canillitas accionistas pueden tener un porcentaje/monto de pago configurado."); // } var itemActivo = await _porcMonCanillaRepository.GetActiveByPublicacionCanillaAndDateAsync(createDto.IdPublicacion, createDto.IdCanilla, createDto.VigenciaD.Date); if (itemActivo != null) { return (null, $"Ya existe un porcentaje/monto activo para esta publicación y canillita en la fecha {createDto.VigenciaD:dd/MM/yyyy}. Cierre el período anterior."); } var nuevoItem = new PorcMonCanilla { IdPublicacion = createDto.IdPublicacion, IdCanilla = createDto.IdCanilla, VigenciaD = createDto.VigenciaD.Date, VigenciaH = null, PorcMon = createDto.PorcMon, EsPorcentaje = createDto.EsPorcentaje }; 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 itemAnterior = await _porcMonCanillaRepository.GetPreviousActiveAsync(createDto.IdPublicacion, createDto.IdCanilla, nuevoItem.VigenciaD, transaction); if (itemAnterior != null) { if (itemAnterior.VigenciaD.Date >= nuevoItem.VigenciaD.Date) { transaction.Rollback(); return (null, $"La fecha de inicio ({nuevoItem.VigenciaD:dd/MM/yyyy}) no puede ser anterior o igual a la del último período vigente ({itemAnterior.VigenciaD:dd/MM/yyyy})."); } itemAnterior.VigenciaH = nuevoItem.VigenciaD.AddDays(-1); await _porcMonCanillaRepository.UpdateAsync(itemAnterior, idUsuario, transaction); } var itemCreado = await _porcMonCanillaRepository.CreateAsync(nuevoItem, idUsuario, transaction); if (itemCreado == null) throw new DataException("Error al crear el registro."); transaction.Commit(); _logger.LogInformation("PorcMonCanilla ID {Id} creado por Usuario ID {UserId}.", itemCreado.IdPorcMon, idUsuario); return (await MapToDtoWithLookup(itemCreado), null); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error CrearAsync PorcMonCanilla para Pub ID {IdPub}, Canilla ID {IdCan}", createDto.IdPublicacion, createDto.IdCanilla); return (null, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> ActualizarAsync(int idPorcMon, UpdatePorcMonCanillaDto 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 itemExistente = await _porcMonCanillaRepository.GetByIdAsync(idPorcMon); if (itemExistente == null) return (false, "Registro de porcentaje/monto no encontrado."); if (updateDto.VigenciaH.HasValue && updateDto.VigenciaH.Value.Date < itemExistente.VigenciaD.Date) return (false, "Vigencia Hasta no puede ser anterior a Vigencia Desde."); if (updateDto.VigenciaH.HasValue) { var itemsPubCanillaData = await _porcMonCanillaRepository.GetByPublicacionIdAsync(itemExistente.IdPublicacion); var itemsPosteriores = itemsPubCanillaData .Where(i => i.Item.IdCanilla == itemExistente.IdCanilla && i.Item.IdPorcMon != idPorcMon && i.Item.VigenciaD.Date <= updateDto.VigenciaH.Value.Date && i.Item.VigenciaD.Date > itemExistente.VigenciaD.Date); if(itemsPosteriores.Any()) { return (false, "No se puede cerrar este período porque existen configuraciones posteriores para este canillita que se solaparían."); } } itemExistente.PorcMon = updateDto.PorcMon; itemExistente.EsPorcentaje = updateDto.EsPorcentaje; if (updateDto.VigenciaH.HasValue) { itemExistente.VigenciaH = updateDto.VigenciaH.Value.Date; } var actualizado = await _porcMonCanillaRepository.UpdateAsync(itemExistente, idUsuario, transaction); if (!actualizado) throw new DataException("Error al actualizar registro."); transaction.Commit(); _logger.LogInformation("PorcMonCanilla ID {Id} actualizado por Usuario ID {UserId}.", idPorcMon, idUsuario); return (true, null); } catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Registro no encontrado."); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error ActualizarAsync PorcMonCanilla ID: {Id}", idPorcMon); return (false, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> EliminarAsync(int idPorcMon, 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 itemAEliminar = await _porcMonCanillaRepository.GetByIdAsync(idPorcMon); if (itemAEliminar == null) return (false, "Registro no encontrado."); if (itemAEliminar.VigenciaH == null) { var todosItemsPubCanillaData = await _porcMonCanillaRepository.GetByPublicacionIdAsync(itemAEliminar.IdPublicacion); var todosItemsPubCanilla = todosItemsPubCanillaData .Where(i => i.Item.IdCanilla == itemAEliminar.IdCanilla) .Select(i => i.Item) .OrderByDescending(i => i.VigenciaD).ToList(); var indiceActual = todosItemsPubCanilla.FindIndex(i => i.IdPorcMon == idPorcMon); if(indiceActual != -1 && (indiceActual + 1) < todosItemsPubCanilla.Count) { var itemAnteriorDirecto = todosItemsPubCanilla[indiceActual + 1]; if(itemAnteriorDirecto.VigenciaH.HasValue && itemAnteriorDirecto.VigenciaH.Value.Date == itemAEliminar.VigenciaD.AddDays(-1).Date) { itemAnteriorDirecto.VigenciaH = null; await _porcMonCanillaRepository.UpdateAsync(itemAnteriorDirecto, idUsuario, transaction); } } } var eliminado = await _porcMonCanillaRepository.DeleteAsync(idPorcMon, idUsuario, transaction); if (!eliminado) throw new DataException("Error al eliminar registro."); transaction.Commit(); _logger.LogInformation("PorcMonCanilla ID {Id} eliminado por Usuario ID {UserId}.", idPorcMon, idUsuario); return (true, null); } catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Registro no encontrado."); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error EliminarAsync PorcMonCanilla ID: {Id}", idPorcMon); return (false, $"Error interno: {ex.Message}"); } } } }