from datetime import datetime from decimal import Decimal from typing import List, Optional from pydantic import BaseModel, Field, ConfigDict # --- SUB-DTO: DATOS ADUANALES (FaData) --- class FaDataDTO(BaseModel): origin_country: Optional[str] = None sector: Optional[str] = None fraction_type: Optional[str] = None model_config = ConfigDict(from_attributes=True) # --- SUB-DTO: DATOS DE INVENTARIO Y COSTEO (InvData) --- class InvDataDTO(BaseModel): part_type: Optional[str] = None material_type: Optional[str] = None reference_number: Optional[str] = None flex_reference_number: Optional[str] = None equivalent_uom: Optional[str] = None conversion_factor: Optional[Decimal] = None stock_uom: Optional[str] = None alternate_uom: Optional[str] = None conversion_uom: Optional[str] = None added_value: Optional[Decimal] = None added_value_type: Optional[str] = None assigned_client: Optional[str] = None supplier_code: Optional[str] = None is_textile: Optional[str] = None bom_version: Optional[int] = None is_repair: Optional[str] = None is_hazardous: Optional[str] = None emergency_number: Optional[str] = None danger_class: Optional[str] = None packaging_group: Optional[str] = None width: Optional[str] = None thickness: Optional[str] = None specification: Optional[str] = None # Desglose de Costos (Anexo 24 - Valor Agregado) total_value: Optional[Decimal] = None direct_labor: Optional[Decimal] = None general_expenses: Optional[Decimal] = None total_expenses: Optional[Decimal] = None depreciation: Optional[Decimal] = None tooling: Optional[Decimal] = None material_consumed: Optional[Decimal] = None profit: Optional[Decimal] = None # Fracciones adicionales y TLCAN/T-MEC us_fraction_alt: Optional[str] = None ca_fraction: Optional[str] = None ad_valorem_us: Optional[Decimal] = None nafta_result: Optional[str] = None nafta_percentage: Optional[Decimal] = None dta: Optional[str] = None dtb: Optional[str] = None dtg: Optional[str] = None model_config = ConfigDict(from_attributes=True) class PartBase(BaseModel): client_id: int part_number: str = Field(..., max_length=70) commercial_part_number: Optional[str] = None description_spanish: Optional[str] = None description_english: Optional[str] = None part_class: Optional[str] = None unit_of_measure: Optional[str] = "PZ" unit_cost: Optional[Decimal] = None currency_key: Optional[str] = None currency_type: Optional[str] = None unit_weight: Optional[Decimal] = None weight_type: Optional[str] = None fraction: Optional[str] = None us_fraction: Optional[str] = None # Regulatorios fda_key: Optional[str] = None fcc_key: Optional[str] = None license_code: Optional[str] = None eccn: Optional[str] = None export_code: Optional[str] = None exclusion_symbol: Optional[str] = None is_active: bool = True part_photo: Optional[str] = None # Anidados fa_data: Optional[FaDataDTO] = None inv_data: Optional[InvDataDTO] = None # --- CREACIÓN --- class PartCreateDTO(PartBase): # Opcional para que lo tome de la URL si no viene en el body company_id: Optional[int] = None # --- ACTUALIZACIÓN --- class PartUpdateDTO(PartBase): client_id: Optional[int] = None part_number: Optional[str] = None # Todo opcional para PATCH pass # --- RESPUESTA --- class PartResponseDTO(PartBase): id: int tenant_id: int company_id: int creation_date: Optional[int] = None modification_date_iso: Optional[datetime] = None model_config = ConfigDict(from_attributes=True) # --- BÁSICO (Para listados ligeros) --- class PartBasicDTO(BaseModel): id: int part_number: str description_spanish: Optional[str] = None fraction: Optional[str] = None is_active: bool model_config = ConfigDict(from_attributes=True) # --- LISTADO PAGINADO --- class PartListResponseDTO(BaseModel): items: List[PartResponseDTO] total: int page: int page_size: int pages: int