feat: Implement consolidated catalog services and endpoints for pedimento and invoice creation/edition.

This commit is contained in:
Galindo97
2026-02-12 13:35:48 -06:00
parent 9d27066da1
commit ab5181f803
16 changed files with 1323 additions and 879 deletions

View File

@@ -196,4 +196,33 @@ class ClassSearchDTO(BaseModel):
)
class Config:
from_attributes = True
from_attributes = True
class ClassWithFADataResponse(BaseModel):
"""DTO para respuesta de clase con datos FA embebidos (para fixed-asset-classes)"""
# Base class fields
id: int
tenant_id: int
company_id: int
class_code: str
description_es: Optional[str] = None
description_en: Optional[str] = None
material_key: Optional[str] = None
unit_of_measure: Optional[str] = None
fraction: Optional[str] = None
us_fraction: Optional[str] = None
sub_key: Optional[str] = None
physical_review: Optional[int] = None
iva_exempt_fraction: Optional[str] = None
created_at: datetime
updated_at: datetime
# FA-specific fields (embedded from a24.fa_classes)
fa_class_id: Optional[int] = None
depreciation_rate: Optional[Decimal] = None
fda_code: Optional[str] = None
class_enabled: Optional[bool] = None
model_config = ConfigDict(from_attributes=True)

View File

@@ -2,34 +2,53 @@
Endpoints API para gestión de clases SCAII y SCAF
"""
from typing import Dict, Any
from fastapi import Depends, Query
from typing import Dict, Any, List
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from core.database import get_core_db
from core.security import get_current_user
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource
from .dto import ClassCreateDTO, ClassCreateDTOFA, ClassResponseDTO, ClassResponseDTOFA, ClassUpdateDTO
from .dto import ClassCreateDTO, ClassCreateDTOFA, ClassResponseDTO, ClassResponseDTOFA, ClassUpdateDTO, ClassWithFADataResponse
from .service import ClassService
# Create router with generic CRUD routes
crud_routes = TenantCRUDRoutes(
service=ClassService,
create_schema=ClassCreateDTO,
update_schema=ClassUpdateDTO,
response_schema=ClassResponseDTO,
prefix="/classes",
tags=["a76 / classes"],
resource_name="Class",
id_name="id",
enable_list=True,
enable_filters=True,
default_page_size=50,
max_page_size=1000,
)
# Create a new router for custom endpoints
router = APIRouter()
router = crud_routes.router
# Add consolidated catalog endpoints FIRST (before generic CRUD routes)
# This ensures they have priority over the generic /{id} route
@router.get(
"/with-fa-data",
response_model=List[ClassWithFADataResponse],
summary="Get Classes with FA Data",
description="Get all classes with their FA data in a single query (eliminates N+1 problem)",
tags=["a76 / classes"],
)
async def get_classes_with_fa_data(
company_id: int = Query(..., description="Company ID"),
page: int = Query(1, ge=1, description="Page number"),
page_size: int = Query(1000, ge=1, le=1000, description="Page size"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""
Get all classes with their FA data using a single LEFT JOIN query.
This endpoint is optimized for the fixed-asset-classes view.
"""
tenant_id = validate_access_to_resource(db, company_id, current_user)
skip = (page - 1) * page_size
classes_with_fa, total = ClassService.get_all_with_fa_data(
db=db,
tenant_id=tenant_id,
company_id=company_id,
skip=skip,
limit=page_size,
)
return classes_with_fa
@router.post(
"/fa",
@@ -37,6 +56,7 @@ router = crud_routes.router
status_code=201,
summary="Create Fixed Asset Class",
description="Create a class with FA extension in a single transaction",
tags=["a76 / classes"],
)
async def create_fa_class(
class_data: ClassCreateDTOFA,
@@ -50,4 +70,24 @@ async def create_fa_class(
result = ClassService.create_fa_class(db, class_data, tenant_id, company_id)
return result
return result
# Now include generic CRUD routes
# These will be registered AFTER the custom endpoints above
crud_router = TenantCRUDRoutes(
service=ClassService,
create_schema=ClassCreateDTO,
update_schema=ClassUpdateDTO,
response_schema=ClassResponseDTO,
prefix="", # No prefix here, will be added in main router
tags=["a76 / classes"],
resource_name="Class",
id_name="id",
enable_list=True,
enable_filters=True,
default_page_size=50,
max_page_size=1000,
).router
# Include the CRUD routes into our main router
router.include_router(crud_router)

View File

@@ -73,6 +73,91 @@ class ClassService:
return items, total
@staticmethod
def get_all_with_fa_data(
db: Session,
tenant_id: int,
company_id: int,
skip: int = 0,
limit: int = 1000,
filters: Optional[Dict[str, Any]] = None,
) -> tuple[List[Dict[str, Any]], int]:
"""
Get all classes with their FA data in a single query using LEFT JOIN.
This eliminates the N+1 query problem.
Returns a list of dicts with combined base class + FA data.
"""
from api.v1.modules.a24.fa.fa_classes.models import QClasses
# Build query with LEFT JOIN
query = (
db.query(Class, QClasses)
.outerjoin(QClasses, and_(
Class.id == QClasses.class_id,
QClasses.tenant_id == tenant_id
))
.filter(Class.tenant_id == tenant_id)
.filter(Class.company_id == company_id)
)
# Apply filters if provided
if filters:
if filters.get("class_code"):
query = query.filter(
Class.class_code.ilike(f"%{filters['class_code']}%")
)
if filters.get("description"):
description_pattern = f"%{filters['description']}%"
query = query.filter(
or_(
Class.description_es.ilike(description_pattern),
Class.description_en.ilike(description_pattern),
)
)
if filters.get("material_key"):
query = query.filter(
Class.material_key.ilike(f"%{filters['material_key']}%")
)
if filters.get("fraction"):
query = query.filter(Class.fraction.ilike(f"%{filters['fraction']}%"))
# Count total before pagination
total = query.count()
# Apply pagination
results = query.offset(skip).limit(limit).all()
# Combine base class + FA data into dicts
combined = []
for base_class, fa_class in results:
class_dict = {
# Base class fields
"id": base_class.id,
"tenant_id": base_class.tenant_id,
"company_id": base_class.company_id,
"class_code": base_class.class_code,
"description_es": base_class.description_es,
"description_en": base_class.description_en,
"material_key": base_class.material_key,
"unit_of_measure": base_class.unit_of_measure,
"fraction": base_class.fraction,
"us_fraction": base_class.us_fraction,
"sub_key": base_class.sub_key,
"physical_review": base_class.physical_review,
"iva_exempt_fraction": base_class.iva_exempt_fraction,
"created_at": base_class.created_at,
"updated_at": base_class.updated_at,
# FA extension fields (None if no FA record exists)
"fa_class_id": fa_class.id if fa_class else None,
"depreciation_rate": fa_class.depreciation_rate if fa_class else None,
"fda_code": fa_class.fda_code if fa_class else None,
"class_enabled": fa_class.class_enabled if fa_class else None,
}
combined.append(class_dict)
return combined, total
@staticmethod
def get_by_id(
db: Session, class_id: int, tenant_id: int, company_id: int

View File

@@ -0,0 +1,217 @@
from typing import Dict, Any, List, Optional
from sqlalchemy.orm import Session
# Import Reference Data Models
from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType
from api.v1.modules.public.reference_data.currency_types.models import CurrencyType
from api.v1.modules.public.reference_data.transport_types.models import TransportType
from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection
from api.v1.modules.public.reference_data.code_pedimento_regimens.models import CodePedimentoRegimen
from api.v1.modules.public.reference_data.incoterms.models import Incoterm
from api.v1.modules.public.reference_data.transport_modes.models import TransportMode
# Import A76 Services
from api.v1.modules.a76.customs_brokers.services import CustomsBrokerService
from api.v1.modules.a76.clients_and_providers.service import ClientProviderService
from api.v1.modules.a76.transportation.transporters.services import TransporterService
from api.v1.modules.a76.transportation.vehicles.services import VehicleService
from api.v1.modules.a76.transportation.drivers.services import DriverService
from api.v1.modules.a76.transportation.trailers.services import TrailerService
from api.v1.modules.a76.general_catalogs.seal.services import SealService
from api.v1.modules.a76.pedmientos.services.pedimentos import PedimentosService
# Import DTOs for mapping
from api.v1.modules.public.reference_data.invoice_types.dto import InvoiceTypeDTO
from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO
from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO
from api.v1.modules.public.reference_data.currency_types.dto import CurrencyTypeDTO
from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO
from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
from api.v1.modules.public.reference_data.incoterms.dto import IncotermDTO
from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO
# Additional DTOs
from api.v1.modules.a76.transportation.transporters.dto import TransporterResponseDTO
from api.v1.modules.a76.transportation.vehicles.dto import VehicleResponseDTO
from api.v1.modules.a76.transportation.drivers.dto import DriverResponseDTO
from api.v1.modules.a76.transportation.trailers.dto import TrailerResponseDTO
from api.v1.modules.a76.general_catalogs.seal.dto import SealResponseDTO
from api.v1.modules.a76.pedmientos.dtos.pedimentos import PedimentosResponse
from .schemas import InvoiceCatalogsResponse, InvoiceCreationResponse, InvoiceEditionResponse
class InvoiceCatalogService:
"""Service to fetch consolidated catalogs for Invoice views"""
@staticmethod
def get_catalogs(db: Session, tenant_id: int, company_id: int) -> InvoiceCatalogsResponse:
"""Fetch all catalogs"""
response = InvoiceCatalogsResponse()
# Helper to fetch reference data (no company_id needed)
def fetch_ref_data():
response.invoice_types = [
InvoiceTypeDTO.model_validate(obj) for obj in db.query(InvoiceType).all()
]
response.currency_types = [
CurrencyTypeDTO.model_validate(obj) for obj in db.query(CurrencyType).all()
]
response.transport_types = [
TransportTypeDTO.model_validate(obj) for obj in db.query(TransportType).all()
]
response.customs_sections = [
CustomsSectionDTO.model_validate(obj) for obj in db.query(CustomsSection).all()
]
response.code_pedimento_regimens = [
CodePedimentoRegimenDTO.model_validate(obj) for obj in db.query(CodePedimentoRegimen).all()
]
response.incoterms = [
IncotermDTO.model_validate(obj) for obj in db.query(Incoterm).all()
]
response.transport_modes = [
TransportModeDTO.model_validate(obj) for obj in db.query(TransportMode).all()
]
# Helper to fetch tenant/company specific data
def fetch_tenant_data():
# Customs Brokers
try:
brokers, _ = CustomsBrokerService.get_all(db, tenant_id, company_id, limit=1000)
response.customs_brokers = [
CustomsBrokerResponseDTO.model_validate(obj) for obj in brokers
]
except Exception as e:
print(f"Error fetching customs brokers: {e}")
# Clients and Providers
try:
# Fetch all clients/providers
# Note: get_all returns list[ClientProvider]
all_cps, _ = ClientProviderService.get_all(
db, tenant_id, company_id, limit=2000
)
# Helper to safely check client type (handles string or Enum)
def is_type(obj, types):
val = obj.client_or_provider
# If it's an enum, get its value, otherwise use as string
val_str = val.value if hasattr(val, 'value') else str(val)
return val_str in types
response.clients = [
ClientProviderResponseDTO.model_validate(obj) for obj in all_cps
if is_type(obj, ['client', 'both'])
]
response.providers = [
ClientProviderResponseDTO.model_validate(obj) for obj in all_cps
if is_type(obj, ['provider', 'both'])
]
except Exception as e:
print(f"Error fetching clients/providers: {e}")
# Transporters
try:
transporters, _ = TransporterService.get_all(db, tenant_id, company_id, limit=1000)
response.transporters = [
TransporterResponseDTO.model_validate(t) for t in transporters
]
except Exception as e:
print(f"Error fetching transporters: {e}")
# Vehicles
try:
vehicles, _ = VehicleService.get_all(db, tenant_id, company_id, limit=1000)
response.vehicles = [
VehicleResponseDTO.model_validate(v) for v in vehicles
]
except Exception as e:
print(f"Error fetching vehicles: {e}")
# Drivers
try:
drivers, _ = DriverService.get_all(db, tenant_id, company_id, limit=1000)
response.drivers = [
DriverResponseDTO.model_validate(d) for d in drivers
]
except Exception as e:
print(f"Error fetching drivers: {e}")
# Trailers
try:
trailers, _ = TrailerService.get_all(db, tenant_id, company_id, limit=1000)
response.trailers = [
TrailerResponseDTO.model_validate(t) for t in trailers
]
except Exception as e:
print(f"Error fetching trailers: {e}")
# Seals
try:
seals, _ = SealService.get_all(db, tenant_id, company_id, limit=1000)
response.seals = [
SealResponseDTO.model_validate(s) for s in seals
]
except Exception as e:
print(f"Error fetching seals: {e}")
# Pedimentos
try:
# Fetch recent pedimentos (e.g. last 100) or filtered if necessary
pedimentos, _ = PedimentosService.get_all(db, tenant_id, company_id, limit=100)
response.pedimentos = [
# Use dict for now if PedimentosResponse fails due to complexity or just map fields manually if needed
# But PedimentosResponse has from_attributes=True
# Note: PedimentosResponse structure is complex with nested relations.
# If Pedimentos model is fully loaded (eager load in service), this should work.
# However, to be safe against recursion or huge payload, we might want a lighter DTO.
# Re-using PedimentosResponse for now but be cautious of payload size.
PedimentosResponse.model_validate(p) for p in pedimentos
]
except Exception as e:
print(f"Error fetching pedimentos: {e}")
try:
fetch_ref_data()
fetch_tenant_data()
except Exception as e:
print(f"Error fetching catalogs: {e}")
# In production, we might want to log this properly and potentially return partial data
# For now, re-raising might be safer to debug, but for resiliency we could suppress.
# Let's log and re-raise to ensure frontend knows something went wrong during dev.
import traceback
traceback.print_exc()
raise e
return response
@staticmethod
def get_creation_data(db: Session, tenant_id: int, company_id: int) -> InvoiceCreationResponse:
catalogs = InvoiceCatalogService.get_catalogs(db, tenant_id, company_id)
return InvoiceCreationResponse(
**catalogs.model_dump(),
is_create=True,
filters={}
)
@staticmethod
def get_edition_data(db: Session, invoice_id: int, tenant_id: int, company_id: int) -> Optional[InvoiceEditionResponse]:
catalogs = InvoiceCatalogService.get_catalogs(db, tenant_id, company_id)
from .services import InvoiceService
invoice = InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id)
if not invoice:
return None
return InvoiceEditionResponse(
**catalogs.model_dump(),
is_create=False,
invoice=invoice,
invoice_id=invoice_id,
filters={}
)

View File

@@ -6,10 +6,36 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Path
from sqlalchemy.orm import Session
from . import schemas, services
from .catalog_service import InvoiceCatalogService
# Create main router
router = APIRouter()
@router.get("/invoices/creation-data", response_model=schemas.InvoiceCreationResponse)
def get_creation_data(
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get consolidated data for creating a new invoice"""
tenant_id = validate_access_to_resource(db, company_id, current_user)
return InvoiceCatalogService.get_creation_data(db, tenant_id, company_id)
@router.get("/invoices/{invoice_id}/edition-data", response_model=schemas.InvoiceEditionResponse)
def get_edition_data(
invoice_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: Dict[str, Any] = Depends(get_current_user),
):
"""Get consolidated data for editing an existing invoice"""
tenant_id = validate_access_to_resource(db, company_id, current_user)
data = InvoiceCatalogService.get_edition_data(db, invoice_id, tenant_id, company_id)
if not data:
raise HTTPException(status_code=404, detail="Invoice not found")
return data
# Create CRUD routes for Invoice Header using TenantCRUDRoutes
invoice_crud = TenantCRUDRoutes(
service=services.InvoiceService,

View File

@@ -568,4 +568,61 @@ class InvoiceHeaderListResponse(BaseModel):
items: List[InvoiceHeaderResponse]
total: int
page: int
page_size: int
# --- Consolidated Response Schemas ---
# Import necessary DTOs from other modules
from api.v1.modules.public.reference_data.invoice_types.dto import InvoiceTypeDTO
from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO
from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO
from api.v1.modules.public.reference_data.currency_types.dto import CurrencyTypeDTO
from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO
from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO
from api.v1.modules.public.reference_data.incoterms.dto import IncotermDTO
from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
# TODO: Check if these paths are correct or need adjustment based on actual file locations
# Using Any for now for potentially complex or unverified paths to avoid immediate ImportErrors
# Detailed verification is needed for:
# - TransporterDTO
# - VehicleDTO
# - DriverDTO
# - TrailerDTO
# - SealResponseDTO
# - PedimentoDTO
class InvoiceCatalogsResponse(BaseModel):
"""Consolidated response for all catalogs needed in Invoice Create/Edit views"""
invoice_types: List[InvoiceTypeDTO] = []
customs_brokers: List[CustomsBrokerResponseDTO] = []
clients: List[ClientProviderResponseDTO] = []
providers: List[ClientProviderResponseDTO] = []
currency_types: List[CurrencyTypeDTO] = []
transport_types: List[TransportTypeDTO] = []
transporters: List[dict] = [] # Placeholder, refine with actual DTO
vehicles: List[dict] = [] # Placeholder, refine with actual DTO
drivers: List[dict] = [] # Placeholder, refine with actual DTO
trailers: List[dict] = [] # Placeholder, refine with actual DTO
customs_sections: List[CustomsSectionDTO] = []
code_pedimento_regimens: List[CodePedimentoRegimenDTO] = []
seals: List[dict] = [] # Placeholder, refine with actual DTO
incoterms: List[IncotermDTO] = []
pedimentos: List[dict] = [] # Placeholder, refine with actual DTO
transport_modes: List[TransportModeDTO] = []
default_settings: Optional[dict] = None
class InvoiceCreationResponse(InvoiceCatalogsResponse):
"""Response for Invoice Creation View"""
is_create: bool = True
invoice: Optional[dict] = None # Should be null for creation
invoice_id: Optional[int] = None
filters: Optional[dict] = None # Pre-filled filters if any
class InvoiceEditionResponse(InvoiceCatalogsResponse):
"""Response for Invoice Edition View"""
is_create: bool = False
invoice: InvoiceHeaderResponse
invoice_id: int
filters: Optional[dict] = None

View File

@@ -0,0 +1,120 @@
from typing import List
from sqlalchemy.orm import Session
# Import Reference Data Models
from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode
from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection
from api.v1.modules.public.reference_data.code_pedimento_regimens.models import CodePedimentoRegimen
# Import A76 Services
from api.v1.modules.a76.customs_brokers.services import CustomsBrokerService
from api.v1.modules.a76.clients_and_providers.service import ClientProviderService
# Import DTOs for mapping
from api.v1.modules.public.reference_data.pedimento_codes.dto import PedimentoCodeDTO
from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO
from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO
from .schemas import PedimentoCatalogsResponse, PedimentoCreationResponse, PedimentoEditionResponse
class PedimentoCatalogService:
"""Service to fetch consolidated catalogs for Pedimento views"""
@staticmethod
def get_catalogs(db: Session, tenant_id: int, company_id: int) -> PedimentoCatalogsResponse:
"""Fetch all catalogs with graceful degradation"""
response = PedimentoCatalogsResponse()
# Helper to fetch reference data (no company_id needed)
def fetch_ref_data():
try:
response.pedimento_codes = [
PedimentoCodeDTO.model_validate(obj) for obj in db.query(PedimentoCode).limit(100).all()
]
except Exception as e:
print(f"Error fetching pedimento_codes: {e}")
try:
response.customs_sections = [
CustomsSectionDTO.model_validate(obj) for obj in db.query(CustomsSection).limit(100).all()
]
except Exception as e:
print(f"Error fetching customs_sections: {e}")
try:
response.code_pedimento_regimens = [
CodePedimentoRegimenDTO.model_validate(obj) for obj in db.query(CodePedimentoRegimen).limit(100).all()
]
except Exception as e:
print(f"Error fetching code_pedimento_regimens: {e}")
# Helper to fetch tenant/company specific data
def fetch_tenant_data():
# Customs Brokers
try:
brokers, _ = CustomsBrokerService.get_all(db, tenant_id, company_id, limit=1000)
response.customs_brokers = [
CustomsBrokerResponseDTO.model_validate(obj) for obj in brokers
]
except Exception as e:
print(f"Error fetching customs brokers: {e}")
# Clients (only clients, not providers)
try:
all_cps, _ = ClientProviderService.get_all(
db, tenant_id, company_id, limit=1000
)
# Helper to safely check client type (handles string or Enum)
def is_type(obj, types):
val = obj.client_or_provider
# If it's an enum, get its value, otherwise use as string
val_str = val.value if hasattr(val, 'value') else str(val)
return val_str in types
response.clients = [
ClientProviderResponseDTO.model_validate(obj) for obj in all_cps
if is_type(obj, ['client', 'both'])
]
except Exception as e:
print(f"Error fetching clients: {e}")
try:
fetch_ref_data()
fetch_tenant_data()
except Exception as e:
print(f"Error fetching catalogs: {e}")
import traceback
traceback.print_exc()
raise e
return response
@staticmethod
def get_creation_data(db: Session, tenant_id: int, company_id: int) -> PedimentoCreationResponse:
catalogs = PedimentoCatalogService.get_catalogs(db, tenant_id, company_id)
return PedimentoCreationResponse(
**catalogs.model_dump(),
is_create=True
)
@staticmethod
def get_edition_data(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> PedimentoEditionResponse:
catalogs = PedimentoCatalogService.get_catalogs(db, tenant_id, company_id)
from .services.pedimentos import PedimentosService
pedimento = PedimentosService.get_by_id(db, pedimento_id, tenant_id, company_id)
if not pedimento:
return None
return PedimentoEditionResponse(
**catalogs.model_dump(),
is_create=False,
pedimento=pedimento,
pedimento_id=pedimento_id
)

View File

@@ -2,13 +2,76 @@
Routes for Pedimentos CRUD operations
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
from core.database import get_core_db
from core.security import get_current_user
from ..dtos.pedimentos import PedimentosCreate, PedimentosResponse, PedimentosUpdate
from ..services.pedimentos import PedimentosService
from ..catalog_service import PedimentoCatalogService
from ..schemas import PedimentoCreationResponse, PedimentoEditionResponse
# Create router with generic CRUD routes
router = TenantCRUDRoutes(
# Create a new router for custom endpoints
router = APIRouter()
# Add consolidated catalog endpoints FIRST (before generic CRUD routes)
# This ensures they have priority over the generic /{id} route
@router.get("/creation-data", response_model=PedimentoCreationResponse, tags=["a76 / pedimentos"])
async def get_creation_data(
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get all catalogs needed for creating a new pedimento.
Consolidates multiple catalog calls into a single endpoint.
"""
tenant_id = current_user["tenant_id"]
try:
return PedimentoCatalogService.get_creation_data(db, tenant_id, company_id)
except Exception as e:
print(f"Error fetching creation data: {e}")
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail="Error fetching creation data")
@router.get("/{pedimento_id}/edition-data", response_model=PedimentoEditionResponse, tags=["a76 / pedimentos"])
async def get_edition_data(
pedimento_id: int,
company_id: int = Query(..., description="Company ID"),
db: Session = Depends(get_core_db),
current_user: dict = Depends(get_current_user)
):
"""
Get all catalogs and pedimento data needed for editing an existing pedimento.
Consolidates multiple catalog calls + pedimento fetch into a single endpoint.
"""
tenant_id = current_user["tenant_id"]
try:
result = PedimentoCatalogService.get_edition_data(db, pedimento_id, tenant_id, company_id)
if result is None:
raise HTTPException(status_code=404, detail="Pedimento not found")
return result
except HTTPException:
raise
except Exception as e:
print(f"Error fetching edition data: {e}")
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail="Error fetching edition data")
# Now include generic CRUD routes
# These will be registered AFTER the custom endpoints above
crud_router = TenantCRUDRoutes(
service=PedimentosService,
create_schema=PedimentosCreate,
update_schema=PedimentosUpdate,
@@ -22,3 +85,6 @@ router = TenantCRUDRoutes(
default_page_size=50,
max_page_size=100,
).router
# Include the CRUD routes into our main router
router.include_router(crud_router)

View File

@@ -0,0 +1,37 @@
"""
Consolidated schemas for Pedimento catalog responses
"""
from typing import List, Optional, Any
from pydantic import BaseModel
# Import DTOs for catalog items
from api.v1.modules.public.reference_data.pedimento_codes.dto import PedimentoCodeDTO
from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO
from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO
class PedimentoCatalogsResponse(BaseModel):
"""Base response containing all catalogs needed for pedimento views"""
pedimento_codes: List[PedimentoCodeDTO] = []
customs_sections: List[CustomsSectionDTO] = []
code_pedimento_regimens: List[CodePedimentoRegimenDTO] = []
customs_brokers: List[CustomsBrokerResponseDTO] = []
clients: List[ClientProviderResponseDTO] = []
class PedimentoCreationResponse(PedimentoCatalogsResponse):
"""Response for creating a new pedimento (catalogs only)"""
is_create: bool = True
class PedimentoEditionResponse(PedimentoCatalogsResponse):
"""Response for editing an existing pedimento (catalogs + pedimento data)"""
is_create: bool = False
pedimento: Optional[Any] = None # Will be PedimentosResponse but avoiding circular import
pedimento_id: Optional[int] = None

View File

@@ -75,7 +75,7 @@ router.include_router(
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(classes_router, prefix="/a76/classes", tags=["a76 / classes"])
router.include_router(parts_router, prefix="/a76", tags=["a76 / parts"])
router.include_router(
permission_rule_oct_router, prefix="/a76", tags=["a76 / permission_rule_oct"]

View File

@@ -101,5 +101,21 @@ export const classesApi = {
*/
createFA: (data: any, company_id: number): Promise<ApiResponse<any>> => {
return api.post(`/v1/a76/classes/fa?company_id=${company_id}`, data);
},
/**
* Get all classes with FA data in a single query (optimized, eliminates N+1)
*/
getWithFAData: (params: {
company_id: number;
page?: number;
page_size?: number;
}): Promise<ApiResponse<A76Class[]>> => {
const query = new URLSearchParams({
company_id: params.company_id.toString(),
page: (params.page || 1).toString(),
page_size: (params.page_size || 1000).toString()
});
return api.get(`/v1/a76/classes/with-fa-data?${query.toString()}`);
}
};

View File

@@ -392,6 +392,26 @@ export const invoicesApi = {
return api.delete(`/v1/a76/invoices/${invoiceId}?${params.toString()}`);
},
/**
* Obtiene datos para la creación de una factura (catálogos consolidados)
*/
getCreationData: (companyId: number) => {
const params = new URLSearchParams({
company_id: companyId.toString()
});
return api.get<any>(`/v1/a76/invoices/creation-data?${params.toString()}`);
},
/**
* Obtiene datos para la edición de una factura (catálogos consolidados + factura)
*/
getEditionData: (invoiceId: number, companyId: number) => {
const params = new URLSearchParams({
company_id: companyId.toString()
});
return api.get<any>(`/v1/a76/invoices/${invoiceId}/edition-data?${params.toString()}`);
},
// --- Nested Resources ---
/**

View File

@@ -26,358 +26,110 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => {
parsedOperationType = operationTypeParam;
}
// Cargar datos de referencia necesarios
const invoiceTypesPromise = authenticatedFetch(
'v1/public/reference_data/invoice-types/?page=1&page_size=100',
{},
cookies,
fetch
);
try {
let invoiceData: any = null;
let catalogsData: any = {};
let defaultSettings: any = null;
let isCreate = false;
const customsBrokersPromise = authenticatedFetch(
`v1/a76/customs-brokers/?company_id=${companyId}&page=1&page_size=100`,
{},
cookies,
fetch
);
// Fetch Default Settings separately if applicable (for creation)
const settingsPromise = (params.id === 'new' && parsedOperationType && invoiceTypeParam && companyId)
? authenticatedFetch(
`v1/a76/invoice-settings/${invoiceTypeParam}?operation_type=${parsedOperationType}&company_id=${companyId}`,
{},
cookies,
fetch
).then(res => res.ok ? res.json() : null).catch(err => {
console.error('Error fetching defaults:', err);
return null;
})
: Promise.resolve(null);
// Cargar clientes y proveedores
const clientsPromise = authenticatedFetch(
`v1/a76/clients-providers/?company_id=${companyId}&type=client&page=1&page_size=1000`,
{},
cookies,
fetch
);
const providersPromise = authenticatedFetch(
`v1/a76/clients-providers/?company_id=${companyId}&type=provider&page=1&page_size=1000`,
{},
cookies,
fetch
);
// Cargar tipos de moneda, transporte, etc.
const currencyTypesPromise = authenticatedFetch(
'v1/public/reference_data/currency-types/?page=1&page_size=100',
{},
cookies,
fetch
);
const transportTypesPromise = authenticatedFetch(
'v1/public/reference_data/transport-types/?page=1&page_size=100',
{},
cookies,
fetch
);
const transportersPromise = authenticatedFetch(
`v1/a76/transportation/transporters/?company_id=${companyId}&page=1&page_size=100`,
{},
cookies,
fetch
);
const vehiclesPromise = authenticatedFetch(
`v1/a76/transportation/vehicles/?company_id=${companyId}&page=1&page_size=100`,
{},
cookies,
fetch
);
const driversPromise = authenticatedFetch(
`v1/a76/transportation/drivers/?company_id=${companyId}`,
{},
cookies,
fetch
);
const trailersPromise = authenticatedFetch(
`v1/a76/transportation/trailers/?company_id=${companyId}&page=1&page_size=100`,
{},
cookies,
fetch
);
const customsSectionsPromise = authenticatedFetch(
'v1/public/reference_data/customs-sections/?page=1&page_size=100',
{},
cookies,
fetch
);
const codePedimentoRegimensPromise = authenticatedFetch(
'v1/public/reference_data/code-pedimento-regimens/?page=1&page_size=1000',
{},
cookies,
fetch
);
const sealsPromise = authenticatedFetch(
`v1/a76/seals/?company_id=${companyId}&page=1&page_size=100`,
{},
cookies,
fetch
);
const incotermsPromise = authenticatedFetch(
'v1/public/reference_data/incoterms/?page=1&page_size=100',
{},
cookies,
fetch
);
const pedimentosPromise = authenticatedFetch(
`v1/a76/pedimentos/?company_id=${companyId}&page=1&page_size=100`,
{},
cookies,
fetch
);
const transportModesPromise = authenticatedFetch(
'v1/public/reference_data/transport-modes/?page=1&page_size=100',
{},
cookies,
fetch
);
// Si el ID es "new", es una creación
if (params.id === 'new') {
try {
// Fetch default settings if type, operation and company are present
const settingsPromise = (parsedOperationType && invoiceTypeParam && companyId)
? authenticatedFetch(
`v1/a76/invoice-settings/${invoiceTypeParam}?operation_type=${parsedOperationType}&company_id=${companyId}`,
if (params.id === 'new') {
isCreate = true;
// Call Consolidated Creation Endpoint
const [creationResponse, settingsResult] = await Promise.all([
authenticatedFetch(
`v1/a76/invoices/creation-data?company_id=${companyId}`,
{},
cookies,
fetch
).catch((err) => {
console.error('Error fetching defaults in server load:', err);
return null;
})
: Promise.resolve(null);
const [
invoiceTypesResponse,
customsBrokersResponse,
clientsResponse,
providersResponse,
currencyTypesResponse,
transportTypesResponse,
transportersResponse,
vehiclesResponse,
driversResponse,
trailersResponse,
customsSectionsResponse,
codePedimentoRegimensResponse,
sealsResponse,
incotermsResponse,
pedimentosResponse,
transportModesResponse,
settingsResponse
] = await Promise.all([
invoiceTypesPromise,
customsBrokersPromise,
clientsPromise,
providersPromise,
currencyTypesPromise,
transportTypesPromise,
transportersPromise,
vehiclesPromise,
driversPromise,
trailersPromise,
customsSectionsPromise,
codePedimentoRegimensPromise,
sealsPromise,
incotermsPromise,
pedimentosPromise,
transportModesPromise,
),
settingsPromise
]);
const invoiceTypes = invoiceTypesResponse.ok ? await invoiceTypesResponse.json() : { items: [] };
const customsBrokers = customsBrokersResponse.ok ? await customsBrokersResponse.json() : { items: [] };
const clients = clientsResponse.ok ? await clientsResponse.json() : { items: [] };
const providers = providersResponse.ok ? await providersResponse.json() : { items: [] };
const currencyTypes = currencyTypesResponse.ok ? await currencyTypesResponse.json() : { items: [] };
const transportTypes = transportTypesResponse.ok ? await transportTypesResponse.json() : { items: [] };
const transporters = transportersResponse.ok ? await transportersResponse.json() : { items: [] };
const vehicles = vehiclesResponse.ok ? await vehiclesResponse.json() : { items: [] };
const drivers = driversResponse.ok ? await driversResponse.json() : { items: [] };
const trailers = trailersResponse.ok ? await trailersResponse.json() : { items: [] };
const customsSections = customsSectionsResponse.ok ? await customsSectionsResponse.json() : { items: [] };
const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] };
const seals = sealsResponse.ok ? await sealsResponse.json() : { items: [] };
const incoterms = incotermsResponse.ok ? await incotermsResponse.json() : { items: [] };
const pedimentos = pedimentosResponse.ok ? await pedimentosResponse.json() : { items: [] };
const transportModes = transportModesResponse.ok ? await transportModesResponse.json() : { items: [] };
let defaultSettings = null;
if (settingsResponse && settingsResponse.ok) {
const settingsData = await settingsResponse.json();
defaultSettings = settingsData.settings || null;
if (creationResponse.ok) {
catalogsData = await creationResponse.json();
} else {
console.error('Error fetching creation data:', creationResponse.status);
// We continuing with empty catalogs might be better than crashing?
// But UI will likely be broken. Let's rely on empty arrays initialization below.
}
return {
invoice: null,
invoiceId: null,
isCreate: true,
invoiceTypes: invoiceTypes.items || [],
customsBrokers: customsBrokers.items || [],
clients: clients.items || [],
providers: providers.items || [],
currencyTypes: currencyTypes.items || [],
transportTypes: transportTypes.items || [],
transporters: transporters.items || [],
vehicles: vehicles.items || [],
drivers: drivers.items || [],
trailers: trailers.items || [],
customsSections: customsSections.items || [],
codePedimentoRegimens: codePedimentoRegimens.items || [],
seals: seals.items || [],
incoterms: incoterms.items || [],
pedimentos: pedimentos.items || [],
transportModes: transportModes.items || [],
defaultSettings,
// Filtros desde query parameters para preselección
filters: {
operation_type: parsedOperationType,
invoice_type: invoiceTypeParam || null
if (settingsResult && settingsResult.settings) {
defaultSettings = settingsResult.settings;
}
} else {
// Edition Mode
const invoiceId = parseInt(params.id);
if (isNaN(invoiceId)) {
throw error(400, 'ID de factura inválido');
}
// Call Consolidated Edition Endpoint
const editionResponse = await authenticatedFetch(
`v1/a76/invoices/${invoiceId}/edition-data?company_id=${companyId}`,
{},
cookies,
fetch
);
if (!editionResponse.ok) {
if (editionResponse.status === 404) {
throw error(404, 'Factura no encontrada');
}
};
} catch (err) {
console.error('Error loading data for new invoice:', err);
// En caso de error, devolver estructura mínima para que la página pueda cargar
return {
invoice: null,
invoiceId: null,
isCreate: true,
invoiceTypes: [],
customsBrokers: [],
clients: [],
providers: [],
currencyTypes: [],
transportTypes: [],
transporters: [],
vehicles: [],
drivers: [],
trailers: [],
customsSections: [],
codePedimentoRegimens: [],
seals: [],
incoterms: [],
pedimentos: [],
transportModes: [],
filters: {
operation_type: parsedOperationType,
invoice_type: invoiceTypeParam || null
}
};
}
}
throw error(editionResponse.status, 'Error al cargar la factura');
}
const invoiceId = parseInt(params.id);
if (isNaN(invoiceId)) {
throw error(400, 'ID de factura inválido');
}
try {
// Cargar la factura desde el backend
const response = await authenticatedFetch(
`v1/a76/invoices/${invoiceId}?company_id=${companyId}`,
{},
cookies,
fetch
);
if (!response.ok) {
throw error(response.status, 'Error al cargar la factura');
const editionData = await editionResponse.json();
catalogsData = editionData; // It includes catalogs + invoice
invoiceData = editionData.invoice;
}
const invoice = await response.json();
// Cargar también los datos de referencia para edición
const [
invoiceTypesResponse,
customsBrokersResponse,
clientsResponse,
providersResponse,
currencyTypesResponse,
transportTypesResponse,
transportersResponse,
vehiclesResponse,
driversResponse,
trailersResponse,
customsSectionsResponse,
codePedimentoRegimensResponse,
sealsResponse,
incotermsResponse,
pedimentosResponse,
transportModesResponse
] = await Promise.all([
invoiceTypesPromise,
customsBrokersPromise,
clientsPromise,
providersPromise,
currencyTypesPromise,
transportTypesPromise,
transportersPromise,
vehiclesPromise,
driversPromise,
trailersPromise,
customsSectionsPromise,
codePedimentoRegimensPromise,
sealsPromise,
incotermsPromise,
pedimentosPromise,
transportModesPromise
]);
const invoiceTypes = invoiceTypesResponse.ok ? await invoiceTypesResponse.json() : { items: [] };
const customsBrokers = customsBrokersResponse.ok ? await customsBrokersResponse.json() : { items: [] };
const clients = clientsResponse.ok ? await clientsResponse.json() : { items: [] };
const providers = providersResponse.ok ? await providersResponse.json() : { items: [] };
const currencyTypes = currencyTypesResponse.ok ? await currencyTypesResponse.json() : { items: [] };
const transportTypes = transportTypesResponse.ok ? await transportTypesResponse.json() : { items: [] };
const transporters = transportersResponse.ok ? await transportersResponse.json() : { items: [] };
const vehicles = vehiclesResponse.ok ? await vehiclesResponse.json() : { items: [] };
const drivers = driversResponse.ok ? await driversResponse.json() : { items: [] };
const trailers = trailersResponse.ok ? await trailersResponse.json() : { items: [] };
const customsSections = customsSectionsResponse.ok ? await customsSectionsResponse.json() : { items: [] };
const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] };
const seals = sealsResponse.ok ? await sealsResponse.json() : { items: [] };
const incoterms = incotermsResponse.ok ? await incotermsResponse.json() : { items: [] };
const pedimentos = pedimentosResponse.ok ? await pedimentosResponse.json() : { items: [] };
const transportModes = transportModesResponse.ok ? await transportModesResponse.json() : { items: [] };
// Map snake_case response to camelCase props expected by Svelte Page
// Providing empty arrays as defaults if something is missing
return {
invoice,
invoiceId,
isCreate: false,
invoiceTypes: invoiceTypes.items || [],
customsBrokers: customsBrokers.items || [],
clients: clients.items || [],
providers: providers.items || [],
currencyTypes: currencyTypes.items || [],
transportTypes: transportTypes.items || [],
transporters: transporters.items || [],
vehicles: vehicles.items || [],
drivers: drivers.items || [],
trailers: trailers.items || [],
customsSections: customsSections.items || [],
codePedimentoRegimens: codePedimentoRegimens.items || [],
seals: seals.items || [],
incoterms: incoterms.items || [],
pedimentos: pedimentos.items || [],
transportModes: transportModes.items || [],
// Filtros desde query parameters para preselección
invoice: invoiceData,
invoiceId: params.id === 'new' ? null : parseInt(params.id),
isCreate: isCreate,
invoiceTypes: catalogsData.invoice_types || [],
customsBrokers: catalogsData.customs_brokers || [],
clients: catalogsData.clients || [],
providers: catalogsData.providers || [],
currencyTypes: catalogsData.currency_types || [],
transportTypes: catalogsData.transport_types || [],
transporters: catalogsData.transporters || [],
vehicles: catalogsData.vehicles || [],
drivers: catalogsData.drivers || [],
trailers: catalogsData.trailers || [],
customsSections: catalogsData.customs_sections || [],
codePedimentoRegimens: catalogsData.code_pedimento_regimens || [],
seals: catalogsData.seals || [],
incoterms: catalogsData.incoterms || [],
pedimentos: catalogsData.pedimentos || [],
transportModes: catalogsData.transport_modes || [],
defaultSettings: defaultSettings,
filters: {
operation_type: parsedOperationType,
invoice_type: invoiceTypeParam || null
}
};
} catch (err) {
console.error('Error loading invoice:', err);
throw error(500, 'Error al cargar la factura');
} catch (err: any) {
console.error('Error in load function:', err);
if (err && err.status === 404) throw err; // Propagate 404
throw error(500, 'Error interno al cargar la página');
}
};

View File

@@ -38,6 +38,7 @@
import ExchangeRateDialog from '$lib/components/dashboard/exchange_rate/create-edit-dialog.svelte';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import { obtenerAtajosEdicionFactura } from '$lib/config/shortcuts/dashboard/invoices/edit';
import { api } from '$lib/api';
// Cargar companyStore solo en el cliente - no usamos sidebar en esta página
let companyStore: any = $state(undefined);
@@ -241,7 +242,7 @@
// Estados para saber si existen datos previos
let observationExists = $state(!!data.defaultSettings?.observationFormData);
let itemsExists = $state(!!(data.defaultSettings?.itemsFormData?.items?.length));
let itemsExists = $state(!!data.defaultSettings?.itemsFormData?.items?.length);
let othersExists = $state(!!data.defaultSettings?.othersFormData);
let continuationExists = $state(!!data.defaultSettings?.continuationFormData);
@@ -525,7 +526,7 @@
}
let isLoadingDefaults = $state(false);
// Compute initial key from source data to avoid state reference warning
const initialLoadedKey = data.defaultSettings?.InvoiceTopFieldsFormData
? `${data.defaultSettings.InvoiceTopFieldsFormData.invoice_type || ''}-${data.defaultSettings.InvoiceTopFieldsFormData.operation_type || ''}`
@@ -552,22 +553,12 @@
isLoadingDefaults = true;
try {
const token = data.user?.token;
const headers: HeadersInit = {
'Content-Type': 'application/json'
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const res = await fetch(
`/api/v1/a76/invoice-settings/${InvoiceTopFieldsFormData.invoice_type}?operation_type=${InvoiceTopFieldsFormData.operation_type}&company_id=${companyStore.activeCompany.id}`,
{ headers }
const res = await api.get(
`/v1/a76/invoice-settings/${InvoiceTopFieldsFormData.invoice_type}?operation_type=${InvoiceTopFieldsFormData.operation_type}&company_id=${companyStore.activeCompany.id}`
);
if (res.ok) {
const settingsData = await res.json();
const settings = settingsData.settings || {};
if (res.success && res.data) {
const settings = res.data.settings || {};
// Mark as loaded even if empty to prevent retries for the same combination
lastLoadedKey = currentKey;
@@ -593,7 +584,7 @@
InvoiceTopFieldsFormData.operation_type || data.filters?.operation_type;
observationExists = !!settings.observationFormData;
itemsExists = !!(itemsFormData.items?.length);
itemsExists = !!itemsFormData.items?.length;
othersExists = !!settings.othersFormData;
continuationExists = !!settings.continuationFormData;

View File

@@ -16,72 +16,44 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
throw error(400, 'No se encontró una compañía seleccionada');
}
// Cargar pedimento_codes (datos de referencia)
const pedimentoCodesPromise = authenticatedFetch(
'v1/public/reference_data/pedimento-codes?page=1&page_size=100',
{},
cookies,
fetch
);
// Cargar customs_sections (datos de referencia)
const customsSectionsPromise = authenticatedFetch(
'v1/public/reference_data/customs-sections?page=1&page_size=100',
{},
cookies,
fetch
);
// Cargar customs_brokers (datos de referencia)
const customsBrokersPromise = authenticatedFetch(
`v1/a76/customs-brokers?company_id=${companyId}`,
{},
cookies,
fetch
);
// Cargar clientes (para el select de client_id)
const clientsPromise = authenticatedFetch(
`v1/a76/clients-providers?company_id=${companyId}&type=client&page=1&page_size=1000`,
{},
cookies,
fetch
);
// Cargar code-pedimento-regimens (para interdependencia de campos)
const codePedimentoRegimensPromise = authenticatedFetch(
'v1/public/reference_data/code-pedimento-regimens?page=1&page_size=100',
{},
cookies,
fetch
);
// Si el ID es "new", es una creación
if (params.id === 'new') {
try {
const [pedimentoCodesResponse, customsSectionsResponse, customsBrokersResponse, clientsResponse, codePedimentoRegimensResponse] = await Promise.all([
pedimentoCodesPromise,
customsSectionsPromise,
customsBrokersPromise,
clientsPromise,
codePedimentoRegimensPromise
]);
// Call consolidated creation endpoint
const response = await authenticatedFetch(
`v1/a76/pedimentos/creation-data?company_id=${companyId}`,
{},
cookies,
fetch
);
const pedimentoCodes = pedimentoCodesResponse.ok ? await pedimentoCodesResponse.json() : { items: [] };
const customsSections = customsSectionsResponse.ok ? await customsSectionsResponse.json() : { items: [] };
const customsBrokers = customsBrokersResponse.ok ? await customsBrokersResponse.json() : { items: [] };
const clients = clientsResponse.ok ? await clientsResponse.json() : { items: [] };
const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] };
if (!response.ok) {
console.error('Error fetching creation data:', response.status);
// Graceful degradation: return empty data
return {
pedimento: null,
pedimentoId: null,
isCreate: true,
pedimentoCodes: [],
customsSections: [],
customsBrokers: [],
clients: [],
codePedimentoRegimens: [],
error: 'Error al cargar catálogos. Verifique la conexión con el backend.'
};
}
const data = await response.json();
return {
pedimento: null,
pedimentoId: null,
isCreate: true,
pedimentoCodes: pedimentoCodes.items || [],
customsSections: customsSections.items || [],
customsBrokers: customsBrokers.items || [],
clients: clients.items || [],
codePedimentoRegimens: codePedimentoRegimens.items || []
pedimentoCodes: data.pedimento_codes || [],
customsSections: data.customs_sections || [],
customsBrokers: data.customs_brokers || [],
clients: data.clients || [],
codePedimentoRegimens: data.code_pedimento_regimens || []
};
} catch (e) {
console.error('❌ Error loading new pedimento data:', e);
@@ -106,11 +78,9 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
}
try {
// Cargar el pedimento desde el backend usando authenticatedFetch
// Call consolidated edition endpoint
const response = await authenticatedFetch(
`v1/a76/pedimentos/${pedimentoId}?company_id=${companyId}`,
`v1/a76/pedimentos/${pedimentoId}/edition-data?company_id=${companyId}`,
{},
cookies,
fetch
@@ -126,32 +96,17 @@ export const load: PageServerLoad = async ({ params, cookies, fetch }) => {
throw error(response.status, 'Error al cargar el pedimento');
}
const pedimento = await response.json();
// Cargar pedimento_codes y customs_sections
const [pedimentoCodesResponse, customsSectionsResponse, customsBrokersResponse, clientsResponse, codePedimentoRegimensResponse] = await Promise.all([
pedimentoCodesPromise,
customsSectionsPromise,
customsBrokersPromise,
clientsPromise,
codePedimentoRegimensPromise
]);
const pedimentoCodes = pedimentoCodesResponse.ok ? await pedimentoCodesResponse.json() : { items: [] };
const customsSections = customsSectionsResponse.ok ? await customsSectionsResponse.json() : { items: [] };
const customsBrokers = customsBrokersResponse.ok ? await customsBrokersResponse.json() : { items: [] };
const clients = clientsResponse.ok ? await clientsResponse.json() : { items: [] };
const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] };
const data = await response.json();
return {
pedimento,
pedimentoId,
pedimento: data.pedimento,
pedimentoId: data.pedimento_id,
isCreate: false,
pedimentoCodes: pedimentoCodes.items || [],
customsSections: customsSections.items || [],
customsBrokers: customsBrokers.items || [],
clients: clients.items || [],
codePedimentoRegimens: codePedimentoRegimens.items || []
pedimentoCodes: data.pedimento_codes || [],
customsSections: data.customs_sections || [],
customsBrokers: data.customs_brokers || [],
clients: data.clients || [],
codePedimentoRegimens: data.code_pedimento_regimens || []
};
} catch (e) {
console.error('Error loading pedimento:', e);