Files
plantillas-proyectos/backend/api/v1/modules/a76/items/models.py
acazares b94d0ac7fb refactor: restructure item interfaces and update related components
- Renamed LineItem interface to Item and adjusted properties accordingly.
- Updated CreateItemData and UpdateItemData interfaces to reflect new structure.
- Modified components to use the new Item interface, removing nested lines.
- Adjusted data binding in item configuration, main data, and other related components.
- Simplified item creation and editing logic by removing unnecessary nesting.
- Ensured all references to line items are updated to reflect the new structure.
2026-02-18 09:56:50 -06:00

384 lines
14 KiB
Python

"""
Normalized Database Schema for SCAF (Fixed Assets) and SCAII (Parts Inventory)
SQLAlchemy v2 - Annex 24 Compliance
"""
from typing import Optional, TYPE_CHECKING
from core.database import Base
from decimal import Decimal
from sqlalchemy import (
Boolean,
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[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
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)
)
```
"""