""" Service layer for Pedimentos CRUD operations """ import logging from typing import Any, Dict, List, Optional from sqlalchemy import desc from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import selectinload from datetime import datetime from ..dtos.pedimentos import PedimentosCreate, PedimentosUpdate from .pedimento_config_additional import PedimentoConfigAdditionalService from .pedimento_config_calculations import PedimentoConfigCalculationsService from .pedimento_config_parameters import PedimentoConfigParametersService from .pedimento_config_surcharges import PedimentoConfigSurchargesService from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationService from .pedimento_config_updates import PedimentoConfigUpdatesService from .pedimento_customs_offices import PedimentoCustomsOfficesService from .pedimento_dates import PedimentoDatesService from .pedimento_decrementables import PedimentoDecrementablesService from .pedimento_incrementables import PedimentoIncrementablesService from .pedimento_indexes import PedimentoIndexesService from .pedimento_payments import PedimentoPaymentsService from .pedimento_rectification_destination import PedimentoRectificationDestinationService from .pedimento_rectification_origin import PedimentoRectificationOriginService from .pedimento_transport_means import PedimentoTransportMeansService from .pedimento_validation import PedimentoValidationService # Crear tablas relacionadas si existen datos from ..models.pedimentos import Pedimentos from ..models.pedimento_dates import PedimentoDates from ..models.pedimento_decrementables import PedimentoDecrementables from ..models.pedimento_incrementables import PedimentoIncrementables from ..models.pedimento_indexes import PedimentoIndexes from ..models.pedimento_validation import PedimentoValidation from ..models.pedimento_customs_offices import PedimentoCustomsOffices from ..models.pedimento_payments import PedimentoPayments from ..models.pedimento_rectification_destination import PedimentoRectificationDestination from ..models.pedimento_rectification_origin import PedimentoRectificationOrigin from ..models.pedimento_transport_means import PedimentoTransportMeans from ..models.pedimento_config_additional import PedimentoConfigAdditional from ..models.pedimento_config_calculations import PedimentoConfigCalculations from ..models.pedimento_config_parameters import PedimentoConfigParameters from ..models.pedimento_config_surcharges import PedimentoConfigSurcharges from ..models.pedimento_config_update_rectification import PedimentoConfigUpdateRectification from ..models.pedimento_config_updates import PedimentoConfigUpdates logger = logging.getLogger(__name__) class PedimentosService: """Service class for Pedimentos business logic""" @staticmethod def get_all( db: Session, tenant_id: int, company_id: int, skip: int = 0, limit: int = 100, filters: Optional[Dict[str, Any]] = None, ) -> tuple[List[Pedimentos], int]: """ Get all pedimentos for a tenant with pagination and filters Args: db: Database session tenant_id: Tenant ID skip: Number of records to skip limit: Maximum number of records to return filters: Optional filters dict Returns: Tuple of (list of pedimentos, total count) """ query = db.query(Pedimentos).filter( Pedimentos.tenant_id == tenant_id, Pedimentos.company_id == company_id) if filters: if filters.get("status"): query = query.filter(Pedimentos.status == filters["status"]) if filters.get("client_id"): query = query.filter( Pedimentos.client_id == filters["client_id"]) if filters.get("year"): query = query.filter(Pedimentos.year == filters["year"]) total = query.count() # Eager load all relationships for the response schema items = ( query.options( selectinload(Pedimentos.pedimento_dates), selectinload(Pedimentos.pedimento_decrementables), selectinload(Pedimentos.pedimento_incrementables), selectinload(Pedimentos.pedimento_indexes), selectinload(Pedimentos.pedimento_validation), selectinload(Pedimentos.pedimento_customs_offices), selectinload(Pedimentos.pedimento_payments), selectinload(Pedimentos.pedimento_rectification_destination), selectinload(Pedimentos.pedimento_rectification_origin), selectinload(Pedimentos.pedimento_transport_means), selectinload(Pedimentos.pedimento_config_additional), selectinload(Pedimentos.pedimento_config_calculations), selectinload(Pedimentos.pedimento_config_parameters), selectinload(Pedimentos.pedimento_config_surcharges), selectinload(Pedimentos.pedimento_config_update_rectification), selectinload(Pedimentos.pedimento_config_updates), ) .order_by(desc(Pedimentos.created_at)) .offset(skip) .limit(limit) .all() ) return items, total @staticmethod def get_by_id( db: Session, pedimento_id: int, tenant_id: int, company_id: int = None ) -> Optional[Pedimentos]: """ Get a pedimento by ID Args: db: Database session pedimento_id: Pedimento ID tenant_id: Tenant ID company_id: Company ID (optional for backwards compatibility) Returns: Pedimento or None if not found """ query = db.query(Pedimentos).filter( Pedimentos.id == pedimento_id, Pedimentos.tenant_id == tenant_id, Pedimentos.company_id == company_id ) if company_id is not None: query = query.filter(Pedimentos.company_id == company_id) # Eager load all relationships for the response schema query = query.options( selectinload(Pedimentos.pedimento_dates), selectinload(Pedimentos.pedimento_decrementables), selectinload(Pedimentos.pedimento_incrementables), selectinload(Pedimentos.pedimento_indexes), selectinload(Pedimentos.pedimento_validation), selectinload(Pedimentos.pedimento_customs_offices), selectinload(Pedimentos.pedimento_payments), selectinload(Pedimentos.pedimento_rectification_destination), selectinload(Pedimentos.pedimento_rectification_origin), selectinload(Pedimentos.pedimento_transport_means), selectinload(Pedimentos.pedimento_config_additional), selectinload(Pedimentos.pedimento_config_calculations), selectinload(Pedimentos.pedimento_config_parameters), selectinload(Pedimentos.pedimento_config_surcharges), selectinload(Pedimentos.pedimento_config_update_rectification), selectinload(Pedimentos.pedimento_config_updates), ) return query.first() @staticmethod def create( db: Session, pedimento_data: PedimentosCreate, tenant_id: int, company_id: int ) -> Pedimentos: """ Create a new pedimento with related tables Args: db: Database session pedimento_data: Pedimento creation data tenant_id: Tenant ID company_id: Company ID Returns: Created pedimento """ try: # Extraer datos de tablas relacionadas related_data = { 'pedimento_dates': pedimento_data.pedimento_dates, 'pedimento_decrementables': pedimento_data.pedimento_decrementables, 'pedimento_incrementables': pedimento_data.pedimento_incrementables, 'pedimento_indexes': pedimento_data.pedimento_indexes, 'pedimento_validation': pedimento_data.pedimento_validation, 'pedimento_customs_offices': pedimento_data.pedimento_customs_offices, 'pedimento_payments': pedimento_data.pedimento_payments, 'pedimento_rectification_destination': pedimento_data.pedimento_rectification_destination, 'pedimento_rectification_origin': pedimento_data.pedimento_rectification_origin, 'pedimento_transport_means': pedimento_data.pedimento_transport_means, 'pedimento_config_additional': pedimento_data.pedimento_config_additional, 'pedimento_config_calculations': pedimento_data.pedimento_config_calculations, 'pedimento_config_parameters': pedimento_data.pedimento_config_parameters, 'pedimento_config_surcharges': pedimento_data.pedimento_config_surcharges, 'pedimento_config_update_rectification': pedimento_data.pedimento_config_update_rectification, 'pedimento_config_updates': pedimento_data.pedimento_config_updates, } # Crear pedimento principal (excluyendo relaciones) pedimento_dict = pedimento_data.model_dump(exclude={ 'pedimento_dates', 'pedimento_decrementables', 'pedimento_incrementables', 'pedimento_indexes', 'pedimento_validation', 'pedimento_customs_offices', 'pedimento_payments', 'pedimento_rectification_destination', 'pedimento_rectification_origin', 'pedimento_transport_means', 'pedimento_config_additional', 'pedimento_config_calculations', 'pedimento_config_parameters', 'pedimento_config_surcharges', 'pedimento_config_update_rectification', 'pedimento_config_updates' }) pedimento = Pedimentos(**pedimento_dict) pedimento.tenant_id = tenant_id pedimento.company_id = company_id db.add(pedimento) db.flush() # Flush para obtener el ID sin commit # Helper function para crear objetos relacionados def create_related(model_class, data, extra_fields=None): if data or extra_fields: # Inicializar obj_dict desde data si existe, sino como dict vacío obj_dict = data.model_dump() if data else {} # Agregar campos extra si se proporcionan if extra_fields: obj_dict.update(extra_fields) obj = model_class(**obj_dict) obj.pedimento_id = pedimento.id obj.tenant_id = tenant_id obj.company_id = company_id db.add(obj) # Crear PedimentoDates con capture_time automático create_related( PedimentoDates, related_data['pedimento_dates'], extra_fields={'capture_time': datetime.now().time()} ) create_related(PedimentoDecrementables, related_data['pedimento_decrementables']) create_related(PedimentoIncrementables, related_data['pedimento_incrementables']) create_related(PedimentoIndexes, related_data['pedimento_indexes']) create_related(PedimentoValidation, related_data['pedimento_validation']) create_related(PedimentoCustomsOffices, related_data['pedimento_customs_offices']) create_related(PedimentoPayments, related_data['pedimento_payments']) create_related(PedimentoRectificationDestination, related_data['pedimento_rectification_destination']) create_related(PedimentoRectificationOrigin, related_data['pedimento_rectification_origin']) create_related(PedimentoTransportMeans, related_data['pedimento_transport_means']) create_related(PedimentoConfigAdditional, related_data['pedimento_config_additional']) create_related(PedimentoConfigCalculations, related_data['pedimento_config_calculations']) create_related(PedimentoConfigParameters, related_data['pedimento_config_parameters']) create_related(PedimentoConfigSurcharges, related_data['pedimento_config_surcharges']) create_related(PedimentoConfigUpdateRectification, related_data['pedimento_config_update_rectification']) create_related(PedimentoConfigUpdates, related_data['pedimento_config_updates']) db.commit() db.refresh(pedimento) return pedimento except Exception as e: db.rollback() logger.error(f"Error creating pedimento with related data: {e}") raise @staticmethod def update( db: Session, pedimento_id: int, tenant_id: int, pedimento_data: PedimentosUpdate, company_id: int = None ) -> Optional[Pedimentos]: """ Update a pedimento and its related tables Args: db: Database session pedimento_id: Pedimento ID tenant_id: Tenant ID pedimento_data: Updated data company_id: Company ID (optional for backwards compatibility) Returns: Updated pedimento or None if not found """ pedimento = PedimentosService.get_by_id( db, pedimento_id, tenant_id, company_id) if not pedimento: return None try: # Actualizar campos principales del pedimento update_data = pedimento_data.model_dump(exclude_unset=True, exclude={ 'pedimento_dates', 'pedimento_decrementables', 'pedimento_incrementables', 'pedimento_indexes', 'pedimento_validation', 'pedimento_customs_offices', 'pedimento_payments', 'pedimento_rectification_destination', 'pedimento_rectification_origin', 'pedimento_transport_means', 'pedimento_config_additional', 'pedimento_config_calculations', 'pedimento_config_parameters', 'pedimento_config_surcharges', 'pedimento_config_update_rectification', 'pedimento_config_updates' }) for field, value in update_data.items(): setattr(pedimento, field, value) db.flush() # Helper function para actualizar o crear objetos relacionados def update_or_create_related(service_class, model_class, data_attr): # Obtener datos del payload completo (no solo exclude_unset) full_data = pedimento_data.model_dump() if data_attr not in full_data: return data = full_data[data_attr] if not data: return existing = service_class.get_by_pedimento_id( db, pedimento_id, tenant_id) if existing: # Actualizar existente for field, value in data.items(): if hasattr(existing, field): setattr(existing, field, value) else: # Crear nuevo obj = model_class(**data) obj.pedimento_id = pedimento_id obj.tenant_id = tenant_id obj.company_id = company_id db.add(obj) # Actualizar o crear tablas relacionadas update_or_create_related( PedimentoDatesService, PedimentoDates, 'pedimento_dates') update_or_create_related( PedimentoDecrementablesService, PedimentoDecrementables, 'pedimento_decrementables') update_or_create_related( PedimentoIncrementablesService, PedimentoIncrementables, 'pedimento_incrementables') update_or_create_related( PedimentoIndexesService, PedimentoIndexes, 'pedimento_indexes') update_or_create_related( PedimentoValidationService, PedimentoValidation, 'pedimento_validation') update_or_create_related( PedimentoCustomsOfficesService, PedimentoCustomsOffices, 'pedimento_customs_offices') update_or_create_related( PedimentoPaymentsService, PedimentoPayments, 'pedimento_payments') update_or_create_related(PedimentoRectificationDestinationService, PedimentoRectificationDestination, 'pedimento_rectification_destination') update_or_create_related(PedimentoRectificationOriginService, PedimentoRectificationOrigin, 'pedimento_rectification_origin') update_or_create_related( PedimentoTransportMeansService, PedimentoTransportMeans, 'pedimento_transport_means') update_or_create_related(PedimentoConfigAdditionalService, PedimentoConfigAdditional, 'pedimento_config_additional') update_or_create_related(PedimentoConfigCalculationsService, PedimentoConfigCalculations, 'pedimento_config_calculations') update_or_create_related(PedimentoConfigParametersService, PedimentoConfigParameters, 'pedimento_config_parameters') update_or_create_related(PedimentoConfigSurchargesService, PedimentoConfigSurcharges, 'pedimento_config_surcharges') update_or_create_related(PedimentoConfigUpdateRectificationService, PedimentoConfigUpdateRectification, 'pedimento_config_update_rectification') update_or_create_related( PedimentoConfigUpdatesService, PedimentoConfigUpdates, 'pedimento_config_updates') db.commit() db.refresh(pedimento) return pedimento except Exception as e: db.rollback() logger.error(f"Error updating pedimento with related data: {e}") raise @staticmethod def delete(db: Session, pedimento_id: int, tenant_id: int, company_id: int = None) -> bool: """ Delete a pedimento Args: db: Database session pedimento_id: Pedimento ID tenant_id: Tenant ID company_id: Company ID (optional for backwards compatibility) Returns: True if deleted, False if not found """ pedimento = PedimentosService.get_by_id( db, pedimento_id, tenant_id, company_id) if not pedimento: return False db.delete(pedimento) db.commit() return True