diff --git a/backend/api/v1/modules/a76/customs_brokers/dto.py b/backend/api/v1/modules/a76/customs_brokers/dto.py index 0caf53e9..07923e14 100644 --- a/backend/api/v1/modules/a76/customs_brokers/dto.py +++ b/backend/api/v1/modules/a76/customs_brokers/dto.py @@ -3,24 +3,65 @@ from typing import Optional from pydantic import BaseModel -class CustomsBrokerDTO(BaseModel): - type: Optional[str] +class CustomsBrokerBaseDTO(BaseModel): + """Base fields for CustomsBroker""" + type: Optional[str] = None + name: Optional[str] = None + address: Optional[str] = None + postal_code: Optional[str] = None + city: Optional[str] = None + state: Optional[str] = None + phone: Optional[str] = None + fax: Optional[str] = None + email: Optional[str] = None + country: Optional[str] = None + tax_id: Optional[str] = None + personal_id: Optional[str] = None + position: Optional[str] = None + license: Optional[str] = None + company: Optional[str] = None + contact: Optional[str] = None + + +class CustomsBrokerCreateDTO(CustomsBrokerBaseDTO): + """Schema for creating a new CustomsBroker""" broker_key: str - name: Optional[str] - address: Optional[str] - postal_code: Optional[str] - city: Optional[str] - state: Optional[str] - phone: Optional[str] - fax: Optional[str] - email: Optional[str] - country: Optional[str] - tax_id: Optional[str] - personal_id: Optional[str] - position: Optional[str] - license: Optional[str] - company: Optional[str] - contact: Optional[str] + + +class CustomsBrokerUpdateDTO(CustomsBrokerBaseDTO): + """Schema for updating an existing CustomsBroker""" + pass + + +class CustomsBrokerResponseDTO(CustomsBrokerBaseDTO): + """Schema for CustomsBroker response""" + broker_key: str + tenant_id: int + company_id: int + + class Config: + from_attributes = True + + +# Legacy DTO for backwards compatibility (if needed elsewhere) +class CustomsBrokerDTO(BaseModel): + type: Optional[str] = None + broker_key: str + name: Optional[str] = None + address: Optional[str] = None + postal_code: Optional[str] = None + city: Optional[str] = None + state: Optional[str] = None + phone: Optional[str] = None + fax: Optional[str] = None + email: Optional[str] = None + country: Optional[str] = None + tax_id: Optional[str] = None + personal_id: Optional[str] = None + position: Optional[str] = None + license: Optional[str] = None + company: Optional[str] = None + contact: Optional[str] = None tenant_id: str company_id: str diff --git a/backend/api/v1/modules/a76/customs_brokers/models.py b/backend/api/v1/modules/a76/customs_brokers/models.py index ee44cd18..2b177204 100644 --- a/backend/api/v1/modules/a76/customs_brokers/models.py +++ b/backend/api/v1/modules/a76/customs_brokers/models.py @@ -1,23 +1,26 @@ from api.v1.common.base_models import TenantScopedMixin from core.database import Base -from sqlalchemy import Column, ForeignKey, ForeignKeyConstraint, Integer, String +from sqlalchemy import Column, ForeignKey, ForeignKeyConstraint, Integer, String, UniqueConstraint, PrimaryKeyConstraint from sqlalchemy.orm import relationship class CustomsBroker(Base, TenantScopedMixin): __tablename__ = "customs_brokers" __table_args__ = ( + PrimaryKeyConstraint("id", name="customs_brokers_pkey"), ForeignKeyConstraint( ["tenant_id"], ["a76.tenants.id"], name="fk_customs_brokers_tenants" ), ForeignKeyConstraint( ["company_id"], ["a76.company.id"], name="fk_customs_brokers_company" ), + UniqueConstraint("broker_key", "tenant_id", "company_id", name="uq_broker_key_tenant_company"), {"schema": "a76"}, ) + id = Column(Integer, primary_key=True) type = Column(String(9), nullable=True) - broker_key = Column(String(5), primary_key=True, nullable=False) + broker_key = Column(String(5), nullable=False) name = Column(String(80), nullable=True) address = Column(String(1500), nullable=True) postal_code = Column(String(15), nullable=True) @@ -44,10 +47,11 @@ class CustomsBroker(Base, TenantScopedMixin): class CustomsBrokerVU(Base): __tablename__ = "customs_brokers_vu" + __table_args__ = {"schema": "a76"} - broker_key = Column( - String(5), - ForeignKey("a76.customs_brokers.broker_key", ondelete="CASCADE"), + customs_broker_id = Column( + Integer, + ForeignKey("a76.customs_brokers.id", ondelete="CASCADE"), primary_key=True, ) certificate_path = Column(String(1499), nullable=True) @@ -75,10 +79,11 @@ class CustomsBrokerVU(Base): class CustomsBrokerPersonnel(Base): __tablename__ = "customs_brokers_personnel" + __table_args__ = {"schema": "a76"} - broker_key = Column( - String(5), - ForeignKey("a76.customs_brokers.broker_key", ondelete="CASCADE"), + customs_broker_id = Column( + Integer, + ForeignKey("a76.customs_brokers.id", ondelete="CASCADE"), primary_key=True, ) line = Column(Integer, primary_key=True, nullable=False) diff --git a/backend/api/v1/modules/a76/customs_brokers/routes.py b/backend/api/v1/modules/a76/customs_brokers/routes.py index 794bd197..b4a756e5 100644 --- a/backend/api/v1/modules/a76/customs_brokers/routes.py +++ b/backend/api/v1/modules/a76/customs_brokers/routes.py @@ -1,43 +1,54 @@ +from typing import Dict, Any +from api.v1.common.tenant_crud_routes import TenantCRUDRoutes from core.database import get_core_db -from fastapi import APIRouter, Depends, HTTPException +from core.security import get_current_user, validate_access_to_resource +from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from . import dto, services +# Create main router router = APIRouter() +# Create CRUD routes for CustomsBroker using TenantCRUDRoutes +customs_broker_crud = TenantCRUDRoutes( + service=services.CustomsBrokerService, + create_schema=dto.CustomsBrokerCreateDTO, + update_schema=dto.CustomsBrokerUpdateDTO, + response_schema=dto.CustomsBrokerResponseDTO, + prefix="/customs-brokers", # No prefix since it's already in the parent router + tags=[], + resource_name="Customs Broker", + id_name="broker_key", + id_type=str, + enable_list=True, # Enable list endpoint with pagination +) -@router.get("/customs-broker/{broker_key}", response_model=dto.CustomsBrokerDTO) -def get_customs_broker(broker_key: str, db: Session = Depends(get_core_db)): - broker = services.CustomsBrokerService.get_by_broker_key(db, broker_key) - if not broker: - raise HTTPException(status_code=404, detail="Customs Broker not found") - return broker +# Include the CRUD routes +router.include_router(customs_broker_crud.router) -@router.post("/customs-broker", response_model=dto.CustomsBrokerDTO) -def create_customs_broker( - broker_data: dto.CustomsBrokerDTO, db: Session = Depends(get_core_db) -): - return services.CustomsBrokerService.create_customs_broker(db, broker_data) - - -@router.delete("/customs-broker/{broker_key}", response_model=dto.CustomsBrokerDTO) -def delete_customs_broker(broker_key: str, db: Session = Depends(get_core_db)): - broker = services.CustomsBrokerService.delete_customs_broker(db, broker_key) - if not broker: - raise HTTPException(status_code=404, detail="Customs Broker not found") - return broker - +# Additional routes for child resources (CustomsBrokerVU and CustomsBrokerPersonnel) +# These remain as manual routes since they have different patterns @router.put( - "/customs-broker-vu/{broker_key}", response_model=dto.CustomsBrokerVUCreateDTO + "/customs-broker-vu/{broker_key}", + response_model=dto.CustomsBrokerVUCreateDTO, ) def update_customs_broker_vu( broker_key: str, vu_data: dto.CustomsBrokerVUCreateDTO, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), ): + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the broker exists and belongs to the tenant/company + broker = services.CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id) + if not broker: + raise HTTPException(status_code=404, detail="Customs Broker not found") + updated_vu = services.CustomsBrokerVUService.update_vu(db, broker_key, vu_data) if not updated_vu: raise HTTPException(status_code=404, detail="Customs Broker VU not found") @@ -52,8 +63,17 @@ def update_customs_broker_personnel( broker_key: str, line: int, personnel_data: dto.CustomsBrokerPersonnelDTO, + company_id: int = Query(..., description="Company ID"), db: Session = Depends(get_core_db), + current_user: Dict[str, Any] = Depends(get_current_user), ): + tenant_id = validate_access_to_resource(db, company_id, current_user) + + # Verify the broker exists and belongs to the tenant/company + broker = services.CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id) + if not broker: + raise HTTPException(status_code=404, detail="Customs Broker not found") + updated_personnel = services.CustomsBrokerPersonnelService.update_personnel( db, broker_key, line, personnel_data ) @@ -62,3 +82,4 @@ def update_customs_broker_personnel( status_code=404, detail="Customs Broker Personnel not found" ) return updated_personnel + diff --git a/backend/api/v1/modules/a76/customs_brokers/services.py b/backend/api/v1/modules/a76/customs_brokers/services.py index ed5d16ea..ea72abe5 100644 --- a/backend/api/v1/modules/a76/customs_brokers/services.py +++ b/backend/api/v1/modules/a76/customs_brokers/services.py @@ -5,28 +5,70 @@ from . import dto, models class CustomsBrokerService: @staticmethod - def get_by_broker_key(db: Session, broker_key: str): + def get_by_id(db: Session, broker_key: str, tenant_id: int, company_id: int): + """Get a customs broker by broker_key with tenant/company validation""" return ( db.query(models.CustomsBroker) - .filter(models.CustomsBroker.broker_key == broker_key) + .filter( + models.CustomsBroker.broker_key == broker_key, + models.CustomsBroker.tenant_id == tenant_id, + models.CustomsBroker.company_id == company_id, + ) .first() ) @staticmethod - def create_customs_broker(db: Session, broker_data: dto.CustomsBrokerDTO): - new_broker = models.CustomsBroker(**broker_data.dict()) + def get_all( + db: Session, + tenant_id: int, + company_id: int, + skip: int = 0, + limit: int = 100, + filters: dict = None, + ): + """Get all customs brokers for a tenant/company with pagination""" + query = db.query(models.CustomsBroker).filter( + models.CustomsBroker.tenant_id == tenant_id, + models.CustomsBroker.company_id == company_id, + ) + + total = query.count() + items = query.offset(skip).limit(limit).all() + return items, total + + @staticmethod + def create(db: Session, broker_data: dto.CustomsBrokerCreateDTO, tenant_id: int, company_id: int): + """Create a new customs broker""" + broker_dict = broker_data.model_dump() + broker_dict["tenant_id"] = tenant_id + broker_dict["company_id"] = company_id + + new_broker = models.CustomsBroker(**broker_dict) db.add(new_broker) db.commit() db.refresh(new_broker) return new_broker @staticmethod - def delete_customs_broker(db: Session, broker_key: str): - broker = CustomsBrokerService.get_by_broker_key(db, broker_key) + def update(db: Session, broker_key: str, tenant_id: int, broker_data: dto.CustomsBrokerUpdateDTO, company_id: int): + """Update an existing customs broker""" + broker = CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id) + if broker: + for key, value in broker_data.model_dump(exclude_unset=True).items(): + setattr(broker, key, value) + db.commit() + db.refresh(broker) + return broker + + @staticmethod + def delete(db: Session, broker_key: str, tenant_id: int, company_id: int): + """Delete a customs broker""" + broker = CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id) if broker: db.delete(broker) db.commit() - return broker + return True + return False class CustomsBrokerVUService: diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py index 9cddc25f..7a89c050 100644 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py +++ b/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py @@ -5,6 +5,23 @@ from typing import Optional from pydantic import BaseModel, ConfigDict, Field +from .pedimento_config_additional import PedimentoConfigAdditionalCreate +from .pedimento_config_calculations import PedimentoConfigCalculationsCreate +from .pedimento_config_parameters import PedimentoConfigParametersCreate +from .pedimento_config_surcharges import PedimentoConfigSurchargesCreate +from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationCreate +from .pedimento_config_updates import PedimentoConfigUpdatesCreate +from .pedimento_customs_offices import PedimentoCustomsOfficesCreate +from .pedimento_dates import PedimentoDatesCreate +from .pedimento_decrementables import PedimentoDecrementablesCreate +from .pedimento_incrementables import PedimentoIncrementablesCreate +from .pedimento_indexes import PedimentoIndexesCreate +from .pedimento_payments import PedimentoPaymentsCreate +from .pedimento_rectification_destination import PedimentoRectificationDestinationCreate +from .pedimento_rectification_origin import PedimentoRectificationOriginCreate +from .pedimento_transport_means import PedimentoTransportMeansCreate +from .pedimento_validation import PedimentoValidationCreate + class OperationType(IntEnum): EXPORTACION = 1 @@ -35,6 +52,22 @@ class PedimentosBase(BaseModel): gross_weight: Optional[Decimal] = Field(None, description="Gross weight") exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate") + pedimento_dates: Optional[PedimentoDatesCreate] = None + pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None + pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None + pedimento_indexes: Optional[PedimentoIndexesCreate] = None + pedimento_validation: Optional[PedimentoValidationCreate] = None + pedimento_customs_offices: Optional[PedimentoCustomsOfficesCreate] = None + pedimento_payments: Optional[PedimentoPaymentsCreate] = None + pedimento_rectification_destination: Optional[PedimentoRectificationDestinationCreate] = None + pedimento_rectification_origin: Optional[PedimentoRectificationOriginCreate] = None + pedimento_transport_means: Optional[PedimentoTransportMeansCreate] = None + pedimento_config_additional: Optional[PedimentoConfigAdditionalCreate] = None + pedimento_config_calculations: Optional[PedimentoConfigCalculationsCreate] = None + pedimento_config_parameters: Optional[PedimentoConfigParametersCreate] = None + pedimento_config_surcharges: Optional[PedimentoConfigSurchargesCreate] = None + pedimento_config_update_rectification: Optional[PedimentoConfigUpdateRectificationCreate] = None + pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None class PedimentosCreate(PedimentosBase): """Schema for creating a new Pedimento""" @@ -48,8 +81,7 @@ class PedimentosCreate(PedimentosBase): operation_type: int = Field(..., description="Operation type") pedimento_type: int = Field(..., description="Pedimento type") regime: str = Field(..., max_length=3, description="Regime") - status: str = Field(..., max_length=30, description="Status") - + status: str = Field(..., max_length=30, description="Status") class PedimentosUpdate(BaseModel): """Schema for updating a Pedimento""" diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py index 4544f65b..ddfca359 100644 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py +++ b/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py @@ -2,13 +2,51 @@ Service layer for Pedimentos CRUD operations """ +import logging from typing import Any, Dict, List, Optional from sqlalchemy import desc from sqlalchemy.orm import Session 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: @@ -36,7 +74,7 @@ class PedimentosService: Returns: Tuple of (list of pedimentos, total count) """ - query = db.query(Pedimentos).filter(Pedimentos.tenant_id == tenant_id) + query = db.query(Pedimentos).filter(Pedimentos.tenant_id == tenant_id, Pedimentos.company_id == company_id) if filters: if filters.get("status"): @@ -70,7 +108,7 @@ class PedimentosService: Pedimento or None if not found """ query = db.query(Pedimentos).filter( - Pedimentos.id == pedimento_id, Pedimentos.tenant_id == tenant_id + Pedimentos.id == pedimento_id, Pedimentos.tenant_id == tenant_id, Pedimentos.company_id == company_id ) if company_id is not None: @@ -83,7 +121,7 @@ class PedimentosService: db: Session, pedimento_data: PedimentosCreate, tenant_id: int, company_id: int ) -> Pedimentos: """ - Create a new pedimento + Create a new pedimento with related tables Args: db: Database session @@ -94,21 +132,87 @@ class PedimentosService: Returns: Created pedimento """ - pedimento = Pedimentos(**pedimento_data.model_dump()) - pedimento.tenant_id = tenant_id - pedimento.company_id = company_id + 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.commit() - db.refresh(pedimento) - return pedimento + db.add(pedimento) + db.flush() # Flush para obtener el ID sin commit + + # Helper function para crear objetos relacionados + def create_related(model_class, data): + if data: + obj_dict = data.model_dump() + obj = model_class(**obj_dict) + obj.pedimento_id = pedimento.id + obj.tenant_id = tenant_id + obj.company_id = company_id + db.add(obj) + + create_related(PedimentoDates, related_data['pedimento_dates']) + 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 + Update a pedimento and its related tables Args: db: Database session @@ -124,13 +228,73 @@ class PedimentosService: if not pedimento: return None - update_data = pedimento_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(pedimento, field, value) + 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.commit() - db.refresh(pedimento) - return pedimento + db.flush() + + # Helper function para actualizar o crear objetos relacionados + def update_or_create_related(service_class, model_class, data_attr): + if not hasattr(pedimento_data, data_attr): + return + + data = getattr(pedimento_data, data_attr) + if not data: + return + + existing = service_class.get_by_pedimento_id(db, pedimento_id, tenant_id) + if existing: + # Actualizar existente + update_dict = data.model_dump(exclude_unset=True) + for field, value in update_dict.items(): + setattr(existing, field, value) + else: + # Crear nuevo + obj_dict = data.model_dump() + obj = model_class(**obj_dict) + 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: @@ -153,5 +317,3 @@ class PedimentosService: db.delete(pedimento) db.commit() return True - db.commit() - return True diff --git a/backend/api/v1/modules/a76/router.py b/backend/api/v1/modules/a76/router.py index 7f443981..8f758c2a 100644 --- a/backend/api/v1/modules/a76/router.py +++ b/backend/api/v1/modules/a76/router.py @@ -38,27 +38,27 @@ router.include_router(user_tenant_router, prefix="/a76", tags=["a76 / user-tenan router.include_router(licenses_router, prefix="/a76", tags=["a76 / licenses"]) router.include_router(pedimentos_router, prefix="/a76") router.include_router( - client_and_provider_router, prefix="/a76", tags=["a76 / clients and providers"] + client_and_provider_router, prefix="/a76", tags=["a76 / clients_and_providers"] ) router.include_router(company_router, prefix="/a76", tags=["a76 / company"]) router.include_router(classes_router, prefix="/a76", tags=["a76 / classes"]) router.include_router(parts_router, prefix="/a76", tags=["a76 / parts"]) router.include_router( - permission_rule_oct_router, prefix="/a76", tags=["a76 / PermissionRuleOct"] + permission_rule_oct_router, prefix="/a76", tags=["a76 / permission_rule_oct"] ) -router.include_router(package_router, prefix="/a76", tags=["a76 / Package"]) -router.include_router(seal_router, prefix="/a76", tags=["a76 / Seal"]) +router.include_router(package_router, prefix="/a76", tags=["a76 / package"]) +router.include_router(seal_router, prefix="/a76", tags=["a76 / seal"]) router.include_router( - fraction_rule_octave_router, prefix="/a76", tags=["a76 / FractionRuleOctave"] + fraction_rule_octave_router, prefix="/a76", tags=["a76 / fraction_rule_octave"] ) router.include_router( - country_rule_oct_router, prefix="/a76", tags=["a76 / CountryRuleOct"] + country_rule_oct_router, prefix="/a76", tags=["a76 / country_rule_oct"] ) -router.include_router(exchange_rate_router, prefix="/a76", tags=["a76 / ExchangeRate"]) -router.include_router(trailers_router, prefix="/a76", tags=["a76 / Trailers"]) +router.include_router(exchange_rate_router, prefix="/a76", tags=["a76 / exchange_rate"]) +router.include_router(trailers_router, prefix="/a76", tags=["a76 / trailers"]) router.include_router( - customs_broker_router, prefix="/a76", tags=["a76 / CustomsBroker"] + customs_broker_router, prefix="/a76", tags=["a76 / customs_broker"] ) -router.include_router(drivers_router, prefix="/a76", tags=["a76 / Drivers"]) -router.include_router(transporters_router, prefix="/a76", tags=["a76 / Transporters"]) -router.include_router(vehicles_router, prefix="/a76", tags=["a76 / Vehicles"]) +router.include_router(drivers_router, prefix="/a76", tags=["a76 / drivers"]) +router.include_router(transporters_router, prefix="/a76", tags=["a76 / transporters"]) +router.include_router(vehicles_router, prefix="/a76", tags=["a76 / vehicles"]) diff --git a/backend/core/security.py b/backend/core/security.py index 0b509d85..60834f26 100644 --- a/backend/core/security.py +++ b/backend/core/security.py @@ -162,7 +162,7 @@ def validate_company_access( # Consultar si la compañía pertenece al tenant try: - from api.v1.modules.a76.company.models import Company + from api.v1.modules.a76.general_catalogs.company.models import Company company = ( db.query(Company) diff --git a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts index ed496e6a..d6a15c7c 100644 --- a/frontend/src/lib/api/dashboard/a76/customs-brokers.ts +++ b/frontend/src/lib/api/dashboard/a76/customs-brokers.ts @@ -88,32 +88,40 @@ export interface CreateCustomsBrokerData { * API para Agentes Aduanales */ export const customsBrokersApi = { + /** + * Lista todos los agentes aduanales + */ + list: (companyId: string) => { + return api.get(`/v1/a76/customs-brokers?company_id=${companyId}`); + }, + /** * Obtiene un agente aduanal por su clave */ get: (brokerKey: string) => { - return api.get(`/api/v1/a76/customs-broker/${brokerKey}`); + return api.get(`/v1/a76/customs-brokers/${brokerKey}`); }, /** * Crea un nuevo agente aduanal */ create: (data: CreateCustomsBrokerData) => { - return api.post('/api/v1/a76/customs-broker', data); + const companyId = data.company_id; + return api.post(`/v1/a76/customs-brokers?company_id=${companyId}`, data); }, /** * Elimina un agente aduanal */ - delete: (brokerKey: string) => { - return api.delete(`/api/v1/a76/customs-broker/${brokerKey}`); + delete: (brokerKey: string, companyId: string) => { + return api.delete(`/v1/a76/customs-brokers/${brokerKey}?company_id=${companyId}`); }, /** * Actualiza la información de VU de un agente aduanal */ updateVU: (brokerKey: string, data: CustomsBrokerVU) => { - return api.put(`/api/v1/a76/customs-broker-vu/${brokerKey}`, data); + return api.put(`/v1/a76/customs-broker-vu/${brokerKey}`, data); }, /** @@ -121,7 +129,7 @@ export const customsBrokersApi = { */ updatePersonnel: (brokerKey: string, line: number, data: CustomsBrokerPersonnel) => { return api.put( - `/api/v1/a76/customs-broker-personnel/${brokerKey}/${line}`, + `/v1/a76/customs-broker-personnel/${brokerKey}/${line}`, data ); } diff --git a/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte index 3e24770d..bf2ffa39 100644 --- a/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte +++ b/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte @@ -23,7 +23,7 @@ error = null; try { - const response = await customsBrokersApi.delete(broker.broker_key); + const response = await customsBrokersApi.delete(broker.broker_key, broker.company_id); if (response.error) { error = response.error; diff --git a/frontend/src/lib/stores/company.svelte.ts b/frontend/src/lib/stores/company.svelte.ts index 54cfb71b..74b13c0d 100644 --- a/frontend/src/lib/stores/company.svelte.ts +++ b/frontend/src/lib/stores/company.svelte.ts @@ -55,13 +55,13 @@ class CompanyStore { if (savedId) { const company = this._companies.find(c => c.id === parseInt(savedId)); if (company) { - this.setActiveCompany(company); + this.setActiveCompany(company, true); // silent=true para inicialización return; } } } // Si no hay guardada, seleccionar la primera - this.setActiveCompany(this._companies[0]); + this.setActiveCompany(this._companies[0], true); // silent=true para inicialización } return; } @@ -90,7 +90,7 @@ class CompanyStore { // Si hay compañías y no hay una activa, seleccionar la primera if (this._companies.length > 0 && !this._activeCompany) { - this.setActiveCompany(this._companies[0]); + this.setActiveCompany(this._companies[0], true); // silent=true para inicialización } } else { console.error('Error loading companies:', response.statusText); @@ -108,8 +108,11 @@ class CompanyStore { /** * Establece la compañía activa + * @param company - La compañía a establecer como activa + * @param silent - Si es true, no dispara el evento companyChanged (para inicialización) */ - setActiveCompany(company: Company) { + setActiveCompany(company: Company, silent: boolean = false) { + const previousCompanyId = this._activeCompany?.id; this._activeCompany = company; // Guardar en localStorage para persistencia @@ -122,8 +125,10 @@ class CompanyStore { document.cookie = `active_company_id=${company.id}; path=/; max-age=${60 * 60 * 24 * 30}; SameSite=Lax`; } - // Despachar evento personalizado para que otros componentes reaccionen - if (typeof window !== 'undefined') { + // Despachar evento personalizado solo si: + // 1. No es silent (no es inicialización) + // 2. Y realmente cambió la compañía (el ID es diferente) + if (!silent && typeof window !== 'undefined' && previousCompanyId !== company.id) { window.dispatchEvent(new CustomEvent('companyChanged', { detail: { companyId: company.id } })); diff --git a/frontend/src/routes/api/company/my-companies/+server.ts b/frontend/src/routes/api/company/my-companies/+server.ts index 956a19c0..16544fa0 100644 --- a/frontend/src/routes/api/company/my-companies/+server.ts +++ b/frontend/src/routes/api/company/my-companies/+server.ts @@ -38,13 +38,11 @@ export const GET: RequestHandler = async ({ cookies, fetch }) => { cookies.delete('active_company_id', { path: '/' }); } return json({ error: 'Failed to fetch companies' }, { status: response.status }); - } - console.log('✅ Token', token); + } const companies = await response.json(); return json(companies); } catch (error) { - console.error('Error fetching companies:', error); - console.log('✅ Token', token); + console.error('Error fetching companies:', error); return json({ error: 'Internal server error' }, { status: 500 }); } }; diff --git a/frontend/src/routes/dashboard/clients_and_providers/+page.server.ts b/frontend/src/routes/dashboard/clients_and_providers/+page.server.ts index 4fd142ce..f27c7557 100644 --- a/frontend/src/routes/dashboard/clients_and_providers/+page.server.ts +++ b/frontend/src/routes/dashboard/clients_and_providers/+page.server.ts @@ -23,11 +23,18 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { const page = parseInt(url.searchParams.get('page') || '1'); const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - // Obtener company_id de la URL o de las companies del usuario + // Obtener company_id de múltiples fuentes (en orden de prioridad): + // 1. URL query param (permite cambiar vía navegación) + // 2. Cookie active_company_id (setted por el team-switcher) + // 3. Primera compañía del usuario (fallback) const companyIdParam = url.searchParams.get('company_id'); + const cookieCompanyId = cookies.get('active_company_id'); + const companyId = companyIdParam ? parseInt(companyIdParam) - : parentData.companies?.[0]?.id; // Usar la primera compañía por defecto + : cookieCompanyId + ? parseInt(cookieCompanyId) + : parentData.companies?.[0]?.id; if (!companyId) { return { diff --git a/frontend/src/routes/dashboard/clients_and_providers/+page.svelte b/frontend/src/routes/dashboard/clients_and_providers/+page.svelte index 6a348f28..0995b3d6 100644 --- a/frontend/src/routes/dashboard/clients_and_providers/+page.svelte +++ b/frontend/src/routes/dashboard/clients_and_providers/+page.svelte @@ -42,6 +42,19 @@ if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { localStorage.setItem('refresh_token', cookieRefreshToken); } + + // Escuchar cambios de compañía + const handleCompanyChange = (event: CustomEvent) => { + // Recargar la página para obtener datos de la nueva compañía + reloadData(); + }; + + window.addEventListener('companyChanged', handleCompanyChange as EventListener); + + // Cleanup + return () => { + window.removeEventListener('companyChanged', handleCompanyChange as EventListener); + }; } }); @@ -97,9 +110,45 @@ } } - function reloadData() { - // Reset y recargar desde el principio - window.location.reload(); + async function reloadData() { + // Reset y recargar desde el principio usando la API + if (!companyStore.activeCompany) return; + + loading = true; + error = null; + + try { + const response = await clientsProvidersApi.list( + companyStore.activeCompany.id, + 1, + pageSize + ); + + if (response.error) { + console.error('📊 [Page] Error en reloadData:', response.error); + if (response.status === 401 || response.status === 403) { + error = 'Sesión expirada. Recargando página...'; + setTimeout(() => { + window.location.reload(); + }, 2000); + } else { + error = response.error; + } + return; + } + + if (response.data?.items) { + // Reemplazar todos los items con los nuevos datos + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + error = 'Error recargando datos'; + console.error('📊 [Page] Error reloading:', e); + } finally { + loading = false; + } } function handleCreateClick() { diff --git a/frontend/src/routes/dashboard/customs_brokers/+page.server.ts b/frontend/src/routes/dashboard/customs_brokers/+page.server.ts index 490bb9ba..b791c5de 100644 --- a/frontend/src/routes/dashboard/customs_brokers/+page.server.ts +++ b/frontend/src/routes/dashboard/customs_brokers/+page.server.ts @@ -1,7 +1,7 @@ import type { PageServerLoad } from './$types'; -import { getAuthTokens } from '$lib/server/api'; +import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; -export const load: PageServerLoad = async ({ cookies, parent }) => { +export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { // Esperar a que el layout padre valide/refresque el token const parentData = await parent(); @@ -10,12 +10,75 @@ export const load: PageServerLoad = async ({ cookies, parent }) => { if (!accessToken) { return { error: 'No authenticated', + brokers: [], companies: parentData.companies || [] }; } - return { - companies: parentData.companies || [], - error: null - }; + try { + // Obtener company_id de múltiples fuentes (en orden de prioridad): + // 1. URL query param (permite cambiar vía navegación) + // 2. Cookie active_company_id (setted por el team-switcher) + // 3. Primera compañía del usuario (fallback) + const companyIdParam = url.searchParams.get('company_id'); + const cookieCompanyId = cookies.get('active_company_id'); + + const companyId = companyIdParam + ? parseInt(companyIdParam) + : cookieCompanyId + ? parseInt(cookieCompanyId) + : parentData.companies?.[0]?.id; + + if (!companyId) { + return { + error: 'No company selected', + brokers: [], + companies: parentData.companies || [] + }; + } + + // Usar authenticatedFetch para manejar automáticamente el refresh de tokens + const response = await authenticatedFetch( + `v1/a76/customs-brokers?company_id=${companyId}`, + {}, + cookies, + fetch + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error('📊 [CustomsBrokers] API Error:', { + status: response.status, + statusText: response.statusText, + error: errorText + }); + + return { + error: `Error ${response.status}: ${response.statusText}`, + brokers: [], + companies: parentData.companies || [], + currentCompanyId: companyId + }; + } + + const data = await response.json(); + + // El endpoint devuelve un objeto con items, total, page, page_size + // Extraer el array de items + const brokers = Array.isArray(data) ? data : (data.items || []); + + return { + brokers: brokers, + error: null, + companies: parentData.companies || [], + currentCompanyId: companyId + }; + } catch (error) { + console.error('📊 [CustomsBrokers] Load error:', error); + return { + error: 'Error loading data', + brokers: [], + companies: parentData.companies || [] + }; + } }; diff --git a/frontend/src/routes/dashboard/customs_brokers/+page.svelte b/frontend/src/routes/dashboard/customs_brokers/+page.svelte index 528b31f7..7ccfb44c 100644 --- a/frontend/src/routes/dashboard/customs_brokers/+page.svelte +++ b/frontend/src/routes/dashboard/customs_brokers/+page.svelte @@ -18,6 +18,11 @@ // Estado para el diálogo de crear let showCreateDialog = $state(false); + // Estado para la lista de agentes aduanales (inicializar con datos del servidor) + let brokersList = $state(data.brokers || []); + let listLoading = $state(false); + let listError = $state(data.error || null); + // Estado para búsqueda let searchKey = $state(''); let searchedBroker = $state(null); @@ -50,6 +55,19 @@ if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { localStorage.setItem('refresh_token', cookieRefreshToken); } + + // Escuchar cambios de compañía + const handleCompanyChange = (event: CustomEvent) => { + // Recargar la página para obtener datos de la nueva compañía + reloadData(); + }; + + window.addEventListener('companyChanged', handleCompanyChange as EventListener); + + // Cleanup + return () => { + window.removeEventListener('companyChanged', handleCompanyChange as EventListener); + }; } }); @@ -91,11 +109,44 @@ } } - function reloadData() { + async function reloadData() { // Limpiar búsqueda searchKey = ''; searchedBroker = null; searchError = null; + + // Recargar lista de brokers desde la API + if (!companyStore.activeCompany) return; + + listLoading = true; + listError = null; + + try { + const response = await customsBrokersApi.list(companyStore.activeCompany.id.toString()); + + if (response.error) { + console.error('📊 [CustomsBrokers] Error en reloadData:', response.error); + if (response.status === 401 || response.status === 403) { + listError = 'Sesión expirada. Recargando página...'; + setTimeout(() => { + window.location.reload(); + }, 2000); + } else { + listError = response.error; + } + return; + } + + if (response.data) { + // Reemplazar la lista con los nuevos datos + brokersList = response.data; + } + } catch (e) { + listError = 'Error recargando datos'; + console.error('📊 [CustomsBrokers] Error reloading:', e); + } finally { + listLoading = false; + } } function handleCreateClick() { @@ -110,8 +161,8 @@ // Crear columnas con el callback onSuccess const columns = createColumns(handleSuccess); - // Array para mostrar en la tabla (vacío o con el broker buscado) - const brokers = $derived(searchedBroker ? [searchedBroker] : []); + // Array para mostrar en la tabla (búsqueda o lista completa) + const brokers = $derived(searchedBroker ? [searchedBroker] : brokersList);
@@ -227,26 +278,49 @@ - {#if searchedBroker} - - -
-
- Resultado de la Búsqueda - + + +
+
+ + {#if searchedBroker} + Resultado de la Búsqueda + {:else} + Agentes Aduanales + {/if} + + + {#if searchedBroker} Se encontró 1 agente aduanal - + {:else if listLoading} + Cargando agentes aduanales... + {:else} + Total: {brokersList.length} agente{brokersList.length !== 1 ? 's' : ''} aduanal{brokersList.length !== 1 ? 'es' : ''} + {/if} + +
+
+
+ + {#if listError} +
+ {listError} +
+ {:else if listLoading} +
+
+
+ Cargando agentes aduanales...
- - + {:else} - -
- {/if} + {/if} + +
diff --git a/frontend/src/routes/dashboard/pedimentos/+page.server.ts b/frontend/src/routes/dashboard/pedimentos/+page.server.ts index 0cc853a8..19a07fcc 100644 --- a/frontend/src/routes/dashboard/pedimentos/+page.server.ts +++ b/frontend/src/routes/dashboard/pedimentos/+page.server.ts @@ -2,11 +2,13 @@ import type { PageServerLoad } from './$types'; import { redirect } from '@sveltejs/kit'; import { getAuthTokens, - getActiveCompanyId, authenticatedFetch } from '$lib/server/api'; -export const load: PageServerLoad = async ({ fetch, cookies }) => { +export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => { + // Esperar a que el layout padre valide/refresque el token + const parentData = await parent(); + // Verificar autenticación const { accessToken } = getAuthTokens(cookies); @@ -15,8 +17,18 @@ export const load: PageServerLoad = async ({ fetch, cookies }) => { } try { - // Obtener el company_id de la cookie o usar la primera disponible - const companyId = await getActiveCompanyId(cookies, fetch); + // Obtener company_id de múltiples fuentes (en orden de prioridad): + // 1. URL query param (permite cambiar vía navegación) + // 2. Cookie active_company_id (setted por el team-switcher) + // 3. Primera compañía del usuario (fallback) + const companyIdParam = url.searchParams.get('company_id'); + const cookieCompanyId = cookies.get('active_company_id'); + + const companyId = companyIdParam + ? parseInt(companyIdParam) + : cookieCompanyId + ? parseInt(cookieCompanyId) + : parentData.companies?.[0]?.id; // Si aún no hay companyId, mostrar error if (!companyId) { @@ -25,7 +37,8 @@ export const load: PageServerLoad = async ({ fetch, cookies }) => { total: 0, page: 1, page_size: 50, - error: 'No se encontró una compañía seleccionada' + error: 'No se encontró una compañía seleccionada', + companies: parentData.companies || [] }; } @@ -44,7 +57,9 @@ export const load: PageServerLoad = async ({ fetch, cookies }) => { total: 0, page: 1, page_size: 50, - error: 'Error al cargar pedimentos' + error: 'Error al cargar pedimentos', + companies: parentData.companies || [], + currentCompanyId: companyId }; } @@ -54,7 +69,9 @@ export const load: PageServerLoad = async ({ fetch, cookies }) => { items: data.items || [], total: data.total || 0, page: data.page || 1, - page_size: data.page_size || 50 + page_size: data.page_size || 50, + companies: parentData.companies || [], + currentCompanyId: companyId }; } catch (error) { console.error('Error loading pedimentos:', error); @@ -63,7 +80,8 @@ export const load: PageServerLoad = async ({ fetch, cookies }) => { total: 0, page: 1, page_size: 50, - error: 'Error al cargar pedimentos' + error: 'Error al cargar pedimentos', + companies: parentData.companies || [] }; } }; diff --git a/frontend/src/routes/dashboard/pedimentos/+page.svelte b/frontend/src/routes/dashboard/pedimentos/+page.svelte index d70be202..8a90390c 100644 --- a/frontend/src/routes/dashboard/pedimentos/+page.svelte +++ b/frontend/src/routes/dashboard/pedimentos/+page.svelte @@ -9,6 +9,7 @@ import { Label } from '$lib/components/ui/label'; import type { PageData } from './$types'; import { browser } from '$app/environment'; + import { companyStore } from '$lib/stores/company.svelte'; // Los datos iniciales vienen del servidor let { data }: { data: PageData } = $props(); @@ -46,12 +47,25 @@ if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) { localStorage.setItem('refresh_token', cookieRefreshToken); } + + // Escuchar cambios de compañía + const handleCompanyChange = (event: CustomEvent) => { + // Recargar los datos sin recargar la página completa + reloadData(); + }; + + window.addEventListener('companyChanged', handleCompanyChange as EventListener); + + // Cleanup + return () => { + window.removeEventListener('companyChanged', handleCompanyChange as EventListener); + }; } }); // Estado para infinite scroll let allItems = $state(data.items || []); - let currentPage = $state(data.page || 1); + let currentPage = $state(data.page); let pageSize = $state(50); let totalItems = $state(data.total || 0); let loading = $state(false); @@ -65,13 +79,15 @@ error = null; try { + const companyId = companyStore.activeCompany?.id; + const filterParams = { status: filters.status || undefined, client_id: filters.client_id ? parseInt(filters.client_id) : undefined, year: filters.year || undefined }; - const response = await pedimentosApi.list(currentPage + 1, pageSize, filterParams); + const response = await pedimentosApi.list(currentPage + 1, pageSize, filterParams, companyId); if (response.error) { console.error('📊 [Page] Error en loadMore:', response.error, 'Status:', response.status); @@ -109,13 +125,15 @@ error = null; try { + const companyId = companyStore.activeCompany?.id || 1; + const filterParams = { status: filters.status || undefined, client_id: filters.client_id ? parseInt(filters.client_id) : undefined, year: filters.year || undefined }; - const response = await pedimentosApi.list(1, pageSize, filterParams); + const response = await pedimentosApi.list(1, pageSize, filterParams, companyId); if (response.error) { console.error('📊 [Page] Error aplicando filtros:', response.error); @@ -150,12 +168,52 @@ client_id: '', year: '' }; - window.location.reload(); + applyFilters(); } - function reloadData() { - // Reset y recargar desde el principio - window.location.reload(); + async function reloadData() { + // Reset y recargar desde el principio usando la API + if (!companyStore.activeCompany) return; + + loading = true; + error = null; + + try { + const companyId = companyStore.activeCompany.id; + + const filterParams = { + status: filters.status || undefined, + client_id: filters.client_id ? parseInt(filters.client_id) : undefined, + year: filters.year || undefined + }; + + const response = await pedimentosApi.list(1, pageSize, filterParams, companyId); + + if (response.error) { + console.error('📊 [Pedimentos] Error en reloadData:', response.error); + if (response.status === 401 || response.status === 403) { + error = 'Sesión expirada. Recargando página...'; + setTimeout(() => { + window.location.reload(); + }, 2000); + } else { + error = response.error; + } + return; + } + + if (response.data?.items) { + // Reemplazar todos los items con los nuevos datos + allItems = response.data.items; + currentPage = 1; + totalItems = response.data.total; + } + } catch (e) { + error = 'Error recargando datos'; + console.error('📊 [Pedimentos] Error reloading:', e); + } finally { + loading = false; + } } function handleCreateClick() { diff --git a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts index 42c4e91f..c011c52c 100644 --- a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts +++ b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts @@ -1,6 +1,6 @@ import type { PageServerLoad } from './$types'; import { error, redirect } from '@sveltejs/kit'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; +import { getAuthTokens, getActiveCompanyId, authenticatedFetch } from '$lib/server/api'; export const load: PageServerLoad = async ({ params, cookies, fetch }) => { const { accessToken } = getAuthTokens(cookies); @@ -24,9 +24,16 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => { } try { + // Obtener el company_id de la cookie + const companyId = await getActiveCompanyId(cookies, fetch); + + if (!companyId) { + throw error(400, 'No se encontró una compañía seleccionada'); + } + // Cargar el pedimento desde el backend usando authenticatedFetch const response = await authenticatedFetch( - `v1/a76/pedimentos/${pedimentoId}`, + `v1/a76/pedimentos/${pedimentoId}?company_id=${companyId}`, {}, cookies, fetch