""" Normalized Database Schema for SCAF (Fixed Assets) and SCAII (Parts Inventory) SQLAlchemy v2 - Annex 24 Compliance """ from typing import Optional, TYPE_CHECKING, List from sqlalchemy import Boolean, String, Integer, ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship from api.v1.common.base_models import TenantScopedMixin, TimestampMixin from core.database import Base if TYPE_CHECKING: from .line_items.models import LineItem # ============================================================================ # CORE ENTITIES # ============================================================================ class Item(Base, TenantScopedMixin, TimestampMixin): """ Unified item header table for all import/export operations Consolidates headers from both SCAF and SCAII systems """ __tablename__ = "items" __table_args__ = { "schema": "a76", } id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) # CONSECUTIVO # Item references reference_number: Mapped[Optional[str]] = mapped_column( String(20)) # NUMREFERENCIA order: Mapped[Optional[str]] = mapped_column( String(50)) # ORDENCOMPRA / ORDENVENTA guide_number: Mapped[Optional[str]] = mapped_column( String(50)) # NUMEROGUIA/NUMERODEGUIA # Dates depreciation_date: Mapped[Optional[int]] = mapped_column( Integer) # FECHADEPRECIACION # Administrative fields rectification: Mapped[Optional[bool]] = mapped_column( Boolean) # RECTIFICACION warehouse: Mapped[Optional[str]] = mapped_column(String(30)) # BODEGA location: Mapped[Optional[str]] = mapped_column( String(200)) # LOCALIZACION # Relationships (one-to-many) lines: Mapped[List["LineItem"]] = relationship( "LineItem", back_populates="item", cascade="all, delete-orphan") invoice: Mapped["InvoiceHeader"] = relationship("InvoiceHeader") # ============================================================================ # SUPPORTING TABLES # ============================================================================ class PackingList(Base, TenantScopedMixin, TimestampMixin): """ Packing list items From: SPartidasPackingList """ __tablename__ = "packing_lists" __table_args__ = { "schema": "a76", } id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) item_line_id: Mapped[int] = mapped_column(Integer) # LINEA packing_list_number: Mapped[Optional[str]] = mapped_column( String(100)) # NUMPACKINGLIST class CTMReceipt(Base, TenantScopedMixin, TimestampMixin): """ CTM Receipt lines (temporary manufacturing) From: SPartidasReciboCTM """ __tablename__ = "ctm_receipts" __table_args__ = { "schema": "a76", } id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) receipt_line: Mapped[int] = mapped_column( ForeignKey("a76.item_lines.id")) # LINEARECIBO option: Mapped[Optional[str]] = mapped_column(String(3)) # OPCION exit_invoice: Mapped[Optional[str]] = mapped_column( String(19)) # FACTURASALIDA class SubassemblyEntry(Base, TenantScopedMixin, TimestampMixin): """ Subassembly/Submanufacturing Entry lines From: SPartidasEntradaSM """ __tablename__ = "subassembly_entries" __table_args__ = { "schema": "a76", } id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) remission_line: Mapped[int] = mapped_column(Integer) # LINEAREMISION exit_invoice: Mapped[Optional[str]] = mapped_column( String(15)) # FACTURASALIDA exit_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEASALIDA # ============================================================================ # INDEXES AND CONSTRAINTS # ============================================================================ """ Recommended indexes for optimal query performance: CREATE INDEX idx_items_consecutive ON items(consecutive); CREATE INDEX idx_items_invoice ON items(invoice_number); CREATE INDEX idx_items_type_system ON items(item_type, system_origin); CREATE INDEX idx_items_dates ON items(invoice_date, depreciation_date); CREATE INDEX idx_lines_item ON item_lines(item_id); CREATE INDEX idx_lines_part ON item_lines(part_number); CREATE INDEX idx_lines_class ON item_lines(class_code); CREATE INDEX idx_lines_invoice_refs ON item_lines(import_invoice, export_invoice); CREATE INDEX idx_lines_fractions ON item_lines(import_fraction, export_fraction); CREATE INDEX idx_packing_consecutive ON packing_lists(consecutive); CREATE INDEX idx_packing_part ON packing_lists(part_number); CREATE INDEX idx_repair_invoice ON repair_parts(import_invoice, import_line); CREATE INDEX idx_ctm_ship_consec ON ctm_shipments(consecutive); CREATE INDEX idx_ctm_rcpt_consec ON ctm_receipts(consecutive); CREATE INDEX idx_sub_entry_consec ON subassembly_entries(consecutive); CREATE INDEX idx_sub_exit_consec ON subassembly_exits(consecutive); CREATE INDEX idx_imposition_consec ON imposition_parts(consecutive); """ # ============================================================================ # MIGRATION NOTES # ============================================================================ """ MIGRATION STRATEGY FROM ORIGINAL TABLES TO NORMALIZED SCHEMA: 1. DOCUMENT MAPPING: - QEqeMaq (Equipment Import Temp) → items (type='EQUIPMENT_IMPORT_TEMP', system='SCAF') - QEqeMaqRep (Equipment Repair Export) → items (type='EQUIPMENT_REPAIR_EXPORT', system='SCAF') - QEqiDef (Equipment Import Definitive) → items (type='EQUIPMENT_IMPORT_DEF', system='SCAF') - QEqiMaq (Equipment Machinery) → items (type='EQUIPMENT_MACHINERY', system='SCAF') - QEqiMaqRep (Equipment Machinery Repair) → items (type='EQUIPMENT_REPAIR_IMPORT', system='SCAF') - SPartidasCM (Common Commerce) → items (type='COMMON_COMMERCE', system='SCAII') - SPartidasExpo (Export) → items (type='EXPORT', system='SCAII') - SPartidasImpo (Import) → items (type='IMPORT', system='SCAII') 2. LINE MAPPING: All line items from Q* and SPartidas* tables map to item_lines with appropriate field mapping based on the original column names (preserved as comments). 3. FIELD CONSOLIDATION RULES: - Costs: Unified under unit_cost_* with currency suffix (usd/mxn/mc) - Values: Unified under value_* with currency suffix - Quantities: Unified under quantity_* with specific purpose suffixes - Descriptions: Consolidated into description_spanish/english/extra - Fractions: All fraction fields preserved with clear naming 4. DATA INTEGRITY: - Original CONSECUTIVO + LINE number preserved for traceability - Foreign key relationships established via item_id - All original fields retained to prevent data loss 5. SPECIAL TABLES: - PackingList, RepairPart, CTM*, Subassembly*, ImpositionPart remain separate as they serve specific purposes and don't fit the main item/line pattern 6. BENEFITS: - Eliminates redundancy across 13 original tables - Unified query interface for all operations - Maintains full audit trail with original field names - Enables cross-system reporting (SCAF + SCAII) - Simplifies maintenance with single schema 7. QUERYING EXAMPLES: ```python # Get all imports (both systems) session.query(Item).filter( Item.item_type.in_(['IMPORT', 'EQUIPMENT_IMPORT_TEMP', 'EQUIPMENT_IMPORT_DEF']) ) # Get all lines for a specific part across all items session.query(LineItem).filter( LineItem.part_number == 'ABC123' ) # Get SCAF equipment with depreciation session.query(Item).join(LineItem).filter( Item.system_origin == 'SCAF', LineItem.value_depreciated_usd.isnot(None) ) ``` """