using GestionIntegral.Api.Data; using GestionIntegral.Api.Data.Repositories.Impresion; using GestionIntegral.Api.Dtos.Impresion; using GestionIntegral.Api.Models.Impresion; using GestionIntegral.Api.Models.Distribucion; // Para Publicacion, PubliSeccion using GestionIntegral.Api.Data.Repositories.Distribucion; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Threading.Tasks; using GestionIntegral.Api.Dtos.Auditoria; namespace GestionIntegral.Api.Services.Impresion { public class StockBobinaService : IStockBobinaService { private readonly IStockBobinaRepository _stockBobinaRepository; private readonly ITipoBobinaRepository _tipoBobinaRepository; private readonly IPlantaRepository _plantaRepository; private readonly IEstadoBobinaRepository _estadoBobinaRepository; private readonly IPublicacionRepository _publicacionRepository; // Para validar IdPublicacion private readonly IPubliSeccionRepository _publiSeccionRepository; // Para validar IdSeccion private readonly DbConnectionFactory _connectionFactory; private readonly ILogger _logger; public StockBobinaService( IStockBobinaRepository stockBobinaRepository, ITipoBobinaRepository tipoBobinaRepository, IPlantaRepository plantaRepository, IEstadoBobinaRepository estadoBobinaRepository, IPublicacionRepository publicacionRepository, IPubliSeccionRepository publiSeccionRepository, DbConnectionFactory connectionFactory, ILogger logger) { _stockBobinaRepository = stockBobinaRepository; _tipoBobinaRepository = tipoBobinaRepository; _plantaRepository = plantaRepository; _estadoBobinaRepository = estadoBobinaRepository; _publicacionRepository = publicacionRepository; _publiSeccionRepository = publiSeccionRepository; _connectionFactory = connectionFactory; _logger = logger; } // Mapeo complejo porque necesitamos nombres de entidades relacionadas private async Task MapToDto(StockBobina bobina) { if (bobina == null) return null!; // Debería ser manejado por el llamador var tipoBobina = await _tipoBobinaRepository.GetByIdAsync(bobina.IdTipoBobina); var planta = await _plantaRepository.GetByIdAsync(bobina.IdPlanta); var estado = await _estadoBobinaRepository.GetByIdAsync(bobina.IdEstadoBobina); Publicacion? publicacion = null; PubliSeccion? seccion = null; if (bobina.IdPublicacion.HasValue) publicacion = await _publicacionRepository.GetByIdSimpleAsync(bobina.IdPublicacion.Value); if (bobina.IdSeccion.HasValue) seccion = await _publiSeccionRepository.GetByIdAsync(bobina.IdSeccion.Value); // Asume que GetByIdAsync existe return new StockBobinaDto { IdBobina = bobina.IdBobina, IdTipoBobina = bobina.IdTipoBobina, NombreTipoBobina = tipoBobina?.Denominacion ?? "N/A", NroBobina = bobina.NroBobina, Peso = bobina.Peso, IdPlanta = bobina.IdPlanta, NombrePlanta = planta?.Nombre ?? "N/A", IdEstadoBobina = bobina.IdEstadoBobina, NombreEstadoBobina = estado?.Denominacion ?? "N/A", Remito = bobina.Remito, FechaRemito = bobina.FechaRemito.ToString("yyyy-MM-dd"), FechaEstado = bobina.FechaEstado?.ToString("yyyy-MM-dd"), IdPublicacion = bobina.IdPublicacion, NombrePublicacion = publicacion?.Nombre, IdSeccion = bobina.IdSeccion, NombreSeccion = seccion?.Nombre, Obs = bobina.Obs }; } public async Task> ObtenerTodosAsync( int? idTipoBobina, string? nroBobinaFilter, int? idPlanta, int? idEstadoBobina, string? remitoFilter, DateTime? fechaDesde, DateTime? fechaHasta) { var bobinas = await _stockBobinaRepository.GetAllAsync(idTipoBobina, nroBobinaFilter, idPlanta, idEstadoBobina, remitoFilter, fechaDesde, fechaHasta); var dtos = new List(); foreach (var bobina in bobinas) { dtos.Add(await MapToDto(bobina)); } return dtos; } public async Task ObtenerPorIdAsync(int idBobina) { var bobina = await _stockBobinaRepository.GetByIdAsync(idBobina); return bobina == null ? null : await MapToDto(bobina); } public async Task<(StockBobinaDto? Bobina, string? Error)> IngresarBobinaAsync(CreateStockBobinaDto createDto, int idUsuario) { if (await _tipoBobinaRepository.GetByIdAsync(createDto.IdTipoBobina) == null) return (null, "Tipo de bobina inválido."); if (await _plantaRepository.GetByIdAsync(createDto.IdPlanta) == null) return (null, "Planta inválida."); if (await _stockBobinaRepository.GetByNroBobinaAsync(createDto.NroBobina) != null) return (null, $"El número de bobina '{createDto.NroBobina}' ya existe."); var nuevaBobina = new StockBobina { IdTipoBobina = createDto.IdTipoBobina, NroBobina = createDto.NroBobina, Peso = createDto.Peso, IdPlanta = createDto.IdPlanta, IdEstadoBobina = 1, // 1 = Disponible (según contexto previo) Remito = createDto.Remito, FechaRemito = createDto.FechaRemito.Date, FechaEstado = createDto.FechaRemito.Date, // Estado inicial en fecha de remito IdPublicacion = null, IdSeccion = null, Obs = null }; 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 bobinaCreada = await _stockBobinaRepository.CreateAsync(nuevaBobina, idUsuario, transaction); if (bobinaCreada == null) throw new DataException("Error al ingresar la bobina."); transaction.Commit(); _logger.LogInformation("Bobina ID {Id} ingresada por Usuario ID {UserId}.", bobinaCreada.IdBobina, idUsuario); return (await MapToDto(bobinaCreada), null); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error IngresarBobinaAsync: {NroBobina}", createDto.NroBobina); return (null, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> ActualizarDatosBobinaDisponibleAsync(int idBobina, UpdateStockBobinaDto 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 bobinaExistente = await _stockBobinaRepository.GetByIdAsync(idBobina); // Obtener dentro de TX if (bobinaExistente == null) return (false, "Bobina no encontrada."); if (bobinaExistente.IdEstadoBobina != 1) // Solo se pueden editar datos si está "Disponible" return (false, "Solo se pueden modificar los datos de una bobina en estado 'Disponible'."); // Validar unicidad de NroBobina si cambió if (bobinaExistente.NroBobina != updateDto.NroBobina && await _stockBobinaRepository.GetByNroBobinaAsync(updateDto.NroBobina) != null) // Validar fuera de TX o pasar TX al repo { try { transaction.Rollback(); } catch { } // Rollback antes de retornar por validación return (false, $"El nuevo número de bobina '{updateDto.NroBobina}' ya existe."); } if (await _tipoBobinaRepository.GetByIdAsync(updateDto.IdTipoBobina) == null) return (false, "Tipo de bobina inválido."); if (await _plantaRepository.GetByIdAsync(updateDto.IdPlanta) == null) return (false, "Planta inválida."); bobinaExistente.IdTipoBobina = updateDto.IdTipoBobina; bobinaExistente.NroBobina = updateDto.NroBobina; bobinaExistente.Peso = updateDto.Peso; bobinaExistente.IdPlanta = updateDto.IdPlanta; bobinaExistente.Remito = updateDto.Remito; bobinaExistente.FechaRemito = updateDto.FechaRemito.Date; // FechaEstado se mantiene ya que el estado no cambia aquí var actualizado = await _stockBobinaRepository.UpdateAsync(bobinaExistente, idUsuario, transaction, "Datos Actualizados"); if (!actualizado) throw new DataException("Error al actualizar la bobina."); transaction.Commit(); return (true, null); } catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); } catch (Exception ex) { try { transaction.Rollback(); } catch { } _logger.LogError(ex, "Error ActualizarDatosBobinaDisponibleAsync ID: {IdBobina}", idBobina); return (false, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> CambiarEstadoBobinaAsync(int idBobina, CambiarEstadoBobinaDto cambiarEstadoDto, 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 bobina = await _stockBobinaRepository.GetByIdAsync(idBobina); // Obtener dentro de la transacción if (bobina == null) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); } var nuevoEstado = await _estadoBobinaRepository.GetByIdAsync(cambiarEstadoDto.NuevoEstadoId); if (nuevoEstado == null) { try { transaction.Rollback(); } catch { } return (false, "El nuevo estado especificado no es válido."); } // --- INICIO DE VALIDACIONES DE FLUJO DE ESTADOS --- if (bobina.IdEstadoBobina == cambiarEstadoDto.NuevoEstadoId) { try { transaction.Rollback(); } catch { } return (false, "La bobina ya se encuentra en ese estado."); } // Reglas específicas para salir de "Dañada" (ID 3) if (bobina.IdEstadoBobina == 3) // 3 = Dañada { if (cambiarEstadoDto.NuevoEstadoId != 1) // 1 = Disponible { try { transaction.Rollback(); } catch { } return (false, "Una bobina dañada solo puede ser revertida a 'Disponible' (si fue un error)."); } // Si se cambia de Dañada a Disponible, se limpiarán Publicacion y Seccion más abajo. } // Regla para salir de "Utilizada" (ID 2) else if (bobina.IdEstadoBobina == 2) // 2 = En Uso { if (cambiarEstadoDto.NuevoEstadoId == 1) // No se puede volver a Disponible directamente { try { transaction.Rollback(); } catch { } return (false, "Una bobina 'En Uso' no puede volver a 'Disponible' directamente mediante esta acción. Primero debe marcarse como Dañada si es necesario."); } // Si se cambia de Utilizada a cualquier otro estado que no sea Dañada (aunque aquí solo se permite Dañada), // y si el nuevo estado NO es En Uso, también se deberían limpiar Publicacion y Seccion. // La lógica actual ya lo hace si nuevoEstadoId no es 2. } // Regla para salir de "Disponible" (ID 1) else if (bobina.IdEstadoBobina == 1) // 1 = Disponible { // Desde Disponible puede pasar a En Uso (2) o Dañada (3). // El frontend ya debería filtrar esto, pero una validación aquí es buena. if (cambiarEstadoDto.NuevoEstadoId != 2 && cambiarEstadoDto.NuevoEstadoId != 3) { try { transaction.Rollback(); } catch { } return (false, "Desde 'Disponible', solo se puede cambiar a 'En Uso' o 'Dañada'."); } } // --- FIN VALIDACIONES DE FLUJO DE ESTADOS --- bobina.IdEstadoBobina = cambiarEstadoDto.NuevoEstadoId; bobina.FechaEstado = cambiarEstadoDto.FechaCambioEstado.Date; bobina.Obs = cambiarEstadoDto.Obs ?? bobina.Obs; if (cambiarEstadoDto.NuevoEstadoId == 2) // 2 = "En Uso" { if (!cambiarEstadoDto.IdPublicacion.HasValue || cambiarEstadoDto.IdPublicacion.Value <= 0) { try { transaction.Rollback(); } catch { } return (false, "Para el estado 'En Uso', se requiere Publicación."); } if (!cambiarEstadoDto.IdSeccion.HasValue || cambiarEstadoDto.IdSeccion.Value <= 0) { try { transaction.Rollback(); } catch { } return (false, "Para el estado 'En Uso', se requiere Sección."); } // Validar existencia de Publicación y Sección var publicacion = await _publicacionRepository.GetByIdSimpleAsync(cambiarEstadoDto.IdPublicacion.Value); if (publicacion == null) { try { transaction.Rollback(); } catch { } return (false, "Publicación inválida."); } // Asumiendo que GetByIdAsync en IPubliSeccionRepository valida si la sección pertenece a la publicación también var seccion = await _publiSeccionRepository.GetByIdAsync(cambiarEstadoDto.IdSeccion.Value); if (seccion == null || seccion.IdPublicacion != cambiarEstadoDto.IdPublicacion.Value) { try { transaction.Rollback(); } catch { } return (false, "Sección inválida o no pertenece a la publicación seleccionada."); } bobina.IdPublicacion = cambiarEstadoDto.IdPublicacion.Value; bobina.IdSeccion = cambiarEstadoDto.IdSeccion.Value; } else // Si el nuevo estado NO es "En Uso" (ej. Disponible, Dañada) { bobina.IdPublicacion = null; bobina.IdSeccion = null; // La observación se mantiene o se actualiza según venga en el DTO. // Si se pasa de "Dañada" a "Disponible", el DTO de cambio de estado debe incluir la nueva obs. } var actualizado = await _stockBobinaRepository.UpdateAsync(bobina, idUsuario, transaction, $"Estado: {nuevoEstado.Denominacion}"); if (!actualizado) throw new DataException("Error al cambiar estado de la bobina."); transaction.Commit(); return (true, null); } catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback CambiarEstadoBobinaAsync."); } _logger.LogError(ex, "Error CambiarEstadoBobinaAsync ID: {IdBobina}", idBobina); return (false, $"Error interno: {ex.Message}"); } } public async Task<(bool Exito, string? Error)> EliminarIngresoErroneoAsync(int idBobina, 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 bobina = await _stockBobinaRepository.GetByIdAsync(idBobina); // Obtener dentro de la transacción if (bobina == null) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); } // --- Permitir eliminar si está Disponible (1) o Dañada (3) --- if (bobina.IdEstadoBobina != 1 && bobina.IdEstadoBobina != 3) { try { transaction.Rollback(); } catch { } return (false, "Solo se pueden eliminar ingresos de bobinas que estén en estado 'Disponible' o 'Dañada'."); } var eliminado = await _stockBobinaRepository.DeleteAsync(idBobina, idUsuario, transaction); if (!eliminado) throw new DataException("Error al eliminar la bobina."); transaction.Commit(); return (true, null); } catch (KeyNotFoundException) { try { transaction.Rollback(); } catch { } return (false, "Bobina no encontrada."); } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error en Rollback EliminarIngresoErroneoAsync."); } _logger.LogError(ex, "Error EliminarIngresoErroneoAsync ID: {IdBobina}", idBobina); return (false, $"Error interno: {ex.Message}"); } } public async Task> ObtenerHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModifico, string? tipoModificacion, int? idBobinaAfectada, int? idTipoBobinaFiltro, int? idPlantaFiltro, int? idEstadoBobinaFiltro) { var historialData = await _stockBobinaRepository.GetHistorialDetalladoAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idBobinaAfectada, idTipoBobinaFiltro, idPlantaFiltro, idEstadoBobinaFiltro); return historialData.Select(h => new StockBobinaHistorialDto { Id_Bobina = h.Historial.Id_Bobina, Id_TipoBobina = h.Historial.Id_TipoBobina, NombreTipoBobina = h.NombreTipoBobina ?? "N/A", NroBobina = h.Historial.NroBobina, Peso = h.Historial.Peso, Id_Planta = h.Historial.Id_Planta, NombrePlanta = h.NombrePlanta ?? "N/A", Id_EstadoBobina = h.Historial.Id_EstadoBobina, NombreEstadoBobina = h.NombreEstadoBobina ?? "N/A", Remito = h.Historial.Remito, FechaRemito = h.Historial.FechaRemito, FechaEstado = h.Historial.FechaEstado, Id_Publicacion = h.Historial.Id_Publicacion, NombrePublicacion = h.NombrePublicacion, Id_Seccion = h.Historial.Id_Seccion, NombreSeccion = h.NombreSeccion, Obs = h.Historial.Obs, Id_Usuario = h.Historial.Id_Usuario, NombreUsuarioModifico = h.NombreUsuarioModifico, FechaMod = h.Historial.FechaMod, TipoMod = h.Historial.TipoMod }).ToList(); } } }