Merge pull request 'feat: Add models for line items and their series, including detailed attributes for item tracking and classification' (#28) from feature/item_lines into feature/facturas

Reviewed-on: ADUANASOFT/anexo76#28
This commit is contained in:
2025-12-19 15:22:00 +00:00
6 changed files with 683 additions and 7 deletions

View File

@@ -0,0 +1,34 @@
from typing import Optional
from sqlalchemy import Boolean, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
class LineItem(Base):
__tablename__ = "line_items"
__table_args__ = (
{"schema": "a24"}
)
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
item_id: Mapped[int] = mapped_column(ForeignKey("a76.items.id"))
# Asset information (SCAF specific)
asset_number: Mapped[Optional[str]] = mapped_column(String(25)) # ASSETNUMBER
asset_photo: Mapped[Optional[str]] = mapped_column(String(255)) # FOTOACTIVOFIJO
equipment_message: Mapped[Optional[str]] = mapped_column(String(40)) # EQI_MENSAJE
invoice_type_asset: Mapped[Optional[str]] = mapped_column(String(6)) # TIPOFACTURAASSET
return_import_invoice: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURAIMPORET
return_import_date: Mapped[Optional[int]] = mapped_column(Integer) # FECHAFACIMPORET
movement_type_import: Mapped[Optional[str]] = mapped_column(String(3)) # TIPOMOVIMPO
# Cross-references IN CASE OF IMPORT REPAIR
search_invoice: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURAEXPO (when line is import) / FACTURAIMPO (when line is export)
search_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEAEXPO (when line is import) / LINEAIMPO (when line is export)
# Search type
search_type: Mapped[Optional[str]] = mapped_column(String(10)) # TIPOBUSQUEDA
# Special flags
download: Mapped[Optional[bool]] = mapped_column(Boolean) # DESCARGA
own_equipment: Mapped[Optional[bool]] = mapped_column(Boolean) # EQUIPOPROPIO
omit_annex31: Mapped[Optional[bool]] = mapped_column(Boolean) # OMITITENANEXO31

View File

@@ -0,0 +1,620 @@
"""
Normalized Database Schema for SCAF (Fixed Assets) and SCAII (Parts Inventory)
SQLAlchemy v2 - Annex 24 Compliance
"""
from datetime import datetime
from decimal import Decimal
from typing import Optional, TYPE_CHECKING
from enum import Enum
from sqlalchemy import Boolean, String, Integer, Numeric, Text, SmallInteger, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from core.database import Base
if TYPE_CHECKING:
from .series.models import Serie
# ============================================================================
# CORE ENTITIES
# ============================================================================
class Item(Base):
"""
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_type: Mapped[str] = mapped_column(String(20)) # Type: IMPORT_TEMP, IMPORT_DEF, EXPORT, REPAIR, etc.
system_origin: Mapped[str] = mapped_column(String(10)) # SCAF or SCAII
# Item references
invoice_number: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURAIMPO/FACTURAEXPO
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
invoice_date: Mapped[Optional[int]] = mapped_column(Integer) # FECHAFACTURA
depreciation_date: Mapped[Optional[int]] = mapped_column(Integer) # FECHADEPRECIACION
# Administrative fields
rectification: Mapped[Optional[int]] = mapped_column(SmallInteger) # RECTIFICACION
warehouse: Mapped[Optional[str]] = mapped_column(String(30)) # BODEGA
location: Mapped[Optional[str]] = mapped_column(String(200)) # LOCALIZACION
# Relationships
lines: Mapped[list["LineItem"]] = relationship(back_populates="item", cascade="all, delete-orphan")
class LineItem(Base):
"""
Unified line items for all items
Consolidates all line-level data from Q and S tables
"""
__tablename__ = "item_lines"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
item_id: Mapped[int] = mapped_column(ForeignKey("a76.items.id"))
line_number: Mapped[int] = mapped_column(Integer) # LINEAIMPO/LINEAEXPO/LINEA
# Part identification
part_number: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.parts.id")) # NUMPARTE
component_part_number: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.parts.id")) # NUMPARTECOM
class_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.classes.id")) # CLASE
# Unit of measure
unit_of_measure: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.units_of_measure_general.id")) # UNIDADMEDIDA/UNIMED
alternate_unit: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.units_of_measure_general.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
# Subitem flags
is_subitem: Mapped[Optional[bool]] = mapped_column(Boolean) # ESSUBPARTIDA
contains_subitems: Mapped[Optional[bool]] = mapped_column(Boolean) # CONTIENESUBP
includes_subitems: Mapped[Optional[bool]] = mapped_column(Boolean) # INCUYESUBPARTIDAS
subitem_number: Mapped[Optional[bool]] = mapped_column(Boolean) # SUBPARTIDA
# 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
# Relationships
item: Mapped["Item"] = relationship(back_populates="lines")
class LineFinancial(Base):
"""
Financial details for line items
Consolidates all line-level data from Q and S tables
"""
__tablename__ = "line_financials"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
item_line_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id"))
# Costs - Capture
unit_cost_capture: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIOCAPTURA
# Costs - USD
unit_cost_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIODLLS/COSTOUNITARIOME
unit_cost_commercial_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUCOMDLLS
unit_cost_current_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOACTUALDLLS
unit_cost_depreciated_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTODEPRECME
unit_cost_subitem_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIOSUBPDLLS
unit_cost_auxiliary_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUAUXILIARME
sales_cost_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOVENTAME
commercial_unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNICOMERCIAL
# Costs - MXN
unit_cost_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIOPESOS/COSTOUNITARIOMN
unit_cost_commercial_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUCOMPESOS
unit_cost_current_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOACTUALMN
unit_cost_depreciated_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTODEPRECMN
unit_cost_subitem_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIOSUBPPESOS
sales_cost_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOVENTAMN
# Costs - MC (Custom Currency)
unit_cost_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(29, 8)) # COSTOUNITARIOMC
# Values - MXN
value_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMN/VALORIMPOMN/VALOREXPOMN
value_commercial_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALOREXPOCOMMN
value_updated_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORACTUALIZADOMN
value_subitem_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORSUBPMN
sub_import_value_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # SUBVALORIMPOMN
value_returned_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORRETORNADOMN
value_depreciated_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORDEPRECIADOMN
customs_value_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORADUANASMN
value_total_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTOTALMN
value_temp_material_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPTEMPMN
value_def_material_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPDEFMN
value_added_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORAGREMN
value_national_packing_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALEMPAQUENACMN
vat_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # IVAIMPOMN/IVAEXPOMN/VALORIVAMN
vat_used_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORIVAMNUSADO
advalorem_line_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # ADVALORMNLINEAPED
# Values - USD
value_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(29, 8)) # VALORME/VALORIMPOME/VALOREXPOME
value_commercial_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALOREXPOCOMME
value_updated_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORACTUALIZADOME
value_subitem_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORSUBPME
sub_import_value_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # SUBVALORIMPOME
value_returned_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORRETORNADOME
value_depreciated_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORDEPRECIADOME
customs_value_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORADUANASME
value_auxiliary_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORIMPOAUXILIARME / VALOREXPORAUXILIARME
value_total_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTOTALME
value_temp_material_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPTEMPME
value_def_material_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPDEFME
value_added_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORAGREME
value_national_packing_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALEMPAQUENACME
value_us_packing_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALEMPAQUEUSME
vat_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # IVAIMPOME/IVAEXPOME/VALORIVAME
vat_used_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORIVAMEUSADO
value_non_originating_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORNOORIGINARIOME
value_originating_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORORIGINARIOME
igi_amount_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # MONTOIGIME
exempt_amount_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # MONTOEXCENTOME
total_commercial_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTOTALCOMERCIAL
advalorem_line_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # ADVALORMELINEAPED
# Values - MC
value_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(29, 8)) # VALORIMPOMC/VALOREXPOMC
sub_import_value_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # SUBVALORIMPOMC
vat_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # IVAIMPOMC/IVAEXPOMC
value_added_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORAGREMC
value_national_packing_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALEMPAQUENACMC
value_total_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTOTALMC
value_temp_material_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPTEMPMC
value_def_material_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPDEFMC
# Relationship
item_line: Mapped["LineItem"] = relationship()
class LineQuantity(Base):
"""
Quantity details for line items
Consolidates all line-level data from Q and S tables
"""
__tablename__ = "line_quantities"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
item_line_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id"))
# Quantities
quantity: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTIMPO / CANTEXPO / CANTIMPODEF
alternate_quantity: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTALTERNA
quantity_uma: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTIMPOUMA / CANTEXPOUMA
auxiliary_quantity: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTIMPOAUXILIAR / CANTEXPOAUXILIAR
# Quantities - Special (SCAF specific)
quantity_temp_export: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTEXPOTEMP
quantity_existence: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTEXISTENCIA
quantity_returned: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTRETORNADA
quantity_returned_temp: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTRETORNADATEMP
serial_count: Mapped[Optional[int]] = mapped_column(Integer) # CANT_SERIES/CANT_SERIESDEF
# Weight
weight_unit: Mapped[Optional[str]] = mapped_column(String(3)) # 'KG' o 'LB'
net_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # PESONETO
gross_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # PESOBRUTO
# Packaging
package_key: Mapped[Optional[str]] = mapped_column(String(5)) # CLAVEBULTOS
package_quantity: Mapped[Optional[int]] = mapped_column(Integer) # CANTBULTOS
package_description: Mapped[Optional[str]] = mapped_column(String(40)) # DESCBULTOS
container_quantity: Mapped[Optional[int]] = mapped_column(SmallInteger) # CANTBULCONT
container_description: Mapped[Optional[str]] = mapped_column(String(40)) # DESCCONTENEDOR
box_count: Mapped[Optional[str]] = mapped_column(String(30)) # NOCAJAS
# Relationship
item_line: Mapped["LineItem"] = relationship()
class LineCustoms(Base):
"""
Customs details for line items
Consolidates all line-level data from Q and S tables
"""
__tablename__ = "line_customs"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
item_line_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id"))
# Tariff/Customs Classifications
fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCION / FRACCIONIMPO / FRACCIONEXPO
fraction_type: Mapped[Optional[str]] = mapped_column(String(7)) # TIPOFRACCION / TIPOFRACCIONIMPO / TIPOFRACCIONEXPO
american_fraction: Mapped[Optional[str]] = mapped_column(String(16)) # FRACCIONAMERICANA
alternate_fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCIONALTERNA
reference_fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCIONREFERENCIA
octave_fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCIONROCTAVA
tlcan_fraction: Mapped[Optional[str]] = mapped_column(String(13)) # FRACCIONTLCAN
extra_american_fraction: Mapped[Optional[str]] = mapped_column(String(16)) # FRACAMESELEXTRA
garment_fraction: Mapped[Optional[str]] = mapped_column(String(19)) # FRACCIONDELAPRENDA/FRACCIONDELAPARTIDA
# Ad Valorem
advalorem: Mapped[Optional[str]] = mapped_column(String(10)) # ADVIMPO / ADVEXPO
advalorem_numeric: Mapped[Optional[Decimal]] = mapped_column(Numeric(7, 2)) # ADVIMPONUM / ADVEXPONUM
advalorem_american: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) # ADVAME
advalorem_tlcan: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) # ADVTLCAN
# Rates
rate: Mapped[Optional[str]] = mapped_column(String(10)) # TASAIM / TASAEX
depreciation_rate: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) # TASADEPRECIA
# Origin/Destination
origin_country: Mapped[Optional[str]] = mapped_column(String(3)) # PAISORIGEN
destination_country: Mapped[Optional[str]] = mapped_column(String(3)) # PAISDESTINO
optional_country: Mapped[Optional[str]] = mapped_column(String(3)) # PAISOPCIONAL
origin_procedure: Mapped[Optional[str]] = mapped_column(String(3)) # PROCEDENCIA
scrap_procedure: Mapped[Optional[str]] = mapped_column(String(3)) # PROCSCRAP
# Sector
sector: Mapped[Optional[str]] = mapped_column(String(8)) # SECTOR
# Relationship
item_line: Mapped["LineItem"] = relationship()
class LineDescription(Base):
"""
Description details for line items
Consolidates all line-level data from Q and S tables
"""
__tablename__ = "line_descriptions"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
item_line_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id"))
# Descriptions
description_spanish: Mapped[Optional[str]] = mapped_column(String(4999)) # DESCRIPCIONE
description_english: Mapped[Optional[str]] = mapped_column(String(4999)) # DESCRIPCIONI
extra_description: Mapped[Optional[str]] = mapped_column(Text) # DESCRIPCIONEEXTRA
part_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONPARTE
class_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONCLASE
# Product attributes
brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCA
model: Mapped[Optional[str]] = mapped_column(String(50)) # MODELO
has_serial: Mapped[Optional[bool]] = mapped_column(Boolean) # LLEVASERIE
# Additional information
additional_info_spanish: Mapped[Optional[str]] = mapped_column(String(1000)) # INFOADICIONESP
additional_info_english: Mapped[Optional[str]] = mapped_column(String(1000)) # INFOADICIONING
# Lot and entry tracking
lot: Mapped[Optional[str]] = mapped_column(String(254)) # LOTE
entry_number: Mapped[Optional[str]] = mapped_column(String(50)) # NUMENTRADA/NUMERODEENTRADA
# Relationship
item_line: Mapped["LineItem"] = relationship()
class LineReference(Base):
"""
Reference details for line items
Consolidates all line-level data from Q and S tables
"""
__tablename__ = "line_references"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
item_line_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id"))
serie_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.item_line_series.id")) # SERIEPARTIDA
# Customer/Vendor
customer_invoice: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # CLIENTEFACTURAR
assigned_client: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # CLIENTEASIGNADO
supplier: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # PROVEEDOR
requisitioner: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # REQUISITOR
sent_to: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # ENVIADOA
# PED line reference
ped_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEAPED
ro_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEARO
# Relationship
item_line: Mapped["LineItem"] = relationship()
# ============================================================================
# SUPPORTING TABLES
# ============================================================================
class PackingList(Base):
"""
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 RepairPart(Base):
"""
Repair parts (orphan table without primary key in original)
From: SPartidasRep
"""
__tablename__ = "repair_parts"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
import_line: Mapped[int] = mapped_column(Integer) # LINEAIMPO
class CTMShipment(Base):
"""
CTM Shipment lines (temporary manufacturing)
From: SPartidasEnviaCTM
"""
__tablename__ = "ctm_shipments"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
shipment_line: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id")) # LINEAENVIO
brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCA
model: Mapped[Optional[str]] = mapped_column(String(50)) # MODELO
serial_numbers: Mapped[Optional[str]] = mapped_column(String(50)) # SERIES
class CTMReceipt(Base):
"""
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):
"""
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
class SubassemblyExit(Base):
"""
Subassembly/Submanufacturing Exit lines
From: SPartidasSalidaSM
"""
__tablename__ = "subassembly_exits"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
exit_line: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id")) # LINEASALIDA
class ImpositionPart(Base):
"""
Imposition parts (orphan table without primary key constraint)
From: SPartidasImposion
"""
__tablename__ = "imposition_parts"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
import_line: Mapped[int] = mapped_column(Integer) # LINEAIMPO
# ============================================================================
# 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)
)
```
"""

View File

@@ -0,0 +1,23 @@
from typing import Optional
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, Numeric, String
from sqlalchemy.orm import Mapped, mapped_column
from core.database import Base
class Serie(Base):
__tablename__ = "item_line_series"
__table_args__ = {
"schema": "a76",
}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
line_item_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id")) # LINEAIMPO / LINEAEXPO
row: Mapped[int] = mapped_column(Integer) # RENGLON
serial_numbers: Mapped[Optional[str]] = mapped_column(String(50)) # SERIEEXPO
model: Mapped[Optional[str]] = mapped_column(String(50)) # MODELOEXPO
sub_model: Mapped[Optional[str]] = mapped_column(String(50)) # SUBMODELOEXPO
brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCA
expo_brad: Mapped[Optional[str]] = mapped_column(String(50)) # MARCAEXPO
number_id: Mapped[Optional[str]] = mapped_column(String(25)) # NUMIDEXPO

View File

@@ -9,12 +9,10 @@ from typing import TYPE_CHECKING, Optional
from api.v1.common.base_models import TenantScopedMixin, TimestampMixin
from core.database import Base
from sqlalchemy import (
ForeignKey,
ForeignKeyConstraint,
Integer,
Numeric,
PrimaryKeyConstraint,
SmallInteger,
String,
UniqueConstraint,
Boolean,
@@ -57,7 +55,7 @@ class Part(Base, TenantScopedMixin, TimestampMixin):
# Unique constraint compuesta
client_id: Mapped[int] = mapped_column(Integer)
part_number: Mapped[str] = mapped_column(String(49))
part_number: Mapped[str] = mapped_column(String(50))
# Basic information
fraction: Mapped[Optional[str]] = mapped_column(String(10))

View File

@@ -16,6 +16,9 @@ from core.middleware import (
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from api.v1.modules.a76.items.models import Item # Importar rutas para registrar con el router
from api.v1.modules.a76.items.series.models import Serie # Importar modelos para registrar con SQLAlchemy
# Configurar logging
logging.basicConfig(
level=logging.INFO if not settings.DEBUG else logging.DEBUG,

View File

@@ -1,14 +1,13 @@
services:
# PostgreSQL - Base de datos core (app)
postgres-a76:
image: postgres:16-alpine
image: postgres:18-alpine
container_name: anexo76-postgres-a76
environment:
POSTGRES_DB: anexo76_core
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_APP_PASSWORD:-postgres}
POSTGRES_INITDB_ARGS: "--encoding=UTF8"
PGDATA: /var/lib/postgresql/data/pgdata
ports:
- "5432:5432"
volumes:
@@ -38,14 +37,13 @@ services:
# PostgreSQL - Base de datos Keycloak
postgres-keycloak:
image: postgres:16-alpine
image: postgres:18-alpine
container_name: anexo76-postgres-keycloak
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_KEYCLOAK_PASSWORD:-postgres}
POSTGRES_INITDB_ARGS: "--encoding=UTF8"
PGDATA: /var/lib/postgresql/data/pgdata
ports:
- "5433:5432"
volumes: