""" Modelo ORM para datos específicos de Inventario y Manufactura (S-Partes) - Anexo 24 """ from typing import TYPE_CHECKING, Optional, List from decimal import Decimal from api.v1.common.base_models import TenantScopedMixin, TimestampMixin from core.database import Base from sqlalchemy import ( Integer, Numeric, PrimaryKeyConstraint, String, Boolean, ForeignKeyConstraint ) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship if TYPE_CHECKING: from api.v1.modules.a76.parts.models import Part # Importar los modelos Aphis para asegurar su registro en SQLAlchemy import api.v1.modules.a24.inv.inv_aphis.models as aphis_models class InvPart(Base, TenantScopedMixin, TimestampMixin): """ Tabla inv_partes: Extensión de Anexo 24 para Inventarios (SPartes). """ __tablename__ = "inv_partes" __table_args__ = ( PrimaryKeyConstraint("id", name="inv_partes_pkey"), ForeignKeyConstraint( ["id"], ["a76.parts.id"], name="fk_inv_partes_master" ), {"schema": "a24", "extend_existing": True}, ) # Relación 1:1 - El ID es el mismo de la tabla maestra id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False) # --- 1. ATRIBUTOS PRINCIPALES DE INVENTARIO --- part_type: Mapped[Optional[str]] = mapped_column(String(10)) # TIPOPARTE material_type: Mapped[Optional[str]] = mapped_column(String(10)) # TIPOMAT reference_number: Mapped[Optional[str]] = mapped_column(String(70)) # NUMPARTEREF flex_reference_number: Mapped[Optional[str]] = mapped_column(String(120)) # NUMPARTEREFFLEX # Conversiones equivalent_uom: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMEDEQUIV conversion_factor: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # FACTORCONV stock_uom: Mapped[Optional[str]] = mapped_column(String(5)) # UMEXISTENCIA alternate_uom: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMEDALTERNA conversion_uom: Mapped[Optional[str]] = mapped_column(String(9)) # UMCONVERSION # Valor Agregado added_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORAGREGADO added_value_type: Mapped[Optional[str]] = mapped_column(String(2)) # TIPOVA assigned_client: Mapped[Optional[str]] = mapped_column(String(50)) # CLIENTEASIGNADO supplier_code: Mapped[Optional[str]] = mapped_column(String(8)) # PROVEEDOR is_textile: Mapped[Optional[str]] = mapped_column(String(2)) # ESTEXTIL # --- 2. MANUFACTURA Y PELIGROSIDAD --- bom_version: Mapped[Optional[int]] = mapped_column(Integer) # VERSIONBOM / VERSIONBILL is_repair: Mapped[Optional[str]] = mapped_column(String(3)) # ESREPARACION is_hazardous: Mapped[Optional[str]] = mapped_column(String(1)) # ESMATPELIGROSO emergency_number: Mapped[Optional[str]] = mapped_column(String(30)) # NUMEMERGENCIA danger_class: Mapped[Optional[str]] = mapped_column(String(4)) # CLASEDEPELIGRO packaging_group: Mapped[Optional[str]] = mapped_column(String(3)) # GRUPOEMBALAJE # Dimensiones width: Mapped[Optional[str]] = mapped_column(String(50)) # ANCHURA thickness: Mapped[Optional[str]] = mapped_column(String(50)) # ESPESOR specification: Mapped[Optional[str]] = mapped_column(String(50)) # SPEC # --- 3. COSTOS DETALLADOS Y ADUANA US --- total_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTOTAL direct_labor: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # TRABAJODIREC general_expenses: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # GASTOGRALES total_expenses: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # TOTALGASTOS depreciation: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # DEPRECIACION tooling: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # TOOLING material_consumed: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # MATCONSUMED profit: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # GANANCIA # Fracciones Internacionales us_fraction_alt: Mapped[Optional[str]] = mapped_column(String(13)) # FRACEUA ca_fraction: Mapped[Optional[str]] = mapped_column(String(13)) # FRACCANADA ad_valorem_us: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) # ADVALOREMAME # Nafta / USMCA nafta_result: Mapped[Optional[str]] = mapped_column(String(19)) # RESULTADOCALCULONAFTA nafta_percentage: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) # PORCENTAJECALCULONAFTA # Impuestos Específicos (Derechos de Trámite Admon) dta: Mapped[Optional[str]] = mapped_column(String(19)) # DTA dtb: Mapped[Optional[str]] = mapped_column(String(19)) # DTB dtg: Mapped[Optional[str]] = mapped_column(String(19)) # DTG # --- CAMPOS ADICIONALES FRONTEND --- substitute_part: Mapped[Optional[str]] = mapped_column(String(70)) complementary_part: Mapped[Optional[str]] = mapped_column(String(70)) preference_part: Mapped[Optional[str]] = mapped_column(String(70)) use_alternate_quantity: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) un_number: Mapped[Optional[str]] = mapped_column(String(30)) shipping_name: Mapped[Optional[str]] = mapped_column(String(200)) hazard_notes: Mapped[Optional[str]] = mapped_column(String(500)) repair_unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) repair_added_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) fraction_9801: Mapped[Optional[str]] = mapped_column(String(10)) immex_type: Mapped[Optional[str]] = mapped_column(String(10)) disable_movements: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) pga_program_code: Mapped[Optional[str]] = mapped_column(String(10)) usmca_fraction: Mapped[Optional[str]] = mapped_column(String(10)) scrap_part_number: Mapped[Optional[str]] = mapped_column(String(70)) waste_part_number: Mapped[Optional[str]] = mapped_column(String(70)) scrap_description_en: Mapped[Optional[str]] = mapped_column(String(500)) scrap_description_es: Mapped[Optional[str]] = mapped_column(String(500)) scrap_export_fraction: Mapped[Optional[str]] = mapped_column(String(10)) scrap_us_fraction: Mapped[Optional[str]] = mapped_column(String(10)) equivalent_uom_2: Mapped[Optional[str]] = mapped_column(String(5)) conversion_factor_2: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) has_auxiliary: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) auxiliary_uom: Mapped[Optional[str]] = mapped_column(String(5)) auxiliary_conversion: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) auxiliary_unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) mex_packing: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) sales_order: Mapped[Optional[str]] = mapped_column(String(50)) use_rule_8: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) sector: Mapped[Optional[str]] = mapped_column(String(150)) origin_country: Mapped[Optional[str]] = mapped_column(String(3), default='MEX') fraction_type: Mapped[Optional[str]] = mapped_column(String(10)) # --- NUEVOS CAMPOS EXTENSION --- agency_code_definition: Mapped[Optional[str]] = mapped_column(String(50)) # Fila 7 carta_porte: Mapped[Optional[str]] = mapped_column(String(100)) # Fila 10 client_part_names: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # Fila 5 part_identifiers: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # Fila 9 substitute_parts: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # Pestaña Continuación aphis_data: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True, default={}) # Sección Aphis en Continuación # JSONB para almacenar clientes con restricción de no descarga non_discharge_clients: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # --- RELACIÓN --- aphis_records: Mapped[List["InvPartAphisGeneral"]] = relationship( "InvPartAphisGeneral", back_populates="inv_part", cascade="all, delete-orphan" ) # Usamos string "Part" para evitar problemas de carga master_info: Mapped["Part"] = relationship("Part", back_populates="inv_data") @property def bom_items(self): """Exponer los BOM items de la parte maestra para Pydantic""" if self.master_info: return self.master_info.bom_items return [] @property def countries(self): """Exponer los países de la parte maestra para Pydantic""" if self.master_info: return self.master_info.inv_countries return [] def __repr__(self) -> str: return f""