using GestionIntegral.Api.Dtos.Empresas; // Para los DTOs (EmpresaDto, CreateEmpresaDto, UpdateEmpresaDto) using Microsoft.Extensions.Logging; // Para ILogger using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Data; using GestionIntegral.Api.Data; using GestionIntegral.Api.Models.Distribucion; using GestionIntegral.Api.Data.Repositories.Distribucion; using GestionIntegral.Api.Data.Repositories.Contables; using GestionIntegral.Api.Dtos.Auditoria; // Para IDbTransaction, ConnectionState namespace GestionIntegral.Api.Services.Distribucion { public class EmpresaService : IEmpresaService { private readonly IEmpresaRepository _empresaRepository; private readonly ISaldoRepository _saldoRepository; private readonly DbConnectionFactory _connectionFactory; // Para manejar la transacción private readonly ILogger _logger; public EmpresaService( IEmpresaRepository empresaRepository, ISaldoRepository saldoRepository, DbConnectionFactory connectionFactory, ILogger logger) { _empresaRepository = empresaRepository; _saldoRepository = saldoRepository; _connectionFactory = connectionFactory; _logger = logger; } public async Task> ObtenerTodasAsync(string? nombreFilter, string? detalleFilter) { // El repositorio ya devuelve solo las activas si es necesario var empresas = await _empresaRepository.GetAllAsync(nombreFilter, detalleFilter); // Mapeo Entidad -> DTO return empresas.Select(e => new EmpresaDto { IdEmpresa = e.IdEmpresa, Nombre = e.Nombre, Detalle = e.Detalle }); } public async Task> ObtenerParaDropdown() { // El repositorio ya devuelve solo las activas si es necesario var empresas = await _empresaRepository.GetAllDropdownAsync(); // Mapeo Entidad -> DTO return empresas.Select(e => new EmpresaDropdownDto { IdEmpresa = e.IdEmpresa, Nombre = e.Nombre }); } public async Task ObtenerPorIdAsync(int id) { // El repositorio ya devuelve solo las activas si es necesario var empresa = await _empresaRepository.GetByIdAsync(id); if (empresa == null) return null; // Mapeo Entidad -> DTO return new EmpresaDto { IdEmpresa = empresa.IdEmpresa, Nombre = empresa.Nombre, Detalle = empresa.Detalle }; } public async Task ObtenerLookupPorIdAsync(int id) { // El repositorio ya devuelve solo las activas si es necesario var empresa = await _empresaRepository.ObtenerLookupPorIdAsync(id); if (empresa == null) return null; // Mapeo Entidad -> DTO return new EmpresaLookupDto { IdEmpresa = empresa.IdEmpresa, Nombre = empresa.Nombre }; } public async Task<(EmpresaDto? Empresa, string? Error)> CrearAsync(CreateEmpresaDto createDto, int idUsuario) { // Validación de negocio: Nombre duplicado if (await _empresaRepository.ExistsByNameAsync(createDto.Nombre)) { return (null, "El nombre de la empresa ya existe."); } var nuevaEmpresa = new Empresa { Nombre = createDto.Nombre, Detalle = createDto.Detalle }; // --- Transacción --- using (var connection = _connectionFactory.CreateConnection()) { // Abrir conexión de forma asíncrona if (connection is System.Data.Common.DbConnection dbConnection) { await dbConnection.OpenAsync(); } else { connection.Open(); // Fallback síncrono } using (var transaction = connection.BeginTransaction()) { try { // 1. Crear Empresa (Repo maneja su historial dentro de esta transacción) var empresaCreada = await _empresaRepository.CreateAsync(nuevaEmpresa, idUsuario, transaction); if (empresaCreada == null) { throw new InvalidOperationException("No se pudo crear la empresa en el repositorio."); } // 2. Obtener IDs de Distribuidores var distribuidoresIds = await _saldoRepository.GetAllDistribuidorIdsAsync(); // No necesita transacción si solo lee // 3. Crear Saldos Iniciales (CERO) para cada distribuidor en esta nueva empresa foreach (var idDistribuidor in distribuidoresIds) { bool saldoCreado = await _saldoRepository.CreateSaldoInicialAsync("Distribuidores", idDistribuidor, empresaCreada.IdEmpresa, transaction); if (!saldoCreado) { throw new InvalidOperationException($"Falló al crear saldo inicial para distribuidor {idDistribuidor} y nueva empresa {empresaCreada.IdEmpresa}."); } _logger.LogInformation("Saldo inicial creado para Distribuidor ID {IdDistribuidor}, Empresa ID {IdEmpresa}", idDistribuidor, empresaCreada.IdEmpresa); } transaction.Commit(); var empresaDto = new EmpresaDto { IdEmpresa = empresaCreada.IdEmpresa, Nombre = empresaCreada.Nombre, Detalle = empresaCreada.Detalle }; _logger.LogInformation("Empresa ID {IdEmpresa} creada exitosamente por Usuario ID {IdUsuario}.", empresaCreada.IdEmpresa, idUsuario); return (empresaDto, null); // Éxito } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error al intentar hacer rollback en CrearAsync Empresa."); } _logger.LogError(ex, "Error en transacción CrearAsync para Empresa. Nombre: {Nombre}", createDto.Nombre); return (null, "Error interno al procesar la creación de la empresa."); } } // La transacción se dispone aquí (y cierra la conexión si no hubo commit/rollback explícito, aunque ya lo hacemos) } // La conexión se cierra/dispone aquí // --- Fin Transacción --- } public async Task<(bool Exito, string? Error)> ActualizarAsync(int id, UpdateEmpresaDto updateDto, int idUsuario) { var empresaExistente = await _empresaRepository.GetByIdAsync(id); if (empresaExistente == null) { return (false, "Empresa no encontrada."); } if (await _empresaRepository.ExistsByNameAsync(updateDto.Nombre, id)) { return (false, "El nombre de la empresa ya existe para otro registro."); } empresaExistente.Nombre = updateDto.Nombre; empresaExistente.Detalle = updateDto.Detalle; // --- Transacción --- using (var connection = _connectionFactory.CreateConnection()) { if (connection is System.Data.Common.DbConnection dbConnection) { await dbConnection.OpenAsync(); } else { connection.Open(); } using (var transaction = connection.BeginTransaction()) { try { var actualizado = await _empresaRepository.UpdateAsync(empresaExistente, idUsuario, transaction); if (!actualizado) { throw new InvalidOperationException("La actualización en el repositorio de empresas devolvió false."); } transaction.Commit(); _logger.LogInformation("Empresa ID {IdEmpresa} actualizada exitosamente por Usuario ID {IdUsuario}.", id, idUsuario); return (true, null); // Éxito } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error al intentar hacer rollback en ActualizarAsync Empresa."); } _logger.LogError(ex, "Error en transacción ActualizarAsync para Empresa ID: {Id}", id); return (false, "Error interno al actualizar la empresa."); } } } // --- Fin Transacción --- } public async Task<(bool Exito, string? Error)> EliminarAsync(int id, int idUsuario) { // Primero verificamos si existe, incluso inactiva, para evitar errores 404 si no existe var empresaExistente = await _empresaRepository.GetByIdAsync(id); if (empresaExistente == null) { return (false, "Empresa no encontrada."); } // Validación: ¿Está en uso? if (await _empresaRepository.IsInUseAsync(id)) { return (false, "No se puede eliminar. Existen publicaciones relacionadas a la empresa."); } // --- Transacción --- using (var connection = _connectionFactory.CreateConnection()) { if (connection is System.Data.Common.DbConnection dbConnection) { await dbConnection.OpenAsync(); } else { connection.Open(); } using (var transaction = connection.BeginTransaction()) { try { // 1. Eliminar Saldos asociados bool saldosEliminados = await _saldoRepository.DeleteSaldosByEmpresaAsync(id, transaction); // No lanzamos error si saldosEliminados es false, podría no haber tenido saldos. Loggeamos si es necesario. if (!saldosEliminados && await _saldoRepository.CheckIfSaldosExistForEmpresaAsync(id)) // Necesitarías este método en ISaldoRepository { _logger.LogWarning("Se intentó eliminar Empresa ID {IdEmpresa} pero falló la eliminación de saldos asociados.", id); // Decidir si continuar o fallar. Por ahora, continuamos pero loggeamos. // throw new InvalidOperationException("Error al intentar eliminar los saldos asociados a la empresa."); } else if (!saldosEliminados) { _logger.LogInformation("No se encontraron saldos para eliminar de la Empresa ID {IdEmpresa}.", id); } else { _logger.LogInformation("Saldos eliminados para Empresa ID {IdEmpresa}.", id); } // 2. Eliminar Empresa (Repo maneja historial) var eliminado = await _empresaRepository.DeleteAsync(id, idUsuario, transaction); if (!eliminado) { throw new InvalidOperationException("La eliminación en el repositorio de empresas devolvió false."); } transaction.Commit(); _logger.LogInformation("Empresa ID {IdEmpresa} eliminada exitosamente por Usuario ID {IdUsuario}.", id, idUsuario); return (true, null); // Éxito } catch (Exception ex) { try { transaction.Rollback(); } catch (Exception rbEx) { _logger.LogError(rbEx, "Error al intentar hacer rollback en EliminarAsync Empresa."); } _logger.LogError(ex, "Error en transacción EliminarAsync para Empresa ID: {Id}", id); return (false, "Error interno al eliminar la empresa."); } } } } public async Task> ObtenerHistorialAsync( DateTime? fechaDesde, DateTime? fechaHasta, int? idUsuarioModifico, string? tipoModificacion, int? idEmpresaAfectada) { var historialData = await _empresaRepository.GetHistorialAsync(fechaDesde, fechaHasta, idUsuarioModifico, tipoModificacion, idEmpresaAfectada); return historialData.Select(h => new EmpresaHistorialDto { Id_Empresa = h.Historial.Id_Empresa, Nombre = h.Historial.Nombre, Detalle = h.Historial.Detalle, Id_Usuario = h.Historial.Id_Usuario, NombreUsuarioModifico = h.NombreUsuarioModifico, FechaMod = h.Historial.FechaMod, TipoMod = h.Historial.TipoMod }).ToList(); } } }