from decimal import Decimal from typing import Optional, Any from pydantic import BaseModel, Field, ConfigDict, field_validator, model_validator # Import nested schemas from ..line_customs.schemas import ( LineCustomCreate, LineCustomUpdate, LineCustomResponse, ) from ..line_descriptions.schemas import ( LineDescriptionCreate, LineDescriptionUpdate, LineDescriptionResponse, ) from ..line_quantities.schemas import ( LineQuantityCreate, LineQuantityUpdate, LineQuantityResponse, ) from ..line_financials.schemas import ( LineFinancialCreate, LineFinancialUpdate, LineFinancialResponse, ) from ..line_references.schemas import ( LineReferenceCreate, LineReferenceUpdate, LineReferenceResponse, ) from api.v1.modules.a24.fa.fa_item_lines.dto import ( FaLineItemCreateDTO, FaLineItemUpdateDTO, FaLineItemResponseDTO, ) # ============================================================================ # LINE ITEM SCHEMAS # ============================================================================ class LineItemBase(BaseModel): """Base schema for line items""" model_config = ConfigDict(populate_by_name=True) item_id: int = Field(..., description="ID of the parent item") line_number: int = Field(..., description="Line number") # Part identification part_number_id: Optional[int] = Field( None, description="Part number", alias="part_number", serialization_alias="part_number_id", ) component_part_number_id: Optional[int] = Field( None, description="Component part number", alias="component_part_number", serialization_alias="component_part_number_id", ) class_id: Optional[int] = Field(None, description="Class code") # Unit of measure unit_of_measure: Optional[int] = Field(None, description="Unit of measure") alternate_unit: Optional[int] = Field(None, description="Alternate unit") uma_key: Optional[str] = Field(None, max_length=2, description="UMA key") auxiliary_unit: Optional[str] = Field( None, max_length=5, description="Auxiliary unit" ) # Permits and certificates permit_number: Optional[str] = Field( None, max_length=20, description="Permit number" ) page_line: Optional[str] = Field(None, max_length=10, description="Page line") has_certificate: Optional[bool] = Field(None, description="Has certificate") certificate_number: Optional[str] = Field( None, max_length=10, description="Certificate number" ) octave_permit: Optional[str] = Field( None, max_length=20, description="Octave permit" ) permits_ped: Optional[str] = Field(None, max_length=500, description="PED permits") # FDA has_fda_code: Optional[bool] = Field(None, description="Has FDA code") fda_key: Optional[str] = Field(None, max_length=10, description="FDA key") # Special flags is_military_mcia: Optional[bool] = Field( None, description="Is military merchandise" ) # IV32 iv32_type_key: Optional[str] = Field( None, max_length=5, description="IV32 type key" ) iv32_number: Optional[str] = Field(None, max_length=35, description="IV32 number") # Export specific scrap_invoice: Optional[str] = Field( None, max_length=15, description="Scrap invoice" ) consecutive_destination: Optional[int] = Field( None, description="Consecutive destination" ) ctm_section: Optional[str] = Field(None, max_length=3, description="CTM section") # Tax payment tax_payment: Optional[bool] = Field(None, description="Tax payment") payment_method: Optional[str] = Field( None, max_length=9, description="Payment method" ) igi_amount: Optional[Decimal] = Field(None, description="IGI amount") igi_payment_method: Optional[str] = Field( None, max_length=9, description="IGI payment method" ) # FCC fcc_key: Optional[str] = Field(None, max_length=30, description="FCC key") # Valuation method valuation_method: Optional[str] = Field( None, max_length=2, description="Valuation method" ) valuation_determined_value: Optional[Decimal] = Field( None, description="Valuation determined value" ) valuation_reason: Optional[str] = Field( None, max_length=500, description="Valuation reason" ) # Container rules container_rule: Optional[str] = Field( None, max_length=50, description="Container rule" ) container_parts_ii: Optional[str] = Field( None, max_length=50, description="Container parts II" ) # APHIS consecutive_aphis: Optional[int] = Field(None, description="Consecutive APHIS") # BOM/Commercial bom_version: Optional[int] = Field(None, description="BOM version") bill_version: Optional[int] = Field(None, description="Bill version") # TLCAN value tlcan_value: Optional[Decimal] = Field(None, description="TLCAN value") # Identifier identifier: Optional[str] = Field(None, max_length=2, description="Identifier") # Validation fields validation_zero: Optional[int] = Field(None, description="Validation zero") validation_one: Optional[int] = Field(None, description="Validation one") # Material type material_type: Optional[str] = Field( None, max_length=50, description="Material type" ) # Order concept order_type: Optional[str] = Field(None, max_length=50, description="Order type") line_concept: Optional[str] = Field(None, max_length=50, description="Line concept") # Review dispatch review_dispatch: Optional[str] = Field( None, max_length=10, description="Review dispatch" ) # Take component from PT take_component_pt: Optional[int] = Field(None, description="Take component from PT") # Pallet pallet2: Optional[int] = Field(None, description="Pallet 2") # Wildcard field wildcard_field: Optional[str] = Field( None, max_length=100, description="Wildcard field" ) class LineItemCreate(LineItemBase): """Schema for creating line item with all nested data""" # Override base fields - estos se asignan automáticamente en el service item_id: Optional[int] = Field( None, description="ID of the parent item (auto-assigned)" ) line_number: Optional[int] = Field(None, description="Line number (auto-assigned)") financial: Optional[LineFinancialCreate] = Field( None, description="Financial data for this line" ) quantity: Optional[LineQuantityCreate] = Field( None, description="Quantity data for this line" ) customs: Optional[LineCustomCreate] = Field( None, description="Customs data for this line" ) description: Optional[LineDescriptionCreate] = Field( None, description="Description data for this line" ) reference: Optional[LineReferenceCreate] = Field( None, description="Reference data for this line" ) fa_data: Optional[FaLineItemCreateDTO] = Field( None, description="Fixed Asset data for this line" ) class LineItemUpdate(LineItemBase): """Schema for updating line item with all nested data""" # Override base fields - todos opcionales en updates item_id: Optional[int] = Field(None, description="ID of the parent item") line_number: Optional[int] = Field(None, description="Line number") financial: Optional[LineFinancialUpdate] = Field( None, description="Financial data for this line" ) quantity: Optional[LineQuantityUpdate] = Field( None, description="Quantity data for this line" ) customs: Optional[LineCustomUpdate] = Field( None, description="Customs data for this line" ) description: Optional[LineDescriptionUpdate] = Field( None, description="Description data for this line" ) reference: Optional[LineReferenceUpdate] = Field( None, description="Reference data for this line" ) fa_data: Optional[FaLineItemUpdateDTO] = Field( None, description="Fixed Asset data for this line" ) class LineItemResponse(LineItemBase): """Schema for line item response with all nested data""" id: int item_id: int financial: Optional[LineFinancialResponse] = None quantity: Optional[LineQuantityResponse] = None customs: Optional[LineCustomResponse] = None description: Optional[LineDescriptionResponse] = None reference: Optional[LineReferenceResponse] = None fa_data: Optional[FaLineItemResponseDTO] = None # Fields populated from relationships class_code: Optional[str] = None class_description: Optional[str] = None unit_of_measure_code: Optional[str] = None model_config = ConfigDict(from_attributes=True) @model_validator(mode="before") @classmethod def extract_relationship_info(cls, data: Any) -> Any: """Extract class_code, class_description and unit_of_measure_code from relationships""" if isinstance(data, dict): return data # It's an ORM object result = {} for key in cls.model_fields.keys(): if hasattr(data, key): result[key] = getattr(data, key) # Map model field names to schema field names for aliased fields if hasattr(data, "part_number"): result["part_number_id"] = data.part_number if hasattr(data, "component_part_number"): result["component_part_number_id"] = data.component_part_number # Extract class info if hasattr(data, "class_info") and data.class_info is not None: result["class_code"] = data.class_info.class_code result["class_description"] = data.class_info.description_es # Extract unit of measure code if ( hasattr(data, "unit_of_measure_info") and data.unit_of_measure_info is not None ): result["unit_of_measure_code"] = data.unit_of_measure_info.code return result