- Changed the type of the depreciation_date field from Integer to Date in the LineItem model to enhance date accuracy and compliance with SQLAlchemy v2 standards.
384 lines
14 KiB
Python
384 lines
14 KiB
Python
"""
|
|
Normalized Database Schema for SCAF (Fixed Assets) and SCAII (Parts Inventory)
|
|
SQLAlchemy v2 - Annex 24 Compliance
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import Optional, TYPE_CHECKING
|
|
from core.database import Base
|
|
from decimal import Decimal
|
|
from sqlalchemy import Boolean, Date, String, Integer, Numeric, SmallInteger, ForeignKey
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
|
|
from core.database import Base
|
|
|
|
from api.v1.modules.a76.classes.models import Class
|
|
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
|
|
|
if TYPE_CHECKING:
|
|
from .line_financials.models import LineFinancial
|
|
from .line_quantities.models import LineQuantity
|
|
from .line_customs.models import LineCustom
|
|
from .line_descriptions.models import LineDescription
|
|
from .line_references.models import LineReference
|
|
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
|
from api.v1.modules.a76.parts.models import Part
|
|
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
|
|
|
# ============================================================================
|
|
# CORE ENTITIES
|
|
# ============================================================================
|
|
|
|
|
|
class LineItem(Base, TenantScopedMixin, TimestampMixin):
|
|
"""
|
|
Unified item header table for all import/export operations
|
|
Consolidates headers from both SCAF and SCAII systems
|
|
"""
|
|
|
|
__tablename__ = "item_lines"
|
|
__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
|
|
|
|
line_number: Mapped[int] = mapped_column(Integer) # LINEAIMPO/LINEAEXPO/LINEA
|
|
|
|
# Part identification
|
|
part_number_id: Mapped[Optional[int]] = mapped_column(
|
|
Integer, ForeignKey("a76.parts.id")
|
|
) # NUMPARTE
|
|
component_part_number_id: Mapped[Optional[int]] = mapped_column(
|
|
Integer, ForeignKey("a76.parts.id")
|
|
) # NUMPARTECOM
|
|
class_id: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("a76.classes.id")
|
|
) # CLASE
|
|
|
|
# Unit of measure
|
|
unit_of_measure: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("a76.units_of_measure.id")
|
|
) # UNIDADMEDIDA/UNIMED
|
|
alternate_unit: Mapped[Optional[int]] = mapped_column(
|
|
ForeignKey("a76.units_of_measure.id")
|
|
) # UNIMEDALTERNA
|
|
uma_key: Mapped[Optional[str]] = mapped_column(String(2)) # CLAVEUMA
|
|
auxiliary_unit: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMEDAUXILIAR
|
|
|
|
# Permits and certificates
|
|
permit_number: Mapped[Optional[str]] = mapped_column(String(20)) # NUMPERMISO
|
|
page_line: Mapped[Optional[str]] = mapped_column(String(10)) # PAGRENGLON
|
|
has_certificate: Mapped[Optional[bool]] = mapped_column(
|
|
Boolean
|
|
) # TIENECO/CERTORIGEN
|
|
certificate_number: Mapped[Optional[str]] = mapped_column(
|
|
String(10)
|
|
) # NOCERTIFICADO
|
|
octave_permit: Mapped[Optional[str]] = mapped_column(String(20)) # PERMISOROCTAVA
|
|
permits_ped: Mapped[Optional[str]] = mapped_column(String(500)) # PERMISOSPED
|
|
|
|
# FDA
|
|
has_fda_code: Mapped[Optional[bool]] = mapped_column(Boolean) # LLEVACODFDA
|
|
fda_key: Mapped[Optional[str]] = mapped_column(String(10)) # CLAVEFDA
|
|
|
|
# Special flags
|
|
is_military_mcia: Mapped[Optional[bool]] = mapped_column(Boolean) # ESMCIAMILITAR
|
|
|
|
# IV32 (Tax identification)
|
|
iv32_type_key: Mapped[Optional[str]] = mapped_column(String(5)) # CLAVETIPOIV32
|
|
iv32_number: Mapped[Optional[str]] = mapped_column(String(35)) # NUMEROIV32
|
|
|
|
# IN CASE OF EXPO
|
|
scrap_invoice: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURASCRAP
|
|
consecutive_destination: Mapped[Optional[int]] = mapped_column(
|
|
Integer
|
|
) # CONSECUTIVODES
|
|
ctm_section: Mapped[Optional[str]] = mapped_column(String(3)) # APARTADOCTM
|
|
|
|
# Tax payment
|
|
tax_payment: Mapped[Optional[bool]] = mapped_column(Boolean) # PAGOIMPUESTO
|
|
payment_method: Mapped[Optional[str]] = mapped_column(
|
|
String(9)
|
|
) # FORMAPAGO/FORMAPAGOTIGI
|
|
igi_amount: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # MONTOIGI
|
|
igi_payment_method: Mapped[Optional[str]] = mapped_column(
|
|
String(9)
|
|
) # FORMAPAGOTIGI
|
|
|
|
# FCC
|
|
fcc_key: Mapped[Optional[str]] = mapped_column(String(30)) # CLAVEFCC
|
|
|
|
# Valuation method
|
|
valuation_method: Mapped[Optional[str]] = mapped_column(String(2)) # METVALOR
|
|
valuation_determined_value: Mapped[Optional[Decimal]] = mapped_column(
|
|
Numeric(29, 8)
|
|
) # METVALORVALORDETERMINADO/METVALORACIONVALORDETERMINADO
|
|
valuation_reason: Mapped[Optional[str]] = mapped_column(
|
|
String(500)
|
|
) # METVALORMOTIVODEUSO/METVALORACIONMOTIVODEUSO
|
|
|
|
# Container rules
|
|
container_rule: Mapped[Optional[str]] = mapped_column(String(50)) # CONTENEDORREGLA
|
|
container_parts_ii: Mapped[Optional[str]] = mapped_column(
|
|
String(50)
|
|
) # CONTENEDORPARTESII
|
|
|
|
# APHIS
|
|
consecutive_aphis: Mapped[Optional[int]] = mapped_column(
|
|
Integer
|
|
) # CONSECUTIVOAPHIS
|
|
|
|
# BOM/Commercial
|
|
bom_version: Mapped[Optional[int]] = mapped_column(Integer) # VERSIONBOM
|
|
bill_version: Mapped[Optional[int]] = mapped_column(Integer) # VERSIONBILL
|
|
|
|
# TLCAN value
|
|
tlcan_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTLCAN
|
|
|
|
# Identifier
|
|
identifier: Mapped[Optional[str]] = mapped_column(String(2)) # IDENTIFICADOR
|
|
|
|
# Validation fields
|
|
validation_zero: Mapped[Optional[int]] = mapped_column(Integer) # VALIDACIONZERO
|
|
validation_one: Mapped[Optional[int]] = mapped_column(Integer) # VALIDACIONUNO
|
|
|
|
# Material type
|
|
material_type: Mapped[Optional[str]] = mapped_column(
|
|
String(50)
|
|
) # TIPOMAT/TIPODENUMPARTE
|
|
|
|
# Order concept
|
|
order_type: Mapped[Optional[str]] = mapped_column(String(50)) # TIPODEORDEN
|
|
line_concept: Mapped[Optional[str]] = mapped_column(
|
|
String(50)
|
|
) # CONCEPTODELAPARTIDA
|
|
|
|
# Review dispatch
|
|
review_dispatch: Mapped[Optional[str]] = mapped_column(String(10)) # REVISARDESP
|
|
|
|
# Take component from PT
|
|
take_component_pt: Mapped[Optional[int]] = mapped_column(Integer) # TOMARCOMOPT
|
|
|
|
# Pallet
|
|
pallet2: Mapped[Optional[int]] = mapped_column(SmallInteger) # PALLET2
|
|
|
|
# Wildcard field
|
|
wildcard_field: Mapped[Optional[str]] = mapped_column(String(100)) # CAMPOCOMODIN
|
|
|
|
# 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[datetime]] = mapped_column(
|
|
Date
|
|
) # 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
|
|
|
|
invoice: Mapped["InvoiceHeader"] = relationship("InvoiceHeader")
|
|
|
|
# Relationships
|
|
financial: Mapped[Optional["LineFinancial"]] = relationship(
|
|
back_populates="line", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
quantity: Mapped[Optional["LineQuantity"]] = relationship(
|
|
back_populates="line", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
customs: Mapped[Optional["LineCustom"]] = relationship(
|
|
back_populates="line", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
description: Mapped[Optional["LineDescription"]] = relationship(
|
|
back_populates="line", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
reference: Mapped[Optional["LineReference"]] = relationship(
|
|
back_populates="line", cascade="all, delete-orphan", uselist=False
|
|
)
|
|
class_info: Mapped[Optional["Class"]] = relationship(
|
|
"api.v1.modules.a76.classes.models.Class",
|
|
foreign_keys=[class_id],
|
|
viewonly=True,
|
|
)
|
|
unit_of_measure_info: Mapped[Optional["UnitOfMeasure"]] = relationship(
|
|
"api.v1.modules.a76.general_catalogs.units_of_measure.models.UnitOfMeasure",
|
|
foreign_keys=[unit_of_measure],
|
|
viewonly=True,
|
|
)
|
|
fa_data: Mapped[Optional["FaLineItem"]] = relationship(
|
|
"FaLineItem",
|
|
back_populates="master_info",
|
|
cascade="all, delete-orphan",
|
|
uselist=False,
|
|
)
|
|
part_info: Mapped[Optional["Part"]] = relationship(
|
|
"Part",
|
|
foreign_keys=[part_number_id],
|
|
viewonly=True,
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# 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(LineItem).filter(
|
|
LineItem.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(LineItem).join(LineItem).filter(
|
|
LineItem.system_origin == 'SCAF',
|
|
LineItem.value_depreciated_usd.isnot(None)
|
|
)
|
|
```
|
|
"""
|