""" Schemas for Items and related entities Complete nested one-to-one structure: LineItem -> LineFinancial -> LineQuantity -> LineCustoms -> LineDescription -> LineReference """ from typing import Any, Optional, Union from datetime import datetime from decimal import Decimal from pydantic import BaseModel, Field, ConfigDict, model_validator # Import schemas from individual modules # 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, ) # ============================================================================ # ITEM SCHEMAS # ============================================================================ class LineItemBase(BaseModel): """Base schema for items""" model_config = ConfigDict(populate_by_name=True) invoice_id: int = Field(..., description="Invoice ID") line_number: int = Field(..., description="Line number") # Part identification part_number_id: Union[int, str, None] = Field( None, description="Part number", alias="part_number", serialization_alias="part_number_id", ) component_part_number_id: Union[int, str, None] = 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" ) reference_number: Optional[str] = Field( None, max_length=20, description="Reference number" ) order: Optional[str] = Field(None, max_length=50, description="Order") guide_number: Optional[str] = Field(None, max_length=50, description="Guide number") # Dates depreciation_date: Optional[int] = Field(None, description="Depreciation date") # Administrative fields rectification: Optional[int] = Field(None, description="Rectification") warehouse: Optional[str] = Field(None, max_length=30, description="Warehouse") location: Optional[str] = Field(None, max_length=200, description="Location") class LineItemCreate(LineItemBase): """Schema for creating item with nested lines (one-to-many)""" # Override base fields - estos se asignan automáticamente en el service 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 item""" invoice_id: Optional[int] = Field(None, description="Invoice ID") # Override base fields - todos opcionales en updates 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 single item response""" id: int invoice_id: int line_number: int # Part identification part_number_id: Optional[int] = Field( None, alias="part_number", serialization_alias="part_number_id" ) component_part_number_id: Optional[int] = Field( None, alias="component_part_number", serialization_alias="component_part_number_id", ) class_id: Optional[int] = None # Nested data 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 # Additional fields that might be present reference_number: Optional[str] = None order: Optional[str] = None warehouse: Optional[str] = None location: Optional[str] = None model_config = ConfigDict(from_attributes=True, populate_by_name=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 class LineItemListResponse(BaseModel): """Schema for paginated item list""" total: int = Field(..., description="Total number of items") items: list[LineItemResponse] = Field(..., description="List of items") skip: int = Field(..., description="Number of skipped items") limit: int = Field(..., description="Maximum items per page") model_config = ConfigDict(from_attributes=True)