Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Depreciation Catalog Module
|
||||
"""
|
||||
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Depreciation Catalog DTOs
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DepreciationCatalogCreate(BaseModel):
|
||||
"""DTO for creating a depreciation catalog entry"""
|
||||
fraction: str = Field(..., max_length=10)
|
||||
description: str = Field(..., max_length=500)
|
||||
depreciation_rate: float = Field(..., ge=0, le=100)
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class DepreciationCatalogUpdate(BaseModel):
|
||||
"""DTO for updating a depreciation catalog entry"""
|
||||
fraction: str | None = Field(None, max_length=10)
|
||||
description: str | None = Field(None, max_length=500)
|
||||
depreciation_rate: float | None = Field(None, ge=0, le=100)
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class DepreciationCatalogResponse(BaseModel):
|
||||
"""DTO for depreciation catalog response"""
|
||||
id: int
|
||||
tenant_id: int
|
||||
company_id: int
|
||||
fraction: str
|
||||
description: str
|
||||
depreciation_rate: float
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Depreciation Catalog Model
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Numeric, ForeignKey, PrimaryKeyConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
|
||||
|
||||
class DepreciationCatalog(Base, TenantScopedMixin, TimestampMixin):
|
||||
"""Depreciation Catalog Model"""
|
||||
|
||||
__tablename__ = 'depreciation_catalog'
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("id", name="depreciation_catalog_pkey"),
|
||||
{'schema': 'a76'}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
|
||||
# Fields
|
||||
fraction: Mapped[str] = mapped_column(String(10), nullable=False, index=True)
|
||||
description: Mapped[str] = mapped_column(String(500), nullable=False, index=True)
|
||||
depreciation_rate: Mapped[float] = mapped_column(Numeric(5, 2), nullable=False)
|
||||
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
Depreciation Catalog Routes
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
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 api.v1.modules.a76.general_catalogs.depreciation_catalog.service import DepreciationCatalogService
|
||||
from api.v1.modules.a76.general_catalogs.depreciation_catalog.dto import (
|
||||
DepreciationCatalogCreate,
|
||||
DepreciationCatalogUpdate,
|
||||
DepreciationCatalogResponse
|
||||
)
|
||||
|
||||
# Create base router with generic CRUD routes (disabled list because we'll create a custom one)
|
||||
base_router = TenantCRUDRoutes(
|
||||
service=DepreciationCatalogService,
|
||||
create_schema=DepreciationCatalogCreate,
|
||||
update_schema=DepreciationCatalogUpdate,
|
||||
response_schema=DepreciationCatalogResponse,
|
||||
prefix="/depreciation-catalog",
|
||||
tags=["a76 / general catalogs / depreciation catalog"],
|
||||
resource_name="DepreciationCatalog",
|
||||
id_name="depreciation_catalog_id",
|
||||
enable_list=False, # Disable default list, we'll add custom one
|
||||
enable_filters=False,
|
||||
default_page_size=100,
|
||||
max_page_size=1000,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/depreciation-catalog", tags=["a76 / general catalogs / depreciation catalog"])
|
||||
|
||||
# Custom list endpoint with search filter
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=Dict[str, Any],
|
||||
summary="List Depreciation Catalog",
|
||||
description="Get paginated list of Depreciation Catalog entries with optional search filter",
|
||||
)
|
||||
async def list_depreciation_catalog(
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
page: int = Query(1, ge=1, description="Page number"),
|
||||
page_size: int = Query(100, ge=1, le=1000, description="Page size"),
|
||||
search: Optional[str] = Query(None, description="Search in fraction or description"),
|
||||
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)
|
||||
|
||||
items, total = DepreciationCatalogService.get_all(
|
||||
db, tenant_id, company_id, page, page_size, search
|
||||
)
|
||||
|
||||
return {
|
||||
"items": [DepreciationCatalogResponse.model_validate(item) for item in items],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"pages": (total + page_size - 1) // page_size,
|
||||
}
|
||||
|
||||
# Include other CRUD routes from base router
|
||||
router.include_router(base_router.router)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Datos semilla para Catálogo de Depreciación
|
||||
Basado en artículo 34 de la Ley del Impuesto Sobre la Renta
|
||||
"""
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
# Formato: (fraction, description, depreciation_rate)
|
||||
seed = [
|
||||
("I", "a) Para inmuebles declarados como monumentos arqueológicos, artísticos, históricos o patrimoniales, conforme a la Ley Federal sobre Monumentos y Zonas Arqueológicos, Artísticos e Históricos, que cuenten con el certificado de restauración expedido por el Instituto Nacional de Antropología e Historia o el Instituto Nacional de Bellas Artes.", Decimal("10.00")),
|
||||
("I", "b) En los demás casos.", Decimal("5.00")),
|
||||
("II", "a) Para bombas de suministro de combustible a trenes.", Decimal("3.00")),
|
||||
("II", "b) Para vías férreas.", Decimal("5.00")),
|
||||
("II", "c) Para carros de ferrocarril, locomotoras, armones y autoarmones.", Decimal("6.00")),
|
||||
("II", "d) Para maquinaria niveladora de vías, desclavadoras, esmeriles para vías, gatos de motor para levantar la vía, removedora, insertadora y taladradora de durmientes.", Decimal("7.00")),
|
||||
("II", "e) Para el equipo de comunicación, señalización y telemando.", Decimal("10.00")),
|
||||
("III", "Para mobiliario y equipo de oficina.", Decimal("10.00")),
|
||||
("IV", "Para embarcaciones.", Decimal("6.00")),
|
||||
("IX", "Para semovientes y vegetales.", Decimal("100.00")),
|
||||
("V", "a) Para los dedicados a la aerofumigación agrícola.", Decimal("25.00")),
|
||||
("V", "b) Para los demás.", Decimal("10.00")),
|
||||
("VI", "Para automóviles, autobuses, camiones de carga, tractocamiones, montacargas y remolques.", Decimal("25.00")),
|
||||
("VII", "Para computadoras personales de escritorio y portátiles; servidores; impresoras, lectores ópticos, graficadores, lectores de código de barras, digitalizadores, unidades de almacenamiento externo y concentradores de redes de cómputo.", Decimal("30.00")),
|
||||
("VIII", "Para dados, troqueles, moldes, matrices y herramental.", Decimal("35.00")),
|
||||
("X", "a) Para torres de transmisión y cables, excepto los de fibra óptica.", Decimal("5.00")),
|
||||
("X", "b) Para sistemas de radio, incluyendo equipo de transmisión y manejo que utiliza el espectro radioeléctrico, tales como el de radiotransmisión de microonda digital o analógica, torres de microondas y guías de onda.", Decimal("8.00")),
|
||||
("X", "c) Para equipo utilizado en la transmisión, tales como circuitos de la planta interna que no forman parte de la conmutación y cuyas funciones se enfocan hacia las troncales que llegan a la central telefónica, incluye multiplexores, equipos concentradores y ruteadores.", Decimal("10.00")),
|
||||
("X", "d) Para equipo de la central telefónica destinado a la conmutación de llamadas de tecnología distinta a la electromecánica.", Decimal("25.00")),
|
||||
("X", "e) Para los demás.", Decimal("10.00")),
|
||||
("XI", "a) Para el segmento satelital en el espacio, incluyendo el cuerpo principal del satélite, los transpondedores, las antenas para la transmisión y recepción de comunicaciones digitales y análogas, y el equipo de monitoreo en el satélite.", Decimal("8.00")),
|
||||
("XI", "b) Para el equipo satelital en tierra, incluyendo las antenas para la transmisión y recepción de comunicaciones digitales y análogas y el equipo para el monitoreo del satélite.", Decimal("10.00")),
|
||||
("XII", "Para adaptaciones que se realicen a instalaciones que impliquen adiciones o mejoras al activo fijo, siempre que dichas adaptaciones tengan como finalidad facilitar a las personas con discapacidad a que se refiere el artículo 186 de esta Ley, el acceso y uso de las instalaciones del contribuyente.", Decimal("100.00")),
|
||||
("XIII", "Para maquinaria y equipo para la generación de energía proveniente de fuentes renovables o de sistemas de cogeneración de electricidad eficiente.", Decimal("100.00")),
|
||||
]
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Depreciation Catalog Service
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import or_, func
|
||||
from api.v1.modules.a76.general_catalogs.depreciation_catalog.models import DepreciationCatalog
|
||||
|
||||
|
||||
class DepreciationCatalogService:
|
||||
"""Service for depreciation catalog operations"""
|
||||
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
page: int = 1,
|
||||
page_size: int = 100,
|
||||
search: str | None = None
|
||||
):
|
||||
"""Get all depreciation catalog entries with optional search"""
|
||||
query = db.query(DepreciationCatalog).filter(
|
||||
DepreciationCatalog.tenant_id == tenant_id,
|
||||
DepreciationCatalog.company_id == company_id
|
||||
)
|
||||
|
||||
# Apply search filter if provided
|
||||
if search:
|
||||
search_filter = or_(
|
||||
DepreciationCatalog.fraction.ilike(f"%{search}%"),
|
||||
DepreciationCatalog.description.ilike(f"%{search}%"),
|
||||
func.cast(DepreciationCatalog.depreciation_rate, db.String).ilike(f"%{search}%")
|
||||
)
|
||||
query = query.filter(search_filter)
|
||||
|
||||
# Get total count before pagination
|
||||
total = query.count()
|
||||
|
||||
# Apply pagination
|
||||
offset = (page - 1) * page_size
|
||||
items = query.order_by(
|
||||
DepreciationCatalog.fraction,
|
||||
DepreciationCatalog.depreciation_rate
|
||||
).offset(offset).limit(page_size).all()
|
||||
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, id: int, tenant_id: int, company_id: int):
|
||||
"""Get depreciation catalog entry by ID"""
|
||||
return db.query(DepreciationCatalog).filter(
|
||||
DepreciationCatalog.id == id,
|
||||
DepreciationCatalog.tenant_id == tenant_id,
|
||||
DepreciationCatalog.company_id == company_id
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, data: dict):
|
||||
"""Create new depreciation catalog entry"""
|
||||
entry = DepreciationCatalog(**data)
|
||||
db.add(entry)
|
||||
db.commit()
|
||||
db.refresh(entry)
|
||||
return entry
|
||||
|
||||
@staticmethod
|
||||
def update(db: Session, id: int, tenant_id: int, company_id: int, data: dict):
|
||||
"""Update depreciation catalog entry"""
|
||||
entry = DepreciationCatalogService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not entry:
|
||||
return None
|
||||
|
||||
for key, value in data.items():
|
||||
if value is not None:
|
||||
setattr(entry, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(entry)
|
||||
return entry
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, id: int, tenant_id: int, company_id: int):
|
||||
"""Delete depreciation catalog entry"""
|
||||
entry = DepreciationCatalogService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not entry:
|
||||
return False
|
||||
|
||||
db.delete(entry)
|
||||
db.commit()
|
||||
return True
|
||||
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
__init__.py para módulo de catálogo FDA
|
||||
"""
|
||||
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
DTOs para el catálogo de claves FDA
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class FDACatalogCreate(BaseModel):
|
||||
"""DTO para crear una entrada en el catálogo FDA"""
|
||||
fda_key: str = Field(..., max_length=20, description="Clave FDA")
|
||||
description: str = Field(..., max_length=500, description="Descripción")
|
||||
fda_code: Optional[str] = Field(None, max_length=50, description="Código FDA")
|
||||
requirements: Optional[str] = Field(None, max_length=500, description="Requerimientos")
|
||||
manufacturer_number: Optional[str] = Field(None, max_length=50, description="Número de fabricante")
|
||||
country_of_production: Optional[str] = Field(None, max_length=100, description="País de producción")
|
||||
storage_status: Optional[str] = Field(None, max_length=100, description="Estatus de almacenaje")
|
||||
warehouse_code: Optional[str] = Field(None, max_length=20, description="Código de almacén")
|
||||
call_atl: Optional[str] = Field(None, max_length=20, description="CallAtl")
|
||||
|
||||
model_config = {
|
||||
"from_attributes": True
|
||||
}
|
||||
|
||||
|
||||
class FDACatalogUpdate(BaseModel):
|
||||
"""DTO para actualizar una entrada en el catálogo FDA"""
|
||||
fda_key: Optional[str] = Field(None, max_length=20, description="Clave FDA")
|
||||
description: Optional[str] = Field(None, max_length=500, description="Descripción")
|
||||
fda_code: Optional[str] = Field(None, max_length=50, description="Código FDA")
|
||||
requirements: Optional[str] = Field(None, max_length=500, description="Requerimientos")
|
||||
manufacturer_number: Optional[str] = Field(None, max_length=50, description="Número de fabricante")
|
||||
country_of_production: Optional[str] = Field(None, max_length=100, description="País de producción")
|
||||
storage_status: Optional[str] = Field(None, max_length=100, description="Estatus de almacenaje")
|
||||
warehouse_code: Optional[str] = Field(None, max_length=20, description="Código de almacén")
|
||||
call_atl: Optional[str] = Field(None, max_length=20, description="CallAtl")
|
||||
|
||||
model_config = {
|
||||
"from_attributes": True
|
||||
}
|
||||
|
||||
|
||||
class FDACatalogResponse(BaseModel):
|
||||
"""DTO para respuesta del catálogo FDA"""
|
||||
id: int
|
||||
fda_key: str
|
||||
description: str
|
||||
fda_code: Optional[str]
|
||||
requirements: Optional[str]
|
||||
manufacturer_number: Optional[str]
|
||||
country_of_production: Optional[str]
|
||||
storage_status: Optional[str]
|
||||
warehouse_code: Optional[str]
|
||||
call_atl: Optional[str]
|
||||
created_at: Optional[str]
|
||||
updated_at: Optional[str]
|
||||
|
||||
model_config = {
|
||||
"from_attributes": True
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
Modelo para el catálogo de claves FDA
|
||||
"""
|
||||
from typing import Optional
|
||||
from sqlalchemy import String, Integer, UniqueConstraint, PrimaryKeyConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from core.database import Base
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
|
||||
|
||||
class FDACatalog(Base, TenantScopedMixin, TimestampMixin):
|
||||
"""Catálogo de claves FDA"""
|
||||
__tablename__ = "fda_catalog"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("id", name="fda_catalog_pkey"),
|
||||
UniqueConstraint('tenant_id', 'company_id', 'fda_key', name='idx_fda_catalog_unique'),
|
||||
{'schema': 'a76'}
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
fda_key: Mapped[str] = mapped_column(String(20), nullable=False, index=True)
|
||||
description: Mapped[str] = mapped_column(String(500), nullable=False, index=True)
|
||||
fda_code: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
requirements: Mapped[Optional[str]] = mapped_column(String(500))
|
||||
manufacturer_number: Mapped[Optional[str]] = mapped_column(String(50))
|
||||
country_of_production: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
storage_status: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
warehouse_code: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
call_atl: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Rutas para el catálogo de claves FDA
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Any, Dict, Optional
|
||||
from api.v1.modules.a76.general_catalogs.fda_catalog.service import FDACatalogService
|
||||
from api.v1.modules.a76.general_catalogs.fda_catalog.dto import FDACatalogResponse
|
||||
from core.database import get_core_db
|
||||
from core.security import get_current_user
|
||||
from api.v1.common.tenant_crud_routes import validate_access_to_resource
|
||||
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/fda-catalog",
|
||||
tags=["a76 / general catalogs / fda catalog"]
|
||||
)
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_fda_catalog(
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
page: int = Query(1, ge=1, description="Page number"),
|
||||
page_size: int = Query(50, ge=1, le=1000, description="Page size"),
|
||||
search: Optional[str] = Query(None, description="Search in FDA key, description or code"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""Listar entradas del catálogo FDA con búsqueda y paginación"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
filters = {"search": search} if search else {}
|
||||
result = FDACatalogService.get_all(db, tenant_id, company_id, page, page_size, filters)
|
||||
|
||||
return {
|
||||
"items": result["items"],
|
||||
"total": result["total"],
|
||||
"page": result["page"],
|
||||
"page_size": result["page_size"],
|
||||
"pages": result["pages"]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{id}")
|
||||
async def get_fda_catalog(
|
||||
id: int,
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user)
|
||||
):
|
||||
"""Obtener una entrada del catálogo FDA por ID"""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
|
||||
entry = FDACatalogService.get_by_id(db, tenant_id, company_id, id)
|
||||
if not entry:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Entrada no encontrada")
|
||||
|
||||
return {
|
||||
"id": entry.id,
|
||||
"fda_key": entry.fda_key,
|
||||
"description": entry.description,
|
||||
"fda_code": entry.fda_code,
|
||||
"requirements": entry.requirements,
|
||||
"manufacturer_number": entry.manufacturer_number,
|
||||
"country_of_production": entry.country_of_production,
|
||||
"storage_status": entry.storage_status,
|
||||
"warehouse_code": entry.warehouse_code,
|
||||
"call_atl": entry.call_atl
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Datos semilla para Catálogo FDA
|
||||
|
||||
Catálogo de clasificación de la Food and Drug Administration (FDA)
|
||||
para importaciones/exportaciones de productos regulados.
|
||||
"""
|
||||
|
||||
seed = [
|
||||
# (fda_key, description, fda_code, requirements, manufacturer_number, country_of_production)
|
||||
("3012", "Dispositivos médicos clase I", "MED-I", "Registro FDA", "MFR001", "US"),
|
||||
("3013", "Dispositivos médicos clase II", "MED-II", "Registro FDA + 510(k)", "MFR002", "US"),
|
||||
("3014", "Suplementos alimenticios", "SUP-01", "Registro Establecimiento", "MFR003", "MX"),
|
||||
("3015", "Medicamentos de venta libre", "OTC-01", "Monografía FDA", "MFR004", "US"),
|
||||
("3016", "Cosméticos", "COS-01", "Registro Voluntario", "MFR005", "MX"),
|
||||
]
|
||||
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
Servicio para el catálogo de claves FDA
|
||||
"""
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDACatalog
|
||||
from api.v1.modules.a76.general_catalogs.fda_catalog.dto import FDACatalogCreate, FDACatalogUpdate
|
||||
|
||||
|
||||
class FDACatalogService:
|
||||
"""Servicio CRUD para catálogo de FDA"""
|
||||
|
||||
@staticmethod
|
||||
def get_all(db: Session, tenant_id: int, company_id: int, page: int = 1, page_size: int = 50, filters: dict = None):
|
||||
"""Obtener todas las entradas del catálogo FDA con paginación y búsqueda"""
|
||||
if filters is None:
|
||||
filters = {}
|
||||
|
||||
query = select(FDACatalog).where(
|
||||
(FDACatalog.tenant_id == tenant_id) &
|
||||
(FDACatalog.company_id == company_id)
|
||||
)
|
||||
|
||||
# Búsqueda multi-campo
|
||||
search = filters.get('search', '').strip()
|
||||
if search:
|
||||
search_pattern = f"%{search}%"
|
||||
query = query.where(
|
||||
(FDACatalog.fda_key.ilike(search_pattern)) |
|
||||
(FDACatalog.description.ilike(search_pattern)) |
|
||||
(FDACatalog.fda_code.ilike(search_pattern))
|
||||
)
|
||||
|
||||
# Contar total
|
||||
total_query = select(FDACatalog).where(
|
||||
(FDACatalog.tenant_id == tenant_id) &
|
||||
(FDACatalog.company_id == company_id)
|
||||
)
|
||||
if search:
|
||||
total_query = total_query.where(
|
||||
(FDACatalog.fda_key.ilike(search_pattern)) |
|
||||
(FDACatalog.description.ilike(search_pattern)) |
|
||||
(FDACatalog.fda_code.ilike(search_pattern))
|
||||
)
|
||||
total = db.execute(select(FDACatalog).distinct()).scalars().all().__len__()
|
||||
|
||||
# Paginación
|
||||
offset = (page - 1) * page_size
|
||||
query = query.offset(offset).limit(page_size)
|
||||
|
||||
items = db.execute(query).scalars().all()
|
||||
|
||||
return {
|
||||
"items": items,
|
||||
"total": len(items),
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"pages": (len(items) + page_size - 1) // page_size
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(db: Session, tenant_id: int, company_id: int, id: int):
|
||||
"""Obtener una entrada por ID"""
|
||||
return db.execute(
|
||||
select(FDACatalog).where(
|
||||
(FDACatalog.id == id) &
|
||||
(FDACatalog.tenant_id == tenant_id) &
|
||||
(FDACatalog.company_id == company_id)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
@staticmethod
|
||||
def create(db: Session, tenant_id: int, company_id: int, data: FDACatalogCreate):
|
||||
"""Crear una nueva entrada"""
|
||||
entry = FDACatalog(
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id,
|
||||
**data.model_dump()
|
||||
)
|
||||
db.add(entry)
|
||||
db.commit()
|
||||
db.refresh(entry)
|
||||
return entry
|
||||
|
||||
@staticmethod
|
||||
def update(db: Session, tenant_id: int, company_id: int, id: int, data: FDACatalogUpdate):
|
||||
"""Actualizar una entrada"""
|
||||
entry = FDACatalogService.get_by_id(db, tenant_id, company_id, id)
|
||||
if not entry:
|
||||
return None
|
||||
|
||||
for field, value in data.model_dump(exclude_unset=True).items():
|
||||
setattr(entry, field, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(entry)
|
||||
return entry
|
||||
|
||||
@staticmethod
|
||||
def delete(db: Session, tenant_id: int, company_id: int, id: int):
|
||||
"""Eliminar una entrada"""
|
||||
entry = FDACatalogService.get_by_id(db, tenant_id, company_id, id)
|
||||
if not entry:
|
||||
return False
|
||||
|
||||
db.delete(entry)
|
||||
db.commit()
|
||||
return True
|
||||
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Módulo de fracciones arancelarias (Tariff Fractions)
|
||||
"""
|
||||
|
||||
from .models import TariffFraction
|
||||
from .routes import router
|
||||
|
||||
__all__ = ["TariffFraction", "router"]
|
||||
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
DTOs (Data Transfer Objects) para fracciones arancelarias
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class TariffFractionCreateDTO(BaseModel):
|
||||
"""DTO para crear una fracción arancelaria"""
|
||||
|
||||
code: str = Field(..., max_length=10, description="Código completo de la fracción")
|
||||
fraction: str = Field(..., max_length=15, description="Fracción formateada")
|
||||
description: Optional[str] = Field(None, max_length=1000, description="Descripción")
|
||||
nico: Optional[str] = Field(None, max_length=10, description="Código NICO")
|
||||
umt: Optional[str] = Field(None, max_length=10, description="Unidad de medida de tarifa")
|
||||
adv_impo: Optional[str] = Field(None, max_length=20, description="Ad valorem importación")
|
||||
adv_expo: Optional[str] = Field(None, max_length=20, description="Ad valorem exportación")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TariffFractionUpdateDTO(BaseModel):
|
||||
"""DTO para actualizar una fracción arancelaria"""
|
||||
|
||||
fraction: Optional[str] = Field(None, max_length=15, description="Fracción formateada")
|
||||
description: Optional[str] = Field(None, max_length=1000, description="Descripción")
|
||||
nico: Optional[str] = Field(None, max_length=10, description="Código NICO")
|
||||
umt: Optional[str] = Field(None, max_length=10, description="Unidad de medida de tarifa")
|
||||
adv_impo: Optional[str] = Field(None, max_length=20, description="Ad valorem importación")
|
||||
adv_expo: Optional[str] = Field(None, max_length=20, description="Ad valorem exportación")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TariffFractionResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de fracción arancelaria"""
|
||||
|
||||
id: int
|
||||
tenant_id: int
|
||||
company_id: int
|
||||
code: str
|
||||
fraction: str
|
||||
description: Optional[str] = None
|
||||
nico: Optional[str] = None
|
||||
umt: Optional[str] = None
|
||||
adv_impo: Optional[str] = None
|
||||
adv_expo: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class TariffFractionBasicDTO(BaseModel):
|
||||
"""DTO para información básica de fracción arancelaria"""
|
||||
|
||||
code: str
|
||||
fraction: str
|
||||
description: Optional[str] = None
|
||||
nico: Optional[str] = None
|
||||
umt: Optional[str] = None
|
||||
adv_impo: Optional[str] = None
|
||||
adv_expo: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TariffFractionListDTO(BaseModel):
|
||||
"""DTO para lista de fracciones arancelarias"""
|
||||
|
||||
items: list[TariffFractionBasicDTO]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
pages: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TariffFractionSearchDTO(BaseModel):
|
||||
"""DTO para búsqueda de fracciones arancelarias"""
|
||||
|
||||
code: Optional[str] = Field(None, description="Buscar por código")
|
||||
fraction: Optional[str] = Field(None, description="Buscar por fracción")
|
||||
description: Optional[str] = Field(None, description="Buscar en descripción")
|
||||
nico: Optional[str] = Field(None, description="Filtrar por NICO")
|
||||
umt: Optional[str] = Field(None, description="Filtrar por UMT")
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
Modelos ORM para fracciones arancelarias (SITAR-SCAII)
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
||||
from core.database import Base
|
||||
from sqlalchemy import Integer, PrimaryKeyConstraint, String, Numeric
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
|
||||
class TariffFraction(Base, TenantScopedMixin, TimestampMixin):
|
||||
"""
|
||||
Modelo para fracciones arancelarias mexicanas (SITAR-SCAII)
|
||||
Corresponde a la tabla sFracciones
|
||||
"""
|
||||
|
||||
__tablename__ = "tariff_fractions"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint("id", name="tariff_fractions_pkey"),
|
||||
{"schema": "a76", "extend_existing": True},
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
|
||||
# Código completo de la fracción (ej: 01012101)
|
||||
code: Mapped[str] = mapped_column(String(10), unique=True, index=True)
|
||||
|
||||
# Fracción formateada (ej: 0101.21.01)
|
||||
fraction: Mapped[str] = mapped_column(String(15), index=True)
|
||||
|
||||
# Descripción de la fracción
|
||||
description: Mapped[Optional[str]] = mapped_column(String(1000))
|
||||
|
||||
# Código NICO
|
||||
nico: Mapped[Optional[str]] = mapped_column(String(10))
|
||||
|
||||
# Unidad de medida de tarifa (UMT)
|
||||
umt: Mapped[Optional[str]] = mapped_column(String(10))
|
||||
|
||||
# Ad valorem de importación
|
||||
adv_impo: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
|
||||
# Ad valorem de exportación
|
||||
adv_expo: Mapped[Optional[str]] = mapped_column(String(20))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<TariffFraction(code='{self.code}', fraction='{self.fraction}', description='{self.description[:50]}...')>"
|
||||
@@ -0,0 +1,74 @@
|
||||
"""
|
||||
Endpoints API para fracciones arancelarias
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
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 (
|
||||
TariffFractionCreateDTO,
|
||||
TariffFractionResponseDTO,
|
||||
TariffFractionUpdateDTO,
|
||||
)
|
||||
from .service import TariffFractionService
|
||||
|
||||
# Create base router with generic CRUD routes (disabled list because we'll create a custom one)
|
||||
base_router = TenantCRUDRoutes(
|
||||
service=TariffFractionService,
|
||||
create_schema=TariffFractionCreateDTO,
|
||||
update_schema=TariffFractionUpdateDTO,
|
||||
response_schema=TariffFractionResponseDTO,
|
||||
prefix="/tariff-fractions",
|
||||
tags=["a76 / general catalogs / tariff fractions"],
|
||||
resource_name="TariffFraction",
|
||||
id_name="tariff_fraction_id",
|
||||
enable_list=False, # Disable default list, we'll add custom one
|
||||
enable_filters=False,
|
||||
default_page_size=50,
|
||||
max_page_size=10000,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/tariff-fractions", tags=["a76 / general catalogs / tariff fractions"])
|
||||
|
||||
# Custom list endpoint with search filter
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=Dict[str, Any],
|
||||
summary="List Tariff Fractions",
|
||||
description="Get paginated list of Tariff Fractions with optional search filter",
|
||||
)
|
||||
async def list_tariff_fractions(
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
page: int = Query(1, ge=1, description="Page number"),
|
||||
page_size: int = Query(50, ge=1, le=10000, description="Page size"),
|
||||
search: Optional[str] = Query(None, description="Search in code, fraction, description, nico, or umt"),
|
||||
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)
|
||||
|
||||
skip = (page - 1) * page_size
|
||||
filters = {}
|
||||
if search:
|
||||
filters["search"] = search
|
||||
|
||||
items, total = TariffFractionService.get_all(
|
||||
db, tenant_id, company_id, skip, page_size, filters
|
||||
)
|
||||
|
||||
return {
|
||||
"items": [TariffFractionResponseDTO.model_validate(item) for item in items],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"pages": (total + page_size - 1) // page_size,
|
||||
}
|
||||
|
||||
# Include other CRUD routes from base router
|
||||
router.include_router(base_router.router)
|
||||
|
||||
8189
backend/api/v1/modules/a76/general_catalogs/tariff_fractions/seed.py
Normal file
8189
backend/api/v1/modules/a76/general_catalogs/tariff_fractions/seed.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,190 @@
|
||||
"""
|
||||
Service para fracciones arancelarias
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException
|
||||
import logging
|
||||
|
||||
from .models import TariffFraction
|
||||
from .dto import TariffFractionCreateDTO, TariffFractionUpdateDTO
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TariffFractionService:
|
||||
"""Service para gestionar fracciones arancelarias"""
|
||||
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[TariffFraction], int]:
|
||||
"""Obtiene todas las fracciones arancelarias con filtros opcionales"""
|
||||
|
||||
query = db.query(TariffFraction).filter(
|
||||
TariffFraction.tenant_id == tenant_id,
|
||||
TariffFraction.company_id == company_id,
|
||||
)
|
||||
|
||||
# Aplicar filtros
|
||||
if filters:
|
||||
# Si hay un filtro 'search', buscar en múltiples campos
|
||||
if filters.get("search"):
|
||||
search_term = f"%{filters['search']}%"
|
||||
query = query.filter(
|
||||
TariffFraction.code.ilike(search_term) |
|
||||
TariffFraction.fraction.ilike(search_term) |
|
||||
TariffFraction.description.ilike(search_term) |
|
||||
TariffFraction.nico.ilike(search_term) |
|
||||
TariffFraction.umt.ilike(search_term)
|
||||
)
|
||||
else:
|
||||
# Filtros individuales
|
||||
if filters.get("code"):
|
||||
query = query.filter(TariffFraction.code.ilike(f"%{filters['code']}%"))
|
||||
if filters.get("fraction"):
|
||||
query = query.filter(TariffFraction.fraction.ilike(f"%{filters['fraction']}%"))
|
||||
if filters.get("description"):
|
||||
query = query.filter(TariffFraction.description.ilike(f"%{filters['description']}%"))
|
||||
if filters.get("nico"):
|
||||
query = query.filter(TariffFraction.nico.ilike(f"%{filters['nico']}%"))
|
||||
if filters.get("umt"):
|
||||
query = query.filter(TariffFraction.umt.ilike(f"%{filters['umt']}%"))
|
||||
|
||||
total = query.count()
|
||||
items = query.offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session,
|
||||
tariff_fraction_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> Optional[TariffFraction]:
|
||||
"""Obtiene una fracción arancelaria por ID"""
|
||||
|
||||
return (
|
||||
db.query(TariffFraction)
|
||||
.filter(
|
||||
TariffFraction.id == tariff_fraction_id,
|
||||
TariffFraction.tenant_id == tenant_id,
|
||||
TariffFraction.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_by_code(
|
||||
db: Session,
|
||||
code: str,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> Optional[TariffFraction]:
|
||||
"""Obtiene una fracción arancelaria por código"""
|
||||
|
||||
return (
|
||||
db.query(TariffFraction)
|
||||
.filter(
|
||||
TariffFraction.code == code,
|
||||
TariffFraction.tenant_id == tenant_id,
|
||||
TariffFraction.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
tariff_fraction_data: TariffFractionCreateDTO,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> TariffFraction:
|
||||
"""Crea una nueva fracción arancelaria"""
|
||||
|
||||
try:
|
||||
tariff_fraction = TariffFraction(
|
||||
**tariff_fraction_data.model_dump(),
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id,
|
||||
)
|
||||
db.add(tariff_fraction)
|
||||
db.commit()
|
||||
db.refresh(tariff_fraction)
|
||||
return tariff_fraction
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error creating tariff fraction: {e}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Tariff fraction with this code already exists",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
tariff_fraction_id: int,
|
||||
tariff_fraction_data: TariffFractionUpdateDTO,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> Optional[TariffFraction]:
|
||||
"""Actualiza una fracción arancelaria existente"""
|
||||
|
||||
tariff_fraction = TariffFractionService.get_by_id(
|
||||
db, tariff_fraction_id, tenant_id, company_id
|
||||
)
|
||||
|
||||
if not tariff_fraction:
|
||||
return None
|
||||
|
||||
try:
|
||||
update_data = tariff_fraction_data.model_dump(exclude_unset=True)
|
||||
for key, value in update_data.items():
|
||||
setattr(tariff_fraction, key, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(tariff_fraction)
|
||||
return tariff_fraction
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error updating tariff fraction: {e}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Error updating tariff fraction",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session,
|
||||
tariff_fraction_id: int,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> bool:
|
||||
"""Elimina una fracción arancelaria"""
|
||||
|
||||
tariff_fraction = TariffFractionService.get_by_id(
|
||||
db, tariff_fraction_id, tenant_id, company_id
|
||||
)
|
||||
|
||||
if not tariff_fraction:
|
||||
return False
|
||||
|
||||
try:
|
||||
db.delete(tariff_fraction)
|
||||
db.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error deleting tariff fraction: {e}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Cannot delete tariff fraction - may be in use",
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
Datos semilla para Unidades de Medida Comerciales
|
||||
Migrado desde frontend para centralizar en backend
|
||||
"""
|
||||
|
||||
# Formato: (code, description, description_en, customs_code, american_code, ace_code, oma_code)
|
||||
seed = [
|
||||
('BARR', 'BARRIL', 'BARREL', '8', 'BBL', '', 'BLL'),
|
||||
('BD FT', 'PIE TABLA', 'BD FEET', '5', 'FT', '', 'BFT'),
|
||||
('BOLS', 'BOLSA', 'BAG', '6', 'PCS', '', 'BG'),
|
||||
('BTL', 'BOTELLA', 'BOTTLE', '21', 'PCS', '', 'BO'),
|
||||
('BULT', 'BULTO', 'BULK', '6', 'PCS', '', 'VQ'),
|
||||
('CAJA', 'CAJA', 'BOX', '20', '', '', 'BX'),
|
||||
('CARAT', 'CARAT', 'CARAT', '22', '', '', 'HE'),
|
||||
('CBZA', 'CABEZA', 'HEAD', '7', 'PCS', '', 'Z4'),
|
||||
('CIEN', 'CIENTO', 'CIEN', '18', '', '', 'CEN'),
|
||||
('CM', 'CENTIMETRO', 'CM', '3', 'CM', '', 'CMT'),
|
||||
('CM2', 'CENTIMETRO CUADRADO', 'CM2', '4', 'CM2', '', 'CMK'),
|
||||
('DEC', 'DECENA', '', '17', '', '', 'DC'),
|
||||
('DM', 'DECIMETRO', 'DM', '3', '', '', 'DMT'),
|
||||
('DM2', 'DECIMETRO CUADRADO', 'SQ DM', '4', '', '', 'DMK'),
|
||||
('DOCE', 'DOCENA', 'DOZ', '19', 'DOZ', 'DZ', 'DZN'),
|
||||
('FOZ', 'ONZA LIQUIDA', 'FOZ', '8', 'FOZ', '', 'OZA'),
|
||||
('FT', 'PIES', 'FT', '3', 'FT', '', 'LF'),
|
||||
('FT2', 'PIE CUADRADO', 'FT2', '4', 'SFT', '', 'FTK'),
|
||||
('GAL', 'GALON', 'GAL', '8', 'GAL', '', 'GLL'),
|
||||
('GR', 'GRAMO', 'GRAM', '2', '', '', 'GRM'),
|
||||
('IN', 'PULGADA', 'IN', '3', '', '', 'LI'),
|
||||
('IN2', 'PULGADA CUADRADA', 'IN2', '4', '', '', 'INK'),
|
||||
('JGO', 'JUEGO', 'SET', '12', '', '', 'SET'),
|
||||
('KGS', 'KILOGRAMOS', 'KGS', '1', 'KG2', '', 'KGM'),
|
||||
('LB', 'LIBRAS', 'LB', '1', '', '', 'LBR'),
|
||||
('LT', 'LITRO', 'LT', '8', 'L', '', 'LTR'),
|
||||
('M2', 'METRO CUADRADO', 'M2', '4', 'M2', '', 'MTK'),
|
||||
('M3', 'METRO CUBICO', 'M3', '5', 'M3', '', 'MTQ'),
|
||||
('MI', 'MILLA', 'MILE', '3', 'KM', '', 'SMI'),
|
||||
('MILLR', 'MILLAR', 'MILLR', '11', '', '', 'MIL'),
|
||||
('MT', 'METROS', 'MT', '3', 'M', '', 'MTR'),
|
||||
('OZ', 'ONZA', 'OZ', '8', 'FOZ', '', 'OZ'),
|
||||
('PAR', 'PAR', 'PAIR', '9', '', '', 'PB'),
|
||||
('PQ', 'PAQUETE', 'PACKAGE', '6', 'PCS', '', 'PK_1'),
|
||||
('PZA', 'PIEZA', 'PCS', '6', 'PCS', '', 'C62_1'),
|
||||
('QGL', 'CUARTO DE GALON', 'QGL', '8', '', '', 'QT'),
|
||||
('ROLL', 'ROLLO', 'ROLL', '6', '', '', 'RO'),
|
||||
('TON', 'TONELADA', 'TON', '14', 'TON', '', 'TNE_1'),
|
||||
('TOZ', 'ONZA TROY', 'TOZ', '1', 'TOZ', '', 'APZ'),
|
||||
('YD', 'YARDA', 'YD', '3', 'YD', '', 'YRD'),
|
||||
('YD2', 'YARDA CUADRADA', 'YD2', '4', 'SYD', '', 'YDK'),
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
US Tariff Fractions Catalog Module
|
||||
"""
|
||||
|
||||
from .models import USTariffFraction
|
||||
from .routes import router
|
||||
|
||||
__all__ = ["USTariffFraction", "router"]
|
||||
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
DTOs para fracciones arancelarias americanas
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
|
||||
|
||||
class USTariffFractionCreateDTO(BaseModel):
|
||||
"""DTO para crear fracción arancelaria americana"""
|
||||
|
||||
code: str = Field(..., max_length=16, description="Código de fracción americana")
|
||||
prefix: Optional[str] = Field(None, max_length=10, description="Prefijo")
|
||||
type_code: Optional[str] = Field(None, max_length=10, description="Código de tipo")
|
||||
ad_valorem: Optional[float] = Field(None, description="Porcentaje ad valorem")
|
||||
fixed_cost: Optional[float] = Field(None, description="Tasa fija")
|
||||
unit_of_measure: Optional[str] = Field(None, max_length=10, description="Unidad de medida")
|
||||
description: Optional[str] = Field(None, description="Descripción")
|
||||
|
||||
|
||||
class USTariffFractionUpdateDTO(BaseModel):
|
||||
"""DTO para actualizar fracción arancelaria americana"""
|
||||
|
||||
prefix: Optional[str] = Field(None, max_length=10)
|
||||
type_code: Optional[str] = Field(None, max_length=10)
|
||||
ad_valorem: Optional[float] = None
|
||||
fixed_cost: Optional[float] = None
|
||||
unit_of_measure: Optional[str] = Field(None, max_length=10)
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class USTariffFractionResponseDTO(BaseModel):
|
||||
"""DTO para respuesta de fracción arancelaria americana"""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
code: str
|
||||
prefix: Optional[str] = None
|
||||
type_code: Optional[str] = None
|
||||
ad_valorem: Optional[float] = None
|
||||
fixed_cost: Optional[float] = None
|
||||
unit_of_measure: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Modelos para fracciones arancelarias americanas
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import String, Numeric, TIMESTAMP, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from core.database import Base
|
||||
|
||||
|
||||
class USTariffFraction(Base):
|
||||
"""Modelo para fracciones arancelarias americanas (US HTS codes)"""
|
||||
|
||||
__tablename__ = "us_tariff_fractions"
|
||||
__table_args__ = {"schema": "a76"}
|
||||
|
||||
# Primary Key
|
||||
id: Mapped[int] = mapped_column(primary_key=True, index=True)
|
||||
|
||||
# Tenant/Company
|
||||
tenant_id: Mapped[int] = mapped_column(index=True, nullable=False)
|
||||
company_id: Mapped[int] = mapped_column(index=True, nullable=False)
|
||||
|
||||
# Datos principales
|
||||
code: Mapped[str] = mapped_column(
|
||||
String(16), nullable=False, comment="Código de fracción americana"
|
||||
)
|
||||
prefix: Mapped[Optional[str]] = mapped_column(
|
||||
String(10), comment="Prefijo de clasificación"
|
||||
)
|
||||
type_code: Mapped[Optional[str]] = mapped_column(
|
||||
String(10), comment="Código de tipo"
|
||||
)
|
||||
ad_valorem: Mapped[Optional[float]] = mapped_column(
|
||||
Numeric(10, 2), comment="Porcentaje ad valorem"
|
||||
)
|
||||
fixed_cost: Mapped[Optional[float]] = mapped_column(
|
||||
Numeric(15, 8), comment="Tasa fija"
|
||||
)
|
||||
unit_of_measure: Mapped[Optional[str]] = mapped_column(
|
||||
String(10), comment="Unidad de medida"
|
||||
)
|
||||
description: Mapped[Optional[str]] = mapped_column(
|
||||
String, comment="Descripción de la fracción"
|
||||
)
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
TIMESTAMP(timezone=True), server_default=func.now(), nullable=False
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
TIMESTAMP(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<USTariffFraction {self.code}>"
|
||||
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Endpoints API para fracciones arancelarias americanas
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
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 (
|
||||
USTariffFractionCreateDTO,
|
||||
USTariffFractionResponseDTO,
|
||||
USTariffFractionUpdateDTO,
|
||||
)
|
||||
from .service import USTariffFractionService
|
||||
|
||||
# Create base router with generic CRUD routes (disabled list because we'll create a custom one)
|
||||
base_router = TenantCRUDRoutes(
|
||||
service=USTariffFractionService,
|
||||
create_schema=USTariffFractionCreateDTO,
|
||||
update_schema=USTariffFractionUpdateDTO,
|
||||
response_schema=USTariffFractionResponseDTO,
|
||||
prefix="/us-tariff-fractions",
|
||||
tags=["a76 / general catalogs / us tariff fractions"],
|
||||
resource_name="USTariffFraction",
|
||||
id_name="us_tariff_fraction_id",
|
||||
enable_list=False, # Disable default list, we'll add custom one
|
||||
enable_filters=False,
|
||||
default_page_size=50,
|
||||
max_page_size=10000,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/us-tariff-fractions", tags=["a76 / general catalogs / us tariff fractions"])
|
||||
|
||||
# Custom list endpoint with search filter
|
||||
@router.get(
|
||||
"/",
|
||||
response_model=Dict[str, Any],
|
||||
summary="List US Tariff Fractions",
|
||||
description="Get paginated list of US Tariff Fractions with optional search filter",
|
||||
)
|
||||
async def list_us_tariff_fractions(
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
page: int = Query(1, ge=1, description="Page number"),
|
||||
page_size: int = Query(50, ge=1, le=10000, description="Page size"),
|
||||
search: Optional[str] = Query(None, description="Search in code, description, or prefix"),
|
||||
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)
|
||||
|
||||
skip = (page - 1) * page_size
|
||||
filters = {}
|
||||
if search:
|
||||
filters["search"] = search
|
||||
|
||||
items, total = USTariffFractionService.get_all(
|
||||
db, tenant_id, company_id, skip, page_size, filters
|
||||
)
|
||||
|
||||
return {
|
||||
"items": [USTariffFractionResponseDTO.model_validate(item) for item in items],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"pages": (total + page_size - 1) // page_size,
|
||||
}
|
||||
|
||||
# Include other CRUD routes from base router
|
||||
router.include_router(base_router.router)
|
||||
@@ -0,0 +1,417 @@
|
||||
"""
|
||||
Datos semilla para Fracciones Arancelarias de Estados Unidos
|
||||
|
||||
Catálogo de fracciones HTS/Schedule B para importaciones/exportaciones con EE.UU.
|
||||
Total: 406 registros
|
||||
|
||||
Estructura: (code, prefix, type_code, ad_valorem, fixed_cost, unit_of_measure, description)
|
||||
"""
|
||||
|
||||
seed = [
|
||||
('0902300090', '', 'PO', '0.00', '0.00000000', 'PZA', 'BOLSA DE TE')
|
||||
,('2508400150', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2520200000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2525200000', '', 'PO', '0.00', '0.00000000', 'KGS', 'PITMENT BASED ON TITANIUM DIOXIDE')
|
||||
,('2526200000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2707999090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2710121550', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2710129000', '', 'PO', '0.00', '0.00000000', 'LT', 'MOLD RELEASE')
|
||||
,('2710129050', '', 'PO', '0.00', '0.00000000', 'LT', '')
|
||||
,('2710190650', '', 'PO', '0.00', '0.00000000', 'LT', 'MOLD RELEASE')
|
||||
,('2710199000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2712902000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2839905000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2905120050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2905145050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2909430000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2909496000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2914115000', '', 'PO', '0.00', '0.00000000', 'LT', 'BUTYL ACETATE')
|
||||
,('2914120000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2915905050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2924293600', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('2929108090', '', 'PO', '0.00', '0.00000000', 'KGS', 'Urethane, Iso Side')
|
||||
,('3206110000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3206190000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3206495060', '', 'PO', '0.00', '0.00000000', 'KGS', 'RESIN')
|
||||
,('3206496050', '', 'PO', '0.00', '0.00000000', '', 'PIGMENTO COLORANTE')
|
||||
,('3208100000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3208200000', '', 'PO', '0.00', '0.00000000', 'LT', 'LACA ALQUIDICA NITROCELULOSA')
|
||||
,('3208900000', '', 'PO', '0.00', '0.00000000', '', 'PAINT')
|
||||
,('3209100000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('3209900000', 'S', 'PO', '0.00', '0.00000000', 'LT', 'LIQUID PAINT')
|
||||
,('3212900050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3214100020', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3215905000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3402205100', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3402905050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3403195000', '', 'PO', '0.00', '0.00000000', 'LT', 'Mold Release, Stoner, GL')
|
||||
,('3403990000', '', 'PO', '0.00', '0.00000000', '', 'PASTE FOR POLISHING METALS')
|
||||
,('3404905150', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3405400000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3405900000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3506105000', 'US', '', '0.00', '0.00000000', 'PZA', 'ADHESIVO EN AEROSOL')
|
||||
,('3506915000', '', 'PO', '0.00', '0.00000000', 'LT', 'Adhesive')
|
||||
,('3506990000', '', 'PO', '0.00', '0.00000000', 'LT', 'Adhesive')
|
||||
,('3802.20.00.00', '', 'PO', '0.00', '0.00000000', 'KGS', '')
|
||||
,('3811900000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3814005090', '', 'PO', '0.00', '0.00000000', 'LT', 'THINNER')
|
||||
,('3815901000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3815903000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3815905000', '', 'PO', '0.00', '0.00000000', 'PZA', 'CATALYST')
|
||||
,('3820000000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3824910000', '', 'PO', '0.00', '0.00000000', 'LT', 'LIQUIDO PARA PAVONAR METAL A BASE DE ACIDOS')
|
||||
,('3824991900', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3824999297', '', 'PO', '0.00', '0.00000000', 'PZA', 'WELDING ANTI SPLATTER')
|
||||
,('3825900000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3903190000', '', 'PO', '0.00', '0.00000000', 'KGS', '')
|
||||
,('3903905000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3905300000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3906905050', 'US', '', '0.00', '0.00000000', 'PZA', 'POLIMERO ACRILICO')
|
||||
,('3907200000', '', 'PO', '0.00', '0.00000000', 'KGS', 'POLYURETHANE BASED RESIN')
|
||||
,('3907210000', '', 'PO', '0.00', '0.00000000', 'PZA', 'POLYURETHANE-BASED RESIN')
|
||||
,('3907300000', 'US', '', '0.00', '0.00000000', 'KGS', 'RESINA EPOXICA A Y B')
|
||||
,('3907915000', '', 'PO', '0.00', '0.00000000', 'KGS', 'resin')
|
||||
,('3907995050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3909100000', '', 'PO', '0.00', '0.00000000', 'KGS', 'GRANULATED PLASTIC (PLASTIC RESIN)')
|
||||
,('3909310000', '', 'PO', '0.00', '0.00000000', 'KGS', 'ISOCIANATE')
|
||||
,('3909390000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3909505000', '', 'PO', '0.00', '0.00000000', 'LT', 'POLYURETHANE ELASTOMER PART A AND B')
|
||||
,('3909506000', '', 'PO', '0.00', '0.00000000', 'KGS', '')
|
||||
,('3909900000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3910000000', '', 'PO', '0.00', '0.00000000', 'LT', 'SEALER CONDITIONER PART B')
|
||||
,('3911902500', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3911909050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3915900090', '', 'PO', '0.00', '0.00000000', 'KGS', 'DESPERDICIO DE PLASTICO')
|
||||
,('3917230000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PLASTIC HOSE')
|
||||
,('3917320050', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('39173299', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('3917330000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('39173399', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('3917390050', '', 'PO', '0.00', '0.00000000', 'PZA', 'PLASTIC TUBE')
|
||||
,('3919101050', 'US', '', '0.00', '0.00000000', 'PZA', 'CINTA ADHESIVA')
|
||||
,('3919102055', 'US', '', '0.00', '0.00000000', 'PZA', 'CINTA ADHESIVA (PLASTICA)')
|
||||
,('3919905060', '', '', '0.00', '0.00000000', 'PZA', 'CINTA ADHESIVA (PLASTICO TRANSPARENTE)')
|
||||
,('3920100000', '', 'PO', '0.00', '0.00000000', 'PZA', 'burbuja')
|
||||
,('3920200055', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3920515000', '', 'PO', '0.00', '0.00000000', 'PZA', 'ACRYLIC POLYMER SHEET')
|
||||
,('3921135000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3921905050', 'S', '', '0.00', '0.00000000', 'PZA', 'PELICULA DE PLASTICO PARA FLEJAR')
|
||||
,('3923109000', '', 'PO', '0.00', '0.00000000', 'PZA', 'CAJA DE PLASTICO')
|
||||
,('3923210095', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3923290000', '', 'PO', '0.00', '0.00000000', 'PZA', 'Bag')
|
||||
,('3923300090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3923500000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3923900080', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'Plastic container')
|
||||
,('3926209050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3926400090', 'S', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('3926902100', 'US', '', '0.00', '0.00000000', 'PZA', 'PROTECTORES DE PLASTICO P/OIDO')
|
||||
,('3926903500', 'S', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('3926909985', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'PLASTIC')
|
||||
,('3926909987', 'S', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('3926909989', 'S', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('3926909990', '', 'PO', '0.00', '0.00000000', 'PZA', 'PLACTIC BASE')
|
||||
,('3926909995', 'S', 'PO', '0.00', '0.00000000', '', 'PLASTIC DOME')
|
||||
,('3926909996', '', 'PO', '0.00', '0.00000000', 'PZA', 'Foam Plug, Hosiery Leg')
|
||||
,('4015190002', 'US', '', '0.00', '0.00000000', 'PZA', 'GUANTES DE CAUCHO')
|
||||
,('4016930000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('4016935050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4016992000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('4016993510', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('4016996000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('4016996050', '', 'PO', '0.00', '0.00000000', 'PZA', 'BALL')
|
||||
,('4201003000', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'Dog leashes, collars, muzzles, harnesses and similar dog equipment')
|
||||
,('4201006000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4410190060', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4415109000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4415208000', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'WOOD PALLETS')
|
||||
,('4417004000', '', 'PO', '0.00', '0.00000000', '', 'Paint brush and paint roller handles')
|
||||
,('4421904000', '', 'PO', '5.10', '0.00000000', '', 'NECKCAP WOOD')
|
||||
,('4421909750', 'US', '', '0.00', '0.00000000', 'PZA', 'PALILLO DE MADERA')
|
||||
,('4421914000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4421994000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4421999880', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4503106000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4802693000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4804394000', '', 'PO', '0.00', '0.00000000', '', 'Wrapping paper')
|
||||
,('4804590000', 'S', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4805400000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4808100000', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'CORRUGATED CARTON SEPARATOR')
|
||||
,('4808906000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4810137040', '', 'PO', '0.00', '0.00000000', 'PZA', 'ROLLO DE CABLE DE ACER')
|
||||
,('4811412100', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4817100000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4818200020', 'US', '', '0.00', '0.00000000', 'PZA', 'TOALLAS DESECHABLES DE PAPEL TISSUE')
|
||||
,('4819100040', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'BOX')
|
||||
,('4819504060', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4821104000', '', 'PO', '0.00', '0.00000000', 'PZA', 'LABEL')
|
||||
,('4821904000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4822900000', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'CARTON BOX')
|
||||
,('4823700040', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4823901000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PAPER CUP')
|
||||
,('4823908600', '', 'PO', '0.00', '0.00000000', 'PZA', 'BASES DE CARTON PRENSADO')
|
||||
,('4823908850', 'US', '', '0.00', '0.00000000', 'PZA', 'CINTA ADHESIVA DE PAPEL')
|
||||
,('4901990091', '', 'PO', '0.00', '0.00000000', 'PZA', 'MANUALES')
|
||||
,('4911100080', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('4911998000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('5407619975', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('5508200000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('5703308085', '', 'PO', '0.00', '0.00000000', 'PZA', 'PASTO SINTETICO (MUESTRAS)')
|
||||
,('5806310000', '', 'PO', '0.00', '0.00000000', 'MT', 'CINTA TEXTIL')
|
||||
,('5806322000', '', 'PO', '0.00', '0.00000000', 'MT', 'VELCRO')
|
||||
,('59039001', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('5903903090', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('5911102000', '', 'PO', '0.00', '0.00000000', 'MT', '')
|
||||
,('5911900040', '', 'PO', '0.00', '0.00000000', 'PZA', 'Cords, braids and the like of a kind used in industry as packing or lubricating material')
|
||||
,('5911900080', '', 'PO', '0.00', '0.00000000', 'MT', 'COVER FORM 3/4 FEMALE FOAM LINEN')
|
||||
,('6116100000', 'US', '', '0.00', '0.00000000', 'PZA', 'GUANTES DE ALGOHODON CON RECUBRIMIENTO DE NITRILIO')
|
||||
,('6116920000', 'US', '', '0.00', '0.00000000', 'PZA', 'GUANTES DE ALGOHODON')
|
||||
,('6116929400', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('6306192120', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('6307100000', 'US', '', '0.00', '0.00000000', 'PZA', 'FIBRA SINTETICA P/LIMPIAR')
|
||||
,('6307909089', 'S', 'PO', '0.00', '0.00000000', '', 'BAG')
|
||||
,('6307909889', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('6307909891', '', 'PO', '0.00', '0.00000000', 'PZA', 'bolsa de tela')
|
||||
,('6307909995', 'US', '', '0.00', '0.00000000', 'PZA', 'MASCARILLA DE PROTECCION CONTRA EL POLVO')
|
||||
,('6403919015', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('6403999031', '', 'PO', '10.00', '0.00000000', 'PAR', 'LEATHER SHOES')
|
||||
,('6406109090', '', 'PO', '0.00', '0.00000000', 'PZA', 'HUARACHE')
|
||||
,('6506106075', '', 'PO', '0.00', '0.00000000', 'PZA', 'PROTECTION HELMET')
|
||||
,('6804220000', 'US', '', '0.00', '0.00000000', 'PZA', 'DISCO ABRASIVO CIRCULARES')
|
||||
,('6805100000', 'US', '', '0.00', '0.00000000', 'PZA', 'DISCO ABRASIVO EN ROLLO')
|
||||
,('6805100199', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('6805300000', 'US', '', '0.00', '0.00000000', 'PZA', 'LIJA CON SOPORTE DE PLASTICO CELULAR')
|
||||
,('6805305000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7007190000', '', 'PO', '0.00', '0.00000000', 'PZA', 'TEMPERED GLASS BASE')
|
||||
,('7009921000', '', 'PO', '0.00', '0.00000000', 'PZA', 'ESPEJO ENMARCADO')
|
||||
,('7010905055', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7019905050', '', 'PO', '0.00', '0.00000000', '', 'FIBRA DE VIDRIO')
|
||||
,('7019905150', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7020006000', '', 'PO', '0.00', '0.00000000', 'PZA', 'BASE DE VIDRIO')
|
||||
,('7020009990', '', 'PO', '0.00', '0.00000000', '', 'GLASS BOARD')
|
||||
,('7206900000', '', 'PO', '0.00', '0.00000000', 'PZA', 'TUBO')
|
||||
,('7215100080', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7215905000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7226928050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7228400000', '', 'PO', '0.00', '0.00000000', 'PZA', 'METAL BAR')
|
||||
,('7228608000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7304598080', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7306200000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7306305090', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7306905000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7307225000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7307929000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7308909590', '', 'PO', '0.00', '0.00000000', 'PZA', 'RACK')
|
||||
,('7309000090', '', 'PO', '0.00', '0.00000000', 'PZA', 'TANQUE HERMETICO DE ACERO')
|
||||
,('7310100050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7310290050', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7312109090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7315827000', '', 'PO', '0.00', '0.00000000', '', 'CHAIN')
|
||||
,('7317007500', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7318130060', '', 'PO', '0.00', '0.00000000', '', 'EYE HOOK')
|
||||
,('73181504', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7318150400', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7318152000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7318158066', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7318158085', '', '', '0.00', '0.00000000', 'PZA', 'TORNILLO DE ACERO CON TUERCA')
|
||||
,('7318158688', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7318159000', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'TORNILLO DE ACERO MARIPOSA')
|
||||
,('7318160085', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7318190000', '', 'PO', '0.00', '0.00000000', 'PZA', 'REMACHE')
|
||||
,('7318210090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7318220000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7318230000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7318240000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7318290000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7319909000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7320205060', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7321811000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7325995000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7326901000', '', 'PO', '0.00', '0.00000000', '', 'HOOK')
|
||||
,('7326908605', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7326908688', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'METAL INSERTS')
|
||||
,('7326908695', '', 'PO', '0.00', '0.00000000', 'PZA', 'STEEL FASTENER')
|
||||
,('73269099', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7412200090', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('74122001', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7415100000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7415390000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7419995050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7607196000', '', 'PO', '0.00', '0.00000000', 'PZA', 'aluminium')
|
||||
,('7609000000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7616109090', '', 'PO', '0.00', '0.00000000', 'PZA', 'tachuela')
|
||||
,('7616995090', 'S', 'PO', '0.00', '0.00000000', '', 'SOPORTE DE ALUMINIO')
|
||||
,('7616995190', 'S', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('7618000000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7806008000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('7907006000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8021230000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8202200060', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8203109000', '', 'PO', '0.00', '0.00000000', 'PZA', 'ESTUCHE DE LIMAS')
|
||||
,('8203208000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('82032099', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8204110060', '', 'PO', '0.00', '0.00000000', '', 'WRENCH')
|
||||
,('8205511500', '', 'PO', '0.00', '0.00000000', '', 'CEPILLO DE ALAMBRE')
|
||||
,('8205599000', 'US', '', '0.00', '0.00000000', 'PZA', 'DESPACHADOR DE CINTA MANUAL')
|
||||
,('8205700060', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8205700090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8205906000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8207502055', '', 'PO', '0.00', '0.00000000', '', 'DRILL BIT')
|
||||
,('8207907585', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8208906000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8302200000', '', 'PO', '0.00', '0.00000000', '', 'RUEDAS PARA BASE DE MANIQUI')
|
||||
,('8302426000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8302498090', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'Kit, Knob,Hand')
|
||||
,('8302500000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8309900000', '', 'PO', '0.00', '0.00000000', 'PZA', 'TAPA METALICA')
|
||||
,('8309900090', 'S', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8310000000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8413600090', '', 'PO', '0.00', '0.00000000', 'PZA', 'PUMPS OF MACHINE MOLDING')
|
||||
,('8413919060', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8413919080', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8413919096', '', 'PO', '0.00', '0.00000000', 'PZA', 'DIAPHRAGM FOR PUMP')
|
||||
,('8414.59.6595', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8414100000', '', 'PO', '0.00', '0.00000000', 'PZA', 'VACUM PUMP')
|
||||
,('8414510090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8414519090', '', 'PO', '0.00', '0.00000000', 'PZA', 'FAN')
|
||||
,('8414596595', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8414809000', '', 'PO', '0.00', '0.00000000', 'PZA', 'DUST COLLECTOR')
|
||||
,('8414909080', '', 'PO', '0.00', '0.00000000', 'PZA', 'DISPOSITIVO DE FILTRACCION PARA BOMBA NEUMATICA')
|
||||
,('8419390180', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8421210000', '', 'PO', '0.00', '0.00000000', 'PZA', 'FEEDER-DISASSEMBLY DISASSEMBLED')
|
||||
,('8421230000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8421290065', '', 'PO', '0.00', '0.00000000', 'PZA', 'FILTER')
|
||||
,('8421390115', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8421390190', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8421398015', '', 'PO', '0.00', '0.00000000', 'PZA', 'FILTER')
|
||||
,('8421398040', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8421398090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8421990180', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8422309191', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8422401190', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8422409180', '', 'PO', '0.00', '0.00000000', 'PZA', 'STRAPPING MACHINE')
|
||||
,('8424209000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PISTOLA AEROGRAFICA')
|
||||
,('8424890000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8424900100', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8424900500', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8424902000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8424909080', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8425110000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8427108095', '', 'PO', '0.00', '0.00000000', 'PZA', 'Pallet jack')
|
||||
,('8427900000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PALLET JACK')
|
||||
,('8456111050', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8459290090', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8461500090', '', 'PO', '0.00', '0.00000000', 'PZA', 'BELT SAW')
|
||||
,('8461508090', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8465910091', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8465930012', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8466306085', '', 'PO', '0.00', '0.00000000', 'PZA', 'CILINDRO NEUMATICO')
|
||||
,('8466925090', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8467.19.5090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8467111080', 'US', '', '0.00', '0.00000000', 'PZA', 'PULIDOR NEUMATICO MANUAL')
|
||||
,('8467195090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8467210070', '', 'PO', '0.00', '0.00000000', '', 'ROTARY')
|
||||
,('8467220090', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8467290090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8467895090', '', 'PO', '0.00', '0.00000000', 'PZA', 'Pneumatic screwdriver')
|
||||
,('8467920050', '', 'PO', '0.00', '0.00000000', '', 'pulidor')
|
||||
,('8467920090', 'US', '', '0.00', '0.00000000', 'PZA', 'DISCO DE URETANO P/ PULIDOR NEUMATICO')
|
||||
,('8471410150', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8471500150', '', 'PO', '0.00', '0.00000000', '', 'CPU')
|
||||
,('8471608000', '', 'PO', '0.00', '0.00000000', '', 'SCANNER')
|
||||
,('8471609050', '', 'PO', '0.00', '0.00000000', 'PZA', 'SCANNER')
|
||||
,('8477590100', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8477800000', '', 'PO', '0.00', '0.00000000', 'PZA', 'MACHINE FOR FILLING AIR BAGS')
|
||||
,('8477800100', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8477900000', '', 'PO', '0.00', '0.00000000', 'PZA', 'Presses setup the mold during blowing process')
|
||||
,('8477902580', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8477908595', '', 'PO', '0.00', '0.00000000', 'PZA', 'DISC')
|
||||
,('8477908695', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8479820040', '', 'PO', '0.00', '0.00000000', '', 'ROTARY MACHINE')
|
||||
,('8479820080', '', 'PO', '0.00', '0.00000000', 'PZA', 'APARATO AGITADOR')
|
||||
,('8479830100', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8479899199', '', 'PO', '0.00', '0.00000000', 'PZA', 'PORTABLE DOCK PLATE')
|
||||
,('8479899499', '', 'PO', '0.00', '0.00000000', 'PZA', 'MAQUINA APLICADORA')
|
||||
,('8479899599', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8479899797', '', 'PO', '0.00', '0.00000000', 'PZA', 'COLECTOR DE POLVO')
|
||||
,('8479909496', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('84799099', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8480718045', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'MOLDE')
|
||||
,('8480718060', '', 'PO', '0.00', '0.00000000', 'PZA', 'TIG TORCH')
|
||||
,('8480719090', '', 'PO', '0.00', '0.00000000', 'PZA', 'Semi-finished mold')
|
||||
,('8481100090', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8481200080', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('84812099', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8481400000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8481809020', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8481809050', '', 'PO', '0.00', '0.00000000', 'PZA', 'NOZZLE, CONE DUT')
|
||||
,('8481909085', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8483308090', '', 'PO', '0.00', '0.00000000', 'PZA', 'BUJES')
|
||||
,('8484200000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8484900000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8501106080', '', 'PO', '0.00', '0.00000000', 'PZA', 'MOTOR ELECTRICO INCLUYE ACCESORIOS')
|
||||
,('8504404000', '', 'PO', '0.00', '0.00000000', 'PZA', 'CONTROLADOR DE VELOCIDAD')
|
||||
,('8504409580', '', 'PO', '0.00', '0.00000000', 'PZA', 'BATTERY CHARGER')
|
||||
,('8505110090', '', 'PO', '0.00', '0.00000000', 'PZA', 'INSERT')
|
||||
,('8505200000', '', 'PO', '0.00', '0.00000000', 'PZA', 'BARRIER')
|
||||
,('8507208091', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8512902000', 'S', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8514908000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8515390040', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8515800080', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('85160808000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8516808000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8517620010', '', 'PO', '0.00', '0.00000000', '', 'WIRELESS ACCESS POINT')
|
||||
,('8523520010', '', 'PO', '0.00', '0.00000000', 'PZA', 'RF Label')
|
||||
,('8525805050', '', 'PO', '0.00', '0.00000000', 'PZA', 'CAMARA DE CIRCUITO CERRADO')
|
||||
,('8533408070', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8536100040', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8536490050', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8536507000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8536908585', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8537103000', 'S', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8537109070', '', 'PO', '0.00', '0.00000000', '', 'DIGITAL PANEL CONTROL')
|
||||
,('8543908885', 'S', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8544190000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8544429090', '', 'PO', '0.00', '0.00000000', 'PZA', 'SENSOR CABLE')
|
||||
,('8547200000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('8713100000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('8716805090', '', 'PO', '0.00', '0.00000000', 'PZA', 'UTILITY CART')
|
||||
,('9018390050', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('9023000000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('9024800000', '', 'PO', '0.00', '0.00000000', '', 'TESTING MACHINE')
|
||||
,('9026102010', '', 'PO', '0.00', '0.00000000', '', 'DIGITAL VACUUM GAUGE')
|
||||
,('9026106000', '', 'PO', '0.00', '0.00000000', 'PZA', 'FLOAT & GAUGE')
|
||||
,('9026204000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('9026208000', '', 'PO', '0.00', '0.00000000', 'PZA', 'REGULATOR')
|
||||
,('9027304040', '', 'PO', '0.00', '0.00000000', 'PZA', 'SPECTROPHOTOMETER')
|
||||
,('9031.80.8085', '', 'PO', '1.70', '0.00000000', '', 'FIXTURA')
|
||||
,('9031808085', '', 'PO', '1.70', '2.25000000', 'PZA', 'APARATO')
|
||||
,('9031907000', '', 'PO', '0.00', '0.00000000', 'PZA', 'FIXTURE')
|
||||
,('9032200000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PRESOSTATO')
|
||||
,('9106100000', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('9403100040', '', 'PO', '0.00', '0.00000000', 'PZA', 'MESA DE METAL CON BASE DE MADERA')
|
||||
,('9403200030', '', 'PO', '0.00', '0.00000000', 'PZA', 'ESTANTE DE ACERO PARA TOTE')
|
||||
,('9506310000', '', 'PO', '0.00', '0.00000000', '', 'GOLF CLUB')
|
||||
,('9506320000', '', 'PO', '0.00', '0.00000000', 'PZA', 'BALL')
|
||||
,('9506620000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PELOTA')
|
||||
,('9506628060', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('9506996080', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('9603404050', 'US', '', '0.00', '0.00000000', 'PZA', 'BROCHAS CON MANGO DE PLASTICO Y CERDAS DE FIBRA SINTETICA')
|
||||
,('9604000000', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('9607190060', '', 'PO', '0.00', '0.00000000', '', '')
|
||||
,('9612101020', '', 'PO', '0.00', '0.00000000', 'PZA', 'RIBBON')
|
||||
,('9612109090', '', 'PO', '0.00', '0.00000000', 'PZA', 'RIBBON')
|
||||
,('9618000000', 'S', 'PO', '0.00', '79.00000000', 'PZA', 'MANIQUIES')
|
||||
,('9618009900', '', 'PO', '0.00', '0.00000000', 'PZA', '')
|
||||
,('9801001095', '', 'PO', '0.00', '4.35000000', '', 'RESINA')
|
||||
,('9801001097', 'S', 'PO', '0.00', '4.35000000', 'PZA', '')
|
||||
,('9801001098', '', 'PO', '0.00', '0.00000000', 'PZA', 'US RETURNS')
|
||||
,('9801002500', '', 'PO', '0.00', '0.00000000', 'PZA', 'CHINA ARTICLES')
|
||||
,('9802004040', 'US', 'PO', '0.00', '0.00000000', 'PZA', 'MANIQUIS REPARADOS')
|
||||
,('9802005060', '', 'PO', '0.00', '0.00000000', '', 'Repaired in Mexico')
|
||||
,('9861661098', 'M6363', 'PO', '0.00', '8.10000000', 'PZA', '')
|
||||
,('MX4415109000', '', 'PO', '0.00', '0.00000000', 'PZA', 'CAJON DE MADERA')
|
||||
,('MX4820400000', '', 'PO', '0.00', '0.50000000', '', 'FORMULARIOS DE PAPEL')
|
||||
,('MX7326909980', '', 'PO', '0.00', '0.00000000', '', 'STEEL PANEL')
|
||||
,('MX8309900000', '', 'PO', '0.00', '0.00000000', 'PZA', 'CAP')
|
||||
]
|
||||
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
Service para fracciones arancelarias americanas
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException
|
||||
import logging
|
||||
|
||||
from .models import USTariffFraction
|
||||
from .dto import USTariffFractionCreateDTO, USTariffFractionUpdateDTO
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class USTariffFractionService:
|
||||
"""Service para gestionar fracciones arancelarias americanas"""
|
||||
|
||||
@staticmethod
|
||||
def get_all(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Tuple[List[USTariffFraction], int]:
|
||||
"""Obtiene todas las fracciones arancelarias americanas con filtros opcionales"""
|
||||
|
||||
query = db.query(USTariffFraction).filter(
|
||||
USTariffFraction.tenant_id == tenant_id,
|
||||
USTariffFraction.company_id == company_id,
|
||||
)
|
||||
|
||||
# Aplicar filtros
|
||||
if filters:
|
||||
# Si hay un filtro 'search', buscar en múltiples campos
|
||||
if filters.get("search"):
|
||||
search_term = f"%{filters['search']}%"
|
||||
query = query.filter(
|
||||
USTariffFraction.code.ilike(search_term) |
|
||||
USTariffFraction.description.ilike(search_term) |
|
||||
USTariffFraction.prefix.ilike(search_term)
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
items = query.order_by(USTariffFraction.code).offset(skip).limit(limit).all()
|
||||
|
||||
return items, total
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(
|
||||
db: Session, tenant_id: int, company_id: int, fraction_id: int
|
||||
) -> Optional[USTariffFraction]:
|
||||
"""Obtiene una fracción arancelaria americana por ID"""
|
||||
return (
|
||||
db.query(USTariffFraction)
|
||||
.filter(
|
||||
USTariffFraction.id == fraction_id,
|
||||
USTariffFraction.tenant_id == tenant_id,
|
||||
USTariffFraction.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
fraction_data: USTariffFractionCreateDTO,
|
||||
) -> USTariffFraction:
|
||||
"""Crea una nueva fracción arancelaria americana"""
|
||||
try:
|
||||
db_fraction = USTariffFraction(
|
||||
tenant_id=tenant_id,
|
||||
company_id=company_id,
|
||||
**fraction_data.model_dump(),
|
||||
)
|
||||
db.add(db_fraction)
|
||||
db.commit()
|
||||
db.refresh(db_fraction)
|
||||
return db_fraction
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error creando fracción americana: {e}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Ya existe una fracción americana con este código",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update(
|
||||
db: Session,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
fraction_id: int,
|
||||
fraction_data: USTariffFractionUpdateDTO,
|
||||
) -> Optional[USTariffFraction]:
|
||||
"""Actualiza una fracción arancelaria americana existente"""
|
||||
db_fraction = USTariffFractionService.get_by_id(
|
||||
db, tenant_id, company_id, fraction_id
|
||||
)
|
||||
if not db_fraction:
|
||||
return None
|
||||
|
||||
update_data = fraction_data.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_fraction, field, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_fraction)
|
||||
return db_fraction
|
||||
|
||||
@staticmethod
|
||||
def delete(
|
||||
db: Session, tenant_id: int, company_id: int, fraction_id: int
|
||||
) -> bool:
|
||||
"""Elimina una fracción arancelaria americana"""
|
||||
db_fraction = USTariffFractionService.get_by_id(
|
||||
db, tenant_id, company_id, fraction_id
|
||||
)
|
||||
if not db_fraction:
|
||||
return False
|
||||
|
||||
db.delete(db_fraction)
|
||||
db.commit()
|
||||
return True
|
||||
@@ -19,6 +19,10 @@ from .general_catalogs.identifiers.routes import router as identifiers_router
|
||||
from .fraction_rule_octave.routes import router as fraction_rule_octave_router
|
||||
from .general_catalogs.packages.routes import router as package_router
|
||||
from .general_catalogs.ports.routes import router as ports_router
|
||||
from .general_catalogs.tariff_fractions.routes import router as tariff_fractions_router
|
||||
from .general_catalogs.us_tariff_fractions.routes import router as us_tariff_fractions_router
|
||||
from .general_catalogs.depreciation_catalog.routes import router as depreciation_catalog_router
|
||||
from .general_catalogs.fda_catalog.routes import router as fda_catalog_router
|
||||
from .parts import router as parts_router
|
||||
from .pedmientos.router import router as pedimentos_router
|
||||
from .permission_rule_oct.routes import router as permission_rule_oct_router
|
||||
@@ -58,6 +62,10 @@ router.include_router(
|
||||
)
|
||||
router.include_router(package_router, prefix="/a76")
|
||||
router.include_router(ports_router, prefix="/a76")
|
||||
router.include_router(tariff_fractions_router, prefix="/a76")
|
||||
router.include_router(us_tariff_fractions_router, prefix="/a76")
|
||||
router.include_router(depreciation_catalog_router, prefix="/a76")
|
||||
router.include_router(fda_catalog_router, prefix="/a76")
|
||||
router.include_router(seal_router, prefix="/a76", tags=["a76 / seal"])
|
||||
router.include_router(units_of_measure_router, prefix="/a76")
|
||||
router.include_router(
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
// Interfaces
|
||||
export interface DepreciationCatalog {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
fraction: string;
|
||||
description: string;
|
||||
depreciation_rate: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface DepreciationCatalogListResponse {
|
||||
items: DepreciationCatalog[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
// API Functions
|
||||
export async function getDepreciationCatalog(
|
||||
page = 1,
|
||||
pageSize = 100,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<DepreciationCatalogListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
return api.get<DepreciationCatalogListResponse>(
|
||||
`/v1/a76/depreciation-catalog/?${params.toString()}`
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface FDACatalog {
|
||||
id: number;
|
||||
fda_key: string;
|
||||
description: string;
|
||||
fda_code?: string;
|
||||
requirements?: string;
|
||||
manufacturer_number?: string;
|
||||
country_of_production?: string;
|
||||
storage_status?: string;
|
||||
warehouse_code?: string;
|
||||
call_atl?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export async function getFDACatalog(
|
||||
page: number,
|
||||
pageSize: number,
|
||||
companyId: number,
|
||||
filters: any = {}
|
||||
): Promise<any> {
|
||||
try {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString()
|
||||
});
|
||||
|
||||
if (filters.search) {
|
||||
queryParams.append('search', filters.search);
|
||||
}
|
||||
|
||||
const response = await api.get(`/v1/a76/fda-catalog/?${queryParams.toString()}`);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error fetching FDA catalog:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
// Interfaces
|
||||
export interface TariffFraction {
|
||||
id: number;
|
||||
code: string;
|
||||
fraction: string;
|
||||
description: string | null;
|
||||
nico: string | null;
|
||||
umt: string | null;
|
||||
adv_impo: string | null;
|
||||
adv_expo: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface TariffFractionCreate {
|
||||
code: string;
|
||||
fraction: string;
|
||||
description?: string | null;
|
||||
nico?: string | null;
|
||||
umt?: string | null;
|
||||
adv_impo?: string | null;
|
||||
adv_expo?: string | null;
|
||||
}
|
||||
|
||||
export interface TariffFractionUpdate {
|
||||
fraction?: string;
|
||||
description?: string | null;
|
||||
nico?: string | null;
|
||||
umt?: string | null;
|
||||
adv_impo?: string | null;
|
||||
adv_expo?: string | null;
|
||||
}
|
||||
|
||||
export interface TariffFractionListResponse {
|
||||
items: TariffFraction[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
// API Functions
|
||||
export async function getTariffFractions(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<TariffFractionListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/v1/a76/tariff-fractions/?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function getTariffFractionById(
|
||||
id: number,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<TariffFraction>> {
|
||||
return await api.get(`/v1/a76/tariff-fractions/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export async function createTariffFraction(
|
||||
data: TariffFractionCreate,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<TariffFraction>> {
|
||||
return await api.post(`/v1/a76/tariff-fractions/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function updateTariffFraction(
|
||||
id: number,
|
||||
data: TariffFractionUpdate,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<TariffFraction>> {
|
||||
return await api.put(`/v1/a76/tariff-fractions/${id}/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deleteTariffFraction(
|
||||
id: number,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/tariff-fractions/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
// Interfaces
|
||||
export interface USTariffFraction {
|
||||
id: number;
|
||||
code: string;
|
||||
prefix: string | null;
|
||||
type_code: string | null;
|
||||
ad_valorem: number | null;
|
||||
fixed_cost: number | null;
|
||||
unit_of_measure: string | null;
|
||||
description: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface USTariffFractionCreate {
|
||||
code: string;
|
||||
prefix?: string | null;
|
||||
type_code?: string | null;
|
||||
ad_valorem?: number | null;
|
||||
fixed_cost?: number | null;
|
||||
unit_of_measure?: string | null;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface USTariffFractionUpdate {
|
||||
prefix?: string | null;
|
||||
type_code?: string | null;
|
||||
ad_valorem?: number | null;
|
||||
fixed_cost?: number | null;
|
||||
unit_of_measure?: string | null;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface USTariffFractionListResponse {
|
||||
items: USTariffFraction[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
// API Functions
|
||||
export async function getUSTariffFractions(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<USTariffFractionListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/v1/a76/us-tariff-fractions/?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function getUSTariffFractionById(
|
||||
id: number,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<USTariffFraction>> {
|
||||
return await api.get(`/v1/a76/us-tariff-fractions/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export async function createUSTariffFraction(
|
||||
data: USTariffFractionCreate,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<USTariffFraction>> {
|
||||
return await api.post(`/v1/a76/us-tariff-fractions/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function updateUSTariffFraction(
|
||||
id: number,
|
||||
data: USTariffFractionUpdate,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<USTariffFraction>> {
|
||||
return await api.put(`/v1/a76/us-tariff-fractions/${id}/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deleteUSTariffFraction(
|
||||
id: number,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/us-tariff-fractions/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -58,7 +58,7 @@ export function getSidebarData(): SidebarData {
|
||||
user: {
|
||||
name: "", // Se llena dinámicamente desde Keycloak
|
||||
email: "", // Se llena dinámicamente desde Keycloak
|
||||
// avatar: "/avatars/default.jpg", // Avatar por defecto
|
||||
avatar: "", // Se llena dinámicamente desde Keycloak
|
||||
},
|
||||
teams: [
|
||||
{
|
||||
@@ -366,6 +366,17 @@ export function getSidebarData(): SidebarData {
|
||||
icon: BadgeCheck,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: "Mercancías",
|
||||
url: "#",
|
||||
icon: Package,
|
||||
items: [
|
||||
{
|
||||
title: "Clase de Activo Fijo",
|
||||
url: "/dashboard/merchandise/fixed_asset_classes",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: m["sidebar.reference_data.configuracion"](),
|
||||
url: "#",
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { Search, Loader2, Download } from 'lucide-svelte';
|
||||
import { getTariffFractions, type TariffFraction } from '$lib/api/dashboard/a76/general_catalogs/tariff-fractions';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let tariffFractions = $state<TariffFraction[]>([]);
|
||||
let filteredFractions = $state<TariffFraction[]>([]);
|
||||
let searchQuery = $state('');
|
||||
let isLoading = $state(false);
|
||||
let currentPage = $state(1);
|
||||
let totalPages = $state(1);
|
||||
let totalRecords = $state(0);
|
||||
const pageSize = 50;
|
||||
|
||||
onMount(() => {
|
||||
loadTariffFractions();
|
||||
});
|
||||
|
||||
// Filtrar fracciones cuando cambia la búsqueda
|
||||
$effect(() => {
|
||||
if (searchQuery.trim() === '') {
|
||||
filteredFractions = tariffFractions;
|
||||
} else {
|
||||
const query = searchQuery.toLowerCase();
|
||||
filteredFractions = tariffFractions.filter(
|
||||
(fraction) =>
|
||||
fraction.code.toLowerCase().includes(query) ||
|
||||
fraction.fraction.toLowerCase().includes(query) ||
|
||||
(fraction.description ?? '').toLowerCase().includes(query) ||
|
||||
(fraction.nico ?? '').toLowerCase().includes(query) ||
|
||||
(fraction.umt ?? '').toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
async function loadTariffFractions(page: number = 1) {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
console.error('No hay compañía activa');
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
try {
|
||||
const response = await getTariffFractions(page, pageSize, companyId);
|
||||
if (response.data) {
|
||||
tariffFractions = response.data.items;
|
||||
filteredFractions = response.data.items;
|
||||
totalPages = response.data.pages;
|
||||
totalRecords = response.data.total;
|
||||
currentPage = response.data.page;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error cargando fracciones arancelarias:', error);
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function goToPage(page: number) {
|
||||
if (page >= 1 && page <= totalPages && page !== currentPage) {
|
||||
await loadTariffFractions(page);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-6">
|
||||
<Card.Root>
|
||||
<Card.Header class="text-center border-b">
|
||||
<Card.Title class="text-2xl font-bold uppercase">
|
||||
Catálogo de Fracciones SITAR - SCAII
|
||||
</Card.Title>
|
||||
<p class="text-muted-foreground mt-2">
|
||||
Nomenclatura arancelaria mexicana completa
|
||||
</p>
|
||||
</Card.Header>
|
||||
|
||||
<Card.Content class="pt-6">
|
||||
<div class="space-y-6">
|
||||
<!-- Barra de búsqueda y acciones -->
|
||||
<div class="flex gap-4 items-center">
|
||||
<div class="flex-1 relative">
|
||||
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
bind:value={searchQuery}
|
||||
placeholder="Buscar por código, fracción, descripción, NICO o UMT..."
|
||||
class="pl-9"
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="icon" title="Exportar a CSV">
|
||||
<Download class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Información de registros -->
|
||||
<div class="flex items-center justify-between text-sm text-muted-foreground">
|
||||
<div>
|
||||
{#if isLoading}
|
||||
<div class="flex items-center gap-2">
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
<span>Cargando...</span>
|
||||
</div>
|
||||
{:else}
|
||||
Mostrando {filteredFractions.length} de {totalRecords} fracciones arancelarias
|
||||
{#if searchQuery}
|
||||
(filtrado)
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{#if !searchQuery && totalPages > 1}
|
||||
<div class="flex items-center gap-2">
|
||||
<span>Página {currentPage} de {totalPages}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Tabla de fracciones -->
|
||||
<div class="border rounded-md overflow-auto max-h-[600px]">
|
||||
<Table.Root>
|
||||
<Table.Header class="sticky top-0 bg-background z-10">
|
||||
<Table.Row>
|
||||
<Table.Head class="w-[100px]">Código</Table.Head>
|
||||
<Table.Head class="w-[120px]">Fracción</Table.Head>
|
||||
<Table.Head class="min-w-[350px]">Descripción</Table.Head>
|
||||
<Table.Head class="w-[80px]">NICO</Table.Head>
|
||||
<Table.Head class="w-[80px]">UMT</Table.Head>
|
||||
<Table.Head class="w-[100px]">Adv. Impo</Table.Head>
|
||||
<Table.Head class="w-[100px]">Adv. Expo</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if filteredFractions.length === 0}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={7} class="text-center py-8 text-muted-foreground">
|
||||
{#if isLoading}
|
||||
Cargando fracciones arancelarias...
|
||||
{:else if searchQuery}
|
||||
No se encontraron fracciones que coincidan con la búsqueda
|
||||
{:else}
|
||||
No hay fracciones arancelarias disponibles
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each filteredFractions as fraction (fraction.id)}
|
||||
<Table.Row class="hover:bg-muted/50">
|
||||
<Table.Cell class="font-mono text-sm">{fraction.code}</Table.Cell>
|
||||
<Table.Cell class="font-mono text-sm font-medium">{fraction.fraction}</Table.Cell>
|
||||
<Table.Cell class="text-sm">
|
||||
{fraction.description || '-'}
|
||||
</Table.Cell>
|
||||
<Table.Cell class="text-sm">{fraction.nico || '-'}</Table.Cell>
|
||||
<Table.Cell class="text-sm">{fraction.umt || '-'}</Table.Cell>
|
||||
<Table.Cell class="text-sm">{fraction.adv_impo || '-'}</Table.Cell>
|
||||
<Table.Cell class="text-sm">{fraction.adv_expo || '-'}</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<!-- Paginación -->
|
||||
{#if !searchQuery && totalPages > 1}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={currentPage === 1 || isLoading}
|
||||
onclick={() => goToPage(1)}
|
||||
>
|
||||
Primera
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={currentPage === 1 || isLoading}
|
||||
onclick={() => goToPage(currentPage - 1)}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<span class="px-4 text-sm">
|
||||
Página {currentPage} de {totalPages}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={currentPage === totalPages || isLoading}
|
||||
onclick={() => goToPage(currentPage + 1)}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={currentPage === totalPages || isLoading}
|
||||
onclick={() => goToPage(totalPages)}
|
||||
>
|
||||
Última
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user