feat: Enhance Pedimentos CRUD operations with related data handling
- Updated PedimentosService to create and update related tables for Pedimentos. - Added company_id filtering in queries for Pedimentos. - Improved error handling and logging during creation and update processes. - Refactored router tags for consistency and clarity. - Adjusted API endpoints for customs brokers to include company_id in requests. - Enhanced company selection logic in various components to prioritize active company. - Implemented event listeners for company changes to reload data dynamically. - Updated frontend components to handle loading states and errors more effectively. - Ensured all relevant routes and API calls are aligned with the new company context.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<CustomsBroker[]>(`/v1/a76/customs-brokers?company_id=${companyId}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un agente aduanal por su clave
|
||||
*/
|
||||
get: (brokerKey: string) => {
|
||||
return api.get<CustomsBroker>(`/api/v1/a76/customs-broker/${brokerKey}`);
|
||||
return api.get<CustomsBroker>(`/v1/a76/customs-brokers/${brokerKey}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Crea un nuevo agente aduanal
|
||||
*/
|
||||
create: (data: CreateCustomsBrokerData) => {
|
||||
return api.post<CustomsBroker>('/api/v1/a76/customs-broker', data);
|
||||
const companyId = data.company_id;
|
||||
return api.post<CustomsBroker>(`/v1/a76/customs-brokers?company_id=${companyId}`, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Elimina un agente aduanal
|
||||
*/
|
||||
delete: (brokerKey: string) => {
|
||||
return api.delete<CustomsBroker>(`/api/v1/a76/customs-broker/${brokerKey}`);
|
||||
delete: (brokerKey: string, companyId: string) => {
|
||||
return api.delete<CustomsBroker>(`/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<CustomsBrokerVU>(`/api/v1/a76/customs-broker-vu/${brokerKey}`, data);
|
||||
return api.put<CustomsBrokerVU>(`/v1/a76/customs-broker-vu/${brokerKey}`, data);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -121,7 +129,7 @@ export const customsBrokersApi = {
|
||||
*/
|
||||
updatePersonnel: (brokerKey: string, line: number, data: CustomsBrokerPersonnel) => {
|
||||
return api.put<CustomsBrokerPersonnel>(
|
||||
`/api/v1/a76/customs-broker-personnel/${brokerKey}/${line}`,
|
||||
`/v1/a76/customs-broker-personnel/${brokerKey}/${line}`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }
|
||||
}));
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 || []
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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<CustomsBroker[]>(data.brokers || []);
|
||||
let listLoading = $state(false);
|
||||
let listError = $state<string | null>(data.error || null);
|
||||
|
||||
// Estado para búsqueda
|
||||
let searchKey = $state('');
|
||||
let searchedBroker = $state<CustomsBroker | null>(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);
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
@@ -227,26 +278,49 @@
|
||||
</Card.Root>
|
||||
|
||||
<!-- Resultados -->
|
||||
{#if searchedBroker}
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Resultado de la Búsqueda</Card.Title>
|
||||
<Card.Description>
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>
|
||||
{#if searchedBroker}
|
||||
Resultado de la Búsqueda
|
||||
{:else}
|
||||
Agentes Aduanales
|
||||
{/if}
|
||||
</Card.Title>
|
||||
<Card.Description>
|
||||
{#if searchedBroker}
|
||||
Se encontró 1 agente aduanal
|
||||
</Card.Description>
|
||||
{:else if listLoading}
|
||||
Cargando agentes aduanales...
|
||||
{:else}
|
||||
Total: {brokersList.length} agente{brokersList.length !== 1 ? 's' : ''} aduanal{brokersList.length !== 1 ? 'es' : ''}
|
||||
{/if}
|
||||
</Card.Description>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
{#if listError}
|
||||
<div class="rounded-lg border border-destructive bg-destructive/10 p-4 text-sm text-destructive">
|
||||
{listError}
|
||||
</div>
|
||||
{:else if listLoading}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="flex items-center gap-2 text-muted-foreground">
|
||||
<div class="h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
Cargando agentes aduanales...
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
{:else}
|
||||
<DataTable
|
||||
data={brokers}
|
||||
{columns}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
{/if}
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
<!-- Diálogo de crear -->
|
||||
|
||||
@@ -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 || []
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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<Pedimento[]>(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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user