278 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			278 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | 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; | ||
|  | 
 | ||
|  | 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<StockBobinaService> _logger; | ||
|  | 
 | ||
|  |         public StockBobinaService( | ||
|  |             IStockBobinaRepository stockBobinaRepository, | ||
|  |             ITipoBobinaRepository tipoBobinaRepository, | ||
|  |             IPlantaRepository plantaRepository, | ||
|  |             IEstadoBobinaRepository estadoBobinaRepository, | ||
|  |             IPublicacionRepository publicacionRepository, | ||
|  |             IPubliSeccionRepository publiSeccionRepository, | ||
|  |             DbConnectionFactory connectionFactory, | ||
|  |             ILogger<StockBobinaService> 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<StockBobinaDto> 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<IEnumerable<StockBobinaDto>> 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<StockBobinaDto>(); | ||
|  |             foreach (var bobina in bobinas) | ||
|  |             { | ||
|  |                 dtos.Add(await MapToDto(bobina)); | ||
|  |             } | ||
|  |             return dtos; | ||
|  |         } | ||
|  | 
 | ||
|  |         public async Task<StockBobinaDto?> 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); | ||
|  |                 if (bobina == null) return (false, "Bobina no encontrada."); | ||
|  | 
 | ||
|  |                 var nuevoEstado = await _estadoBobinaRepository.GetByIdAsync(cambiarEstadoDto.NuevoEstadoId); | ||
|  |                 if (nuevoEstado == null) return (false, "El nuevo estado especificado no es válido."); | ||
|  | 
 | ||
|  |                 // Validaciones de flujo de estados | ||
|  |                 if (bobina.IdEstadoBobina == cambiarEstadoDto.NuevoEstadoId) | ||
|  |                     return (false, "La bobina ya se encuentra en ese estado."); | ||
|  |                 if (bobina.IdEstadoBobina == 3 && cambiarEstadoDto.NuevoEstadoId != 3) // 3 = Dañada | ||
|  |                     return (false, "Una bobina dañada no puede cambiar de estado."); | ||
|  |                  if (bobina.IdEstadoBobina == 2 && cambiarEstadoDto.NuevoEstadoId == 1) // 2 = En Uso, 1 = Disponible | ||
|  |                     return (false, "Una bobina 'En Uso' no puede volver a 'Disponible' directamente mediante esta acción."); | ||
|  | 
 | ||
|  | 
 | ||
|  |                 bobina.IdEstadoBobina = cambiarEstadoDto.NuevoEstadoId; | ||
|  |                 bobina.FechaEstado = cambiarEstadoDto.FechaCambioEstado.Date; | ||
|  |                 bobina.Obs = cambiarEstadoDto.Obs ?? bobina.Obs; // Mantener obs si no se provee uno nuevo | ||
|  | 
 | ||
|  |                 if (cambiarEstadoDto.NuevoEstadoId == 2) // "En Uso" | ||
|  |                 { | ||
|  |                     if (!cambiarEstadoDto.IdPublicacion.HasValue || !cambiarEstadoDto.IdSeccion.HasValue) | ||
|  |                         return (false, "Para el estado 'En Uso', se requiere Publicación y Sección."); | ||
|  |                     if (await _publicacionRepository.GetByIdSimpleAsync(cambiarEstadoDto.IdPublicacion.Value) == null) | ||
|  |                         return (false, "Publicación inválida."); | ||
|  |                     if (await _publiSeccionRepository.GetByIdAsync(cambiarEstadoDto.IdSeccion.Value) == null) // Asume GetByIdAsync en IPubliSeccionRepository | ||
|  |                         return (false, "Sección inválida."); | ||
|  |                      | ||
|  |                     bobina.IdPublicacion = cambiarEstadoDto.IdPublicacion.Value; | ||
|  |                     bobina.IdSeccion = cambiarEstadoDto.IdSeccion.Value; | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { // Si no es "En Uso", limpiar estos campos | ||
|  |                     bobina.IdPublicacion = null; | ||
|  |                     bobina.IdSeccion = null; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 var actualizado = await _stockBobinaRepository.UpdateAsync(bobina, idUsuario, transaction, $"Estado Cambiado a: {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 { } | ||
|  |                 _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); | ||
|  |                 if (bobina == null) return (false, "Bobina no encontrada."); | ||
|  |                 if (bobina.IdEstadoBobina != 1) // Solo se pueden eliminar las "Disponibles" (ingresos erróneos) | ||
|  |                     return (false, "Solo se pueden eliminar ingresos de bobinas que estén en estado 'Disponible'."); | ||
|  | 
 | ||
|  |                 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 { } | ||
|  |                 _logger.LogError(ex, "Error EliminarIngresoErroneoAsync ID: {IdBobina}", idBobina); | ||
|  |                 return (false, $"Error interno: {ex.Message}"); | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | } |