Merge branch 'feature/facturas' into development

This commit is contained in:
AlexeerCT
2025-12-24 15:07:10 -06:00
49 changed files with 4899 additions and 6443 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

@@ -10,74 +10,98 @@ from ....common.base_models import TenantScopedMixin, TimestampMixin
class OperationType(str, Enum):
IMP = "imp" # Importación
EXP = "exp" # Exportación
SM_IN = "sm_in" # Entrada SM
SM_OUT = "sm_out" # Salida SM
CTM_SEND = "ctm_send" # Envío CTM
CTM_RECEIVE = "ctm_receive" # Recibo CTM
class TransportType(str, Enum):
NONE = "none" # Ninguno
TRANSPORT = "transport" # Transporte
BOX = "box" # Caja
PLATES = "licence plates" # Placas
TRUCK = "truck" # Camión
VESSEL = "vessel" # Buque
BARGE = "rail barge" # Ferrobarcaza
CONTAINER = "container" # Contenedor
AIRPLANE = "airplane" # Avión
GONDOLA = "gondola" # Góndola
FLATBED = "flatbed" # Plataforma
NONE = "none"
TRANSPORT = "transport"
BOX = "box"
PLATES = "licence plates"
TRUCK = "truck"
VESSEL = "vessel"
BARGE = "rail barge"
CONTAINER = "container"
AIRPLANE = "airplane"
GONDOLA = "gondola"
FLATBED = "flatbed"
# --- 1. Invoice Header (invoice_header) ---
class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_header"
__table_args__ = (
{"schema": "a76"},
)
__table_args__ = ({"schema": "a76"},)
id: Mapped[int] = mapped_column(
BigInteger, primary_key=True, autoincrement=True)
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
# Identifiers
operation_type: Mapped[OperationType] = mapped_column(
String(3)) # TIPOMOVIMIENTO / Clasifica imp/exp
invoice_type: Mapped[Optional[str]] = mapped_column(ForeignKey(
"public.invoice_types.key")) # TIPOFACTURA (invoice_types)
invoice_number: Mapped[Optional[str]] = mapped_column(
String(20)) # FACTURAIMPO/FACTURAEXPO/FACTURAREMISION
project_number: Mapped[Optional[str]] = mapped_column(
String(14)) # NUMPROYECTO
purchase_order: Mapped[Optional[str]] = mapped_column(
String(50)) # ORDENCOMPRA
related_doc_id: Mapped[Optional[int]] = mapped_column(
Integer) # IDRELDOC (Para Rectificaciones)
system: Mapped[Optional[str]] = mapped_column(String(10)) # SISTEMA / Sistema de origen <-- no tiene campo en la antigua base de datos, sera para fixed-asset(scaf), inventory(scaii)
operation_type: Mapped[OperationType] = mapped_column(String(10)) # TIPOMOVIMIENTO / Clasifica imp/exp/sm/ctm
invoice_type: Mapped[Optional[str]] = mapped_column(ForeignKey("public.invoice_types.key")) # TIPOFACTURA / TIPODOC
invoice_number: Mapped[Optional[str]] = mapped_column(String(20)) # FACTURAIMPO/FACTURAEXPO/FACTURAREMISION/FACTURAENVIO/FACTURASALIDA
project_number: Mapped[Optional[str]] = mapped_column(String(14)) # NUMPROYECTO
purchase_order: Mapped[Optional[str]] = mapped_column(String(50)) # ORDENCOMPRA
related_doc_id: Mapped[Optional[int]] = mapped_column(Integer) # IDRELDOC / Para Rectificaciones
alternate_invoice: Mapped[Optional[str]] = mapped_column(String(99)) # FACTURAALTERNA
invoice_ref: Mapped[Optional[str]] = mapped_column(String(19)) # FACTURAEXPOREF
proforma_number: Mapped[Optional[str]] = mapped_column(String(20)) # NUMEROPROFORMA
# Dates
invoice_date: Mapped[Optional[datetime]
] = mapped_column(Date) # FECHAFACTURA
capture_date: Mapped[datetime] = mapped_column(
TIMESTAMP(timezone=False), default=datetime.now) # FECHACAPTURA + HORAACTUAL
invoice_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAFACTURA
capture_date: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=False), default=datetime.now) # FECHACAPTURA + HORAACTUAL
emission_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAEMISION
# Status & Control
is_updated: Mapped[Optional[str]] = mapped_column(Boolean) # ESTATUS
updated_date: Mapped[Optional[datetime]] = mapped_column(TIMESTAMP(timezone=False)) # FECHAACTUALIZACION
who_updated: Mapped[Optional[str]] = mapped_column(String(20)) # QUIENA ACTUALIZO
is_updated: Mapped[Optional[bool]] = mapped_column(Boolean) # ESTATUS
updated_date: Mapped[Optional[datetime]] = mapped_column(TIMESTAMP(timezone=False)) # FECHAACTUALIZACION / FECHAACTUAL
who_updated: Mapped[Optional[str]] = mapped_column(String(20)) # USUARIOACT / Quien actualizó
capture_user: Mapped[Optional[str]] = mapped_column(String(20)) # USUARIOCAP / Usuario que capturó
traffic_light_status: Mapped[Optional[str]] = mapped_column(String(50)) # SEMAFORO / SEMAFOROEXPO/IMPO
process_log: Mapped[Optional[str]] = mapped_column(String(300)) # COMOFUEPROCESADA
status_rec: Mapped[Optional[int]] = mapped_column(Integer) # ESTATUSREC / Estatus de recepción
status_rep: Mapped[Optional[str]] = mapped_column(String(2)) # ESTATUSREP / Estatus de reporte
# Comments
observation_es: Mapped[Optional[str]] = mapped_column(Text) #OBSERVACIONE
observation_en: Mapped[Optional[str]] = mapped_column(Text) #OBSERVACIONI
comments_status: Mapped[Optional[str]] = mapped_column(Text) #COMENTARIOSESTATUS
# Comments
observation_es: Mapped[Optional[str]] = mapped_column(Text) # OBSERVACIONE / Observaciones en español
observation_en: Mapped[Optional[str]] = mapped_column(Text) # OBSERVACIONI / Observaciones en inglés
comments_status: Mapped[Optional[str]] = mapped_column(Text) # COMENTARIOSESTATUS
vu_observations: Mapped[Optional[str]] = mapped_column(String(500)) # OBSERVACIONESVU / Observaciones VUCEM
# Digital Archive Links
cfdi_uuid: Mapped[Optional[str]] = mapped_column(String(100)) # CFDIUUID
path_pdf: Mapped[Optional[str]] = mapped_column(String(500)) # CFDIPATHPDF
path_xml: Mapped[Optional[str]] = mapped_column(String(500)) # CFDIPATHXML
# Relationships (Para navegacion ORM)
compliance_mx: Mapped["InvoiceComplianceMx"] = relationship(
back_populates="header", cascade="all, delete-orphan")
financials: Mapped["InvoiceFinancials"] = relationship(
back_populates="header", cascade="all, delete-orphan")
# Control & Subcompany
subcompany: Mapped[Optional[str]] = mapped_column(String(5)) # SUBEMPRESA
party_count: Mapped[Optional[int]] = mapped_column(Integer) # CANT_PARTIDAS / Cantidad de partidas
# Generation flags
generate_id: Mapped[Optional[str]] = mapped_column(String(1)) # GENERAID
generate_desc_parties: Mapped[Optional[str]] = mapped_column(String(12)) # GENDESCPARTIDAS / Generar descripción de partidas
apply_manual_discount: Mapped[Optional[str]] = mapped_column(String(1)) # APLICADESCMANUAL
# Bulk & Downloads
is_bulk: Mapped[Optional[bool]] = mapped_column(Boolean) # ESAGRANEL / Es a granel
download_substance: Mapped[Optional[bool]] = mapped_column(Boolean) # DESCARGASUST / Descarga de sustancia
download_class: Mapped[Optional[bool]] = mapped_column(Boolean) # DESCARGACLASE / Descarga de clase
download_def: Mapped[Optional[bool]] = mapped_column(Boolean) # DESCARGADEF / Descarga definitiva
# Additional fields
payment_terms: Mapped[Optional[str]] = mapped_column(String(200)) # TERMINOSPAGO / Términos de pago
handling_fees: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # MANIOBRAS
option_iv18: Mapped[Optional[str]] = mapped_column(String(50)) # OPCIONIV18
enajenation_goods: Mapped[Optional[bool]] = mapped_column(Boolean) # ENAJENACIONBIENES / Enajenación de bienes
# Relationships
compliance_mx: Mapped[Optional["InvoiceComplianceMx"]] = relationship(
back_populates="header", cascade="all, delete-orphan", uselist=False)
financials: Mapped[Optional["InvoiceFinancials"]] = relationship(
back_populates="header", cascade="all, delete-orphan", uselist=False)
details: Mapped[List["InvoiceSalesDetails"]] = relationship(
back_populates="header", cascade="all, delete-orphan")
collections: Mapped[List["InvoiceCollections"]] = relationship(
@@ -89,79 +113,150 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
# --- 2. Compliance MX (invoice_compliance_mx) ---
class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_compliance_mx"
__table_args__ = (
{"schema": "a76"},
)
__table_args__ = ({"schema": "a76"},)
invoice_id: Mapped[int] = mapped_column(
ForeignKey("a76.invoice_header.id"), primary_key=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id"), primary_key=True)
# Core Customs Data
pedimento: Mapped[Optional[str]] = mapped_column(
String(19)) # PEDIMENTO/PEDIMENTOIMPO/EXPO
pedimento_code: Mapped[Optional[str]] = mapped_column(
String(5)) # PEDIMENTOR1, K1
remesa: Mapped[Optional[int]] = mapped_column(Integer)
pedimento: Mapped[Optional[str]] = mapped_column(String(19)) # PEDIMENTO/PEDIMENTOIMPO/EXPO
pedimento_code: Mapped[Optional[str]] = mapped_column(String(5)) # PEDIMENTOR1
pedimento_k1: Mapped[Optional[str]] = mapped_column(String(15)) # PEDIMENTOK1
remesa: Mapped[Optional[int]] = mapped_column(Integer) # REMESA
aduana: Mapped[Optional[str]] = mapped_column(ForeignKey("public.customs_sections.customs_code")) # ADUANA_CRUCE
port_of_entry: Mapped[Optional[str]] = mapped_column(String(6)) # PUERTOENTRADA / Puerto de entrada
destination: Mapped[Optional[str]] = mapped_column(String(3)) # DESTINO / Código de destino
manifest_number: Mapped[Optional[str]] = mapped_column(String(15)) # MANIFIESTO / Número de manifiesto
# Clients & Providers
provider_header: Mapped[Optional[str]] = mapped_column(String(20)) # PROVEEDOREXPORTADOR
provider_header: Mapped[Optional[str]] = mapped_column(String(20)) # PROVEEDOREXPORTADOR
provider_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # PROVEEDOR
sold_to_header: Mapped[Optional[str]] = mapped_column(String(20)) # VENDIDOCONSIGNADO
sold_to_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOA
shipped_to_header: Mapped[Optional[str]] = mapped_column(String(20)) # ENVIADOTRANSFERIDO
shipped_to_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # ENVIADOA
shipped_to_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # ENVIADOA
shipped_by_header: Mapped[Optional[str]] = mapped_column(String(20)) # ENVIADOPORVENDIDOPOR
shipped_by_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOPOR
shipped_by_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOPOR/ENVIADOPOR
customs_broker_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANAL / Agente aduanal
customs_broker_us_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANALAME / Agente aduanal americano
customs_broker_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANAL
# Broker Invoice
broker_invoice_num: Mapped[Optional[str]] = mapped_column(String(20)) # NUMFACTURABROKER / Número factura broker
broker_invoice_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAFACBROKER / Fecha factura broker
# Flags & Specific Regimes
is_mixed: Mapped[Optional[bool]] = mapped_column(Boolean) # ESMIXTO
waste_type: Mapped[Optional[str]] = mapped_column(String(1)) # TIPODESPERDICIO
appendix_17: Mapped[Optional[int]] = mapped_column(Integer) # APENDICE17
is_mixed: Mapped[Optional[bool]] = mapped_column(Boolean) # ESMIXTO / Es mixto
waste_type: Mapped[Optional[str]] = mapped_column(String(1)) # TIPODESPERDICIO / Tipo de desperdicio
scrap_type: Mapped[Optional[str]] = mapped_column(String(1)) # TIPOSCRAP / Tipo de scrap
appendix_17: Mapped[Optional[int]] = mapped_column(Integer) # APENDICE17 / Apéndice 17
is_regime_change: Mapped[Optional[str]] = mapped_column(String(1)) # ESCAMBIOREGIMEN / Es cambio de régimen
which_exchange_rate: Mapped[Optional[str]] = mapped_column(String(5)) # CUALTIPOCAMBIO / Cuál tipo de cambio
value_method: Mapped[Optional[str]] = mapped_column(String(2)) # METVALOR / Método de valoración
act_value: Mapped[Optional[str]] = mapped_column(String(5)) # ACTVALOR / Actualizar valor
is_pedimento_pending: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # PED_PENDIENTE_ASIGNAR (Mapear 1 -> True, 0 -> False)
# Ownership & Balances
is_owner_of_goods: Mapped[Optional[str]] = mapped_column(String(2)) # ESDUENOMCIA / Es dueño de mercancía
generate_balances: Mapped[Optional[str]] = mapped_column(String(2)) # GENERARSALDOS / Generar saldos
was_reviewed_by_company: Mapped[Optional[bool]] = mapped_column(Boolean) # FUEREVISADAMCIA / Fue revisada por la compañía
# VUCEM / Digital
edocument: Mapped[Optional[str]] = mapped_column(String(50)) # EDOCUMENT
electronic_signature: Mapped[Optional[str]] = mapped_column(String(999)) # FIRMAELECTRONICA
sem_id: Mapped[Optional[int]] = mapped_column(Integer) # SEM (de SFacEntradaSM/SFacSalidaSM)
edocument: Mapped[Optional[str]] = mapped_column(String(50)) # EDOCUMENT / Documento electrónico
electronic_signature: Mapped[Optional[str]] = mapped_column(String(999)) # FIRMAELECTRONICA / Firma electrónica
certificate_number: Mapped[Optional[str]] = mapped_column(String(99)) # NUMEROCERTIFICADO / Número de certificado
niu_number: Mapped[Optional[str]] = mapped_column(String(19)) # NUMERONIU / Número NIU
bill_of_lading_count: Mapped[Optional[str]] = mapped_column(String(12)) # CANTGUIASEMBARQUE / Cantidad guías embarque
addendum_vu: Mapped[Optional[str]] = mapped_column(String(204)) # ADENDAVU / Adenda VUCEM
origin_destination_cove: Mapped[Optional[str]] = mapped_column(String(19)) # DESTINOORIGENCOVE / Destino/Origen COVE
vucem_operation_num: Mapped[Optional[str]] = mapped_column(String(19)) # NUMOPERACIONVU / Número operación VUCEM
customs_person_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEAPERSONAAA / Línea persona agente aduanal
# Additional Control
contingency_mode: Mapped[Optional[bool]] = mapped_column(Boolean) # MODOCONTINGENCIA / Modo contingencia
enclosure: Mapped[Optional[str]] = mapped_column(String(4)) # RECINTO / Recinto fiscal
guide_type_to_identify: Mapped[Optional[str]] = mapped_column(String(1)) # TIPODEGUIAAIDENTIFICAR / Tipo de guía a identificar
location: Mapped[Optional[str]] = mapped_column(String(200)) # LOCALIZACION / Localización
# DOT & Official
dot_code: Mapped[Optional[str]] = mapped_column(String(20)) # CLAVEDOT / Clave DOT
subdivision: Mapped[Optional[str]] = mapped_column(String(20)) # SUBDIVISION / Subdivisión
acts_as: Mapped[Optional[str]] = mapped_column(String(20)) # FUNGECOMOCO / Funge como
movement_type: Mapped[Optional[str]] = mapped_column(String(31)) # TIPOMOV / Tipo de movimiento
office_document: Mapped[Optional[str]] = mapped_column(String(30)) # OFICIO / Oficio
reason_export: Mapped[Optional[str]] = mapped_column(String(1)) # RAZONEXPORTACION / Razón de exportación
signature_key: Mapped[Optional[str]] = mapped_column(String(10)) # CLAVEFIRMA / Clave de firma
# SM specific
sem_id: Mapped[Optional[int]] = mapped_column(Integer) # SEM / ID SEM (de SFacEntradaSM/SFacSalidaSM)
# Relationship
header: Mapped["InvoiceHeader"] = relationship(
back_populates="compliance_mx")
header: Mapped["InvoiceHeader"] = relationship(back_populates="compliance_mx")
# --- 3. Financials (invoice_financials) ---
class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_financials"
__table_args__ = (
{"schema": "a76"},
)
__table_args__ = ({"schema": "a76"},)
id: Mapped[int] = mapped_column(
BigInteger, primary_key=True, autoincrement=True)
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id"))
currency: Mapped[Optional[str]] = mapped_column(String(3)) # CLAVEMONEDA
currency_type: Mapped[Optional[str]] = mapped_column(ForeignKey("public.currency_types.code")) # TIPOCLAVEMONEDA
exchange_rate: Mapped[Optional[float]] = mapped_column(Numeric(13, 6)) # TIPOCAMBIO
# Currency
currency: Mapped[Optional[str]] = mapped_column(String(3)) # CLAVEMONEDA / Clave de moneda
currency_type: Mapped[Optional[str]] = mapped_column(ForeignKey("public.currency_types.code")) # TIPOMONEDA / TIPOCLAVEMONEDA
exchange_rate: Mapped[Optional[float]] = mapped_column(Numeric(13, 6)) # TIPOCAMBIO / Tipo de cambio
exchange_rate_mm: Mapped[Optional[float]] = mapped_column(Numeric(13, 6)) # TIPOCAMBIOMM / Tipo de cambio moneda a moneda
# Merchandise Values
value_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORIMPOMN/EXPOMN
value_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0)
customs_value_mn: Mapped[Optional[float]] = mapped_column( Numeric(23, 8), default=0) # VALORADUANASMN
# Merchandise Values (MN = National Currency, ME = Foreign Currency, MC = Third Currency)
value_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORIMPOMN/VALOREXPOMN/VALORENTMN/VALORSALMN
value_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORIMPOME/VALOREXPOME/VALORENTME/VALORSALME
value_mc: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORIMPOMC/VALOREXPOMC
# Customs Value
customs_value_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORADUANASMN / Valor en aduanas MN
customs_value_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORADUANASME / Valor en aduanas ME
# Costs & Taxes
freight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # FLETE
insurance: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # VALSEGUROS/SEGUROS
iva_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # IVAEXPOMN / VALORIVAMN
iva_factor: Mapped[Optional[float]] = mapped_column(Numeric(17, 4)) # FACTORIVA
# Raw Materials
raw_material_value_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORMPMN / Valor materia prima MN
raw_material_value_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORMPME / Valor materia prima ME
# Aggregate Value
aggregate_value_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORAGREMN / Valor agregado MN
aggregate_value_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORAGREME / Valor agregado ME
aggregate_value_mc: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORAGREMC / Valor agregado MC
# Mexican Merchandise Value
mexican_value_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORVMEXMN / Valor mercancía mexicana MN
mexican_value_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORVMEXME / Valor mercancía mexicana ME
mexican_value_mc: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORVMEXMC / Valor mercancía mexicana MC
# National Packaging
national_packaging_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALEMPAQUENACMN / Valor empaque nacional MN
national_packaging_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALEMPAQUENACME / Valor empaque nacional ME
national_packaging_mc: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALEMPAQUENACMC / Valor empaque nacional MC
# Costs & Increments
freight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # FLETE / Flete
insurance: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # SEGUROS / Seguros
insurance_value: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # VALSEGUROS / Valor seguros
packaging: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # EMBALAJES / Embalajes
other_increments: Mapped[Optional[float]] = mapped_column(Numeric(19, 8), default=0) # OTROSINCREMENTA / Otros incrementables
total_increments_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # TOTALINCREMMN / Total incrementables MN
total_increments_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # TOTALINCREMME / Total incrementables ME
# Taxes
iva_mn: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # IVAEXPOMN/VALORIVAMN / IVA en MN
iva_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # IVAEXPOME/VALORIVAME / IVA en ME
iva_mc: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # IVAEXPOMC / IVA en MC
iva_factor: Mapped[Optional[str]] = mapped_column(String(10)) # FACTORIVA / Factor IVA (puede ser varchar en imports)
tax_value_me: Mapped[Optional[float]] = mapped_column(Numeric(23, 8), default=0) # VALORIMPUESTOME / Valor impuesto ME
seal_value_2500: Mapped[Optional[bool]] = mapped_column(Boolean) # SELLOVALOR2500 / Sello valor 2500
# Weights & Quantities
total_quantity: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # CANTEXPO / CANTIMPO
gross_weight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # PESOBRUTO
net_weight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # PESONETO
bundle_count: Mapped[Optional[int]] = mapped_column(Integer) # CANTBULTOS
total_quantity: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # CANTEXPO/CANTIMPO / Cantidad total
gross_weight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # PESOBRUTO / Peso bruto
net_weight: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # PESONETO / Peso neto
bundle_count: Mapped[Optional[int]] = mapped_column(Integer) # CANTBULTOS / Cantidad de bultos
weight_factor: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # FACTORPESO / Factor de peso
# Relationship
header: Mapped["InvoiceHeader"] = relationship(back_populates="financials")
@@ -170,38 +265,67 @@ class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin):
# --- 4. Logistics (invoice_logistics) ---
class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_logistics"
__table_args__ = (
{"schema": "a76"},
)
__table_args__ = ({"schema": "a76"},)
id: Mapped[int] = mapped_column(
BigInteger, primary_key=True, autoincrement=True)
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id"))
# Carrier Info
carrier_id: Mapped[Optional[str]] = mapped_column(
String(10)) # TRANSPORTISTA
transport_type: Mapped[TransportType] = mapped_column(
String(15), default="none") # TRANSPORTE
transport_mode: Mapped[Optional[str]] = mapped_column(
String(15)) # MODTRANS
driver_name: Mapped[Optional[str]] = mapped_column(String(80)) # CONDUCTOR
is_rail: Mapped[Optional[str]] = mapped_column(String(2)) # ESFERROCARRIL
rail_id: Mapped[Optional[str]] = mapped_column(
String(31)) # IDFERRORCARRIL
# Carrier Info
carrier_id: Mapped[Optional[str]] = mapped_column(String(10)) # TRANSPORTISTA / Transportista
transport_id: Mapped[Optional[str]] = mapped_column(String(10)) # NUMTRAILER / Transportista
transport_us_id: Mapped[Optional[str]] = mapped_column(String(10)) # TRANSPORTISTAAME / Transportista americano
transport_type: Mapped[TransportType] = mapped_column(String(15), default="none") # TRANSPORTE / Tipo de transporte
transport_num: Mapped[Optional[str]] = mapped_column(String(20)) # NUMTRASPORTE / Número de transporte
transport_mode: Mapped[Optional[str]] = mapped_column(String(15)) # MODTRANS / Modo de transporte
driver_name: Mapped[Optional[str]] = mapped_column(String(80)) # CONDUCTOR / Nombre del conductor
is_rail: Mapped[Optional[str]] = mapped_column(String(2)) # ESFERROCARRIL / Es ferrocarril
rail_id: Mapped[Optional[str]] = mapped_column(String(31)) # IDFERRORCARRIL / ID ferrocarril
# Vehicle & Tracking
vehicle_num: Mapped[Optional[str]] = mapped_column(
String(20)) # NUMVEHICULO / NUMTRAILER
license_plate: Mapped[Optional[str]] = mapped_column(
String(20)) # NUMTRASPORTE
seal_number: Mapped[Optional[str]] = mapped_column(String(15)) # PRECINTO
guide_number: Mapped[Optional[str]] = mapped_column(
String(20)) # NUMEROGUIA
vehicle_num: Mapped[Optional[str]] = mapped_column(String(20)) # NUMVEHICULO / Número de vehículo
license_plate: Mapped[Optional[str]] = mapped_column(String(20)) # NUMTRASPORTE / Número de transporte/placa
license_plate_complete: Mapped[Optional[str]] = mapped_column(String(40)) # NUMTRASPORTECOMPLE / Número transporte completo
trailer_num: Mapped[Optional[str]] = mapped_column(String(20)) # NUMTRAILER / Número de trailer
seal_number: Mapped[Optional[str]] = mapped_column(String(15)) # PRECINTO / Precinto
guide_number: Mapped[Optional[str]] = mapped_column(String(20)) # NUMEROGUIA / Número de guía
bill_number: Mapped[Optional[str]] = mapped_column(String(15)) # BILLNUMBER / Número de bill
reference_number: Mapped[Optional[str]] = mapped_column(String(14)) # NUMREFERENCIA / Número de referencia
shipment_number: Mapped[Optional[str]] = mapped_column(String(19)) # NUMEMBARQUE / Número de embarque
# Incoterms
incoterm: Mapped[Optional[str]] = mapped_column(String(5)) # INCOTERM / Término de comercio internacional
# Identifiers & Complements
identifier_1: Mapped[Optional[str]] = mapped_column(String(2)) # IDENTIFICADOR / Identificador 1
complement_1: Mapped[Optional[str]] = mapped_column(String(30)) # COMPLEMENTO1 / Complemento 1
identifier_2: Mapped[Optional[str]] = mapped_column(String(2)) # IDENTIFICADOR2 / Identificador 2
complement_2: Mapped[Optional[str]] = mapped_column(String(30)) # COMPLEMENTO2 / Complemento 2
# Weight & Container Info
weight_type: Mapped[Optional[str]] = mapped_column(String(6)) # TIPOPESO / Tipo de peso
container_types: Mapped[Optional[str]] = mapped_column(String(500)) # CONTENEDORESTIPO / Tipos de contenedores
vehicle_data: Mapped[Optional[str]] = mapped_column(String(500)) # DATOSVEHICULO / Datos del vehículo
# Locations & Routes
origin_location: Mapped[Optional[str]] = mapped_column(String(200)) # ORIGENUBICACION / Ubicación de origen
destination_location: Mapped[Optional[str]] = mapped_column(String(200)) # DESTINOUBICACION / Ubicación de destino
transport_itinerary: Mapped[Optional[str]] = mapped_column(String(1000)) # ITINERARIOTRANPORTE / Itinerario del transporte
destination_goods: Mapped[Optional[str]] = mapped_column(String(50)) # DESTINOMCIA / Destino de mercancía
# Logistics Dates
entry_exit_date: Mapped[Optional[datetime]] = mapped_column(
Date) # FECHAENTRADA / FECHAENVIO
entry_exit_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAENTRADA/FECHAENVIO/FECHARECIBO / Fecha entrada/salida
delivery_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAENTREGA / Fecha de entrega
# Delivery Control
delivered_status: Mapped[Optional[str]] = mapped_column(String(2)) # ENTREGADO / Estado de entrega
received_by: Mapped[Optional[str]] = mapped_column(String(50)) # RECIBIDOPOR / Recibido por
# Payment Info
payment_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAPAGO / Fecha de pago
payment_receipt_num: Mapped[Optional[str]] = mapped_column(String(20)) # NUMRECIBOPAGO / Número de recibo de pago
# CTM Process
is_ctm_process: Mapped[Optional[str]] = mapped_column(String(2)) # SETRATAPROCESOCTM / Se trata de proceso CTM
# Relationship
header: Mapped["InvoiceHeader"] = relationship(back_populates="logistics")
@@ -210,25 +334,18 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
# --- 5. Sales Order Details (invoice_sales_details) ---
class InvoiceSalesDetails(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_sales_details"
__table_args__ = (
{"schema": "a76"},
)
__table_args__ = ({"schema": "a76"},)
id: Mapped[int] = mapped_column(
BigInteger, primary_key=True, autoincrement=True)
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id"))
line_number: Mapped[int] = mapped_column(Integer) # LINEA
sales_order: Mapped[Optional[str]] = mapped_column(
String(20)) # ORDENVENTA
line_number: Mapped[int] = mapped_column(Integer) # LINEA / Número de línea
sales_order: Mapped[Optional[str]] = mapped_column(String(20)) # ORDENVENTA / Orden de venta
# Specific Custom Fields
colors_description: Mapped[Optional[str]
] = mapped_column(String(49)) # COLORES
square_color_code: Mapped[Optional[str]] = mapped_column(
String(1)) # COLORCUADRITO
line_bundles: Mapped[Optional[int]] = mapped_column(
Integer) # CANTBULTOS (de la linea)
colors_description: Mapped[Optional[str]] = mapped_column(String(49)) # COLORES / Descripción de colores
square_color_code: Mapped[Optional[str]] = mapped_column(String(1)) # COLORCUADRITO / Código de color cuadrito
line_bundles: Mapped[Optional[int]] = mapped_column(Integer) # CANTBULTOS / Cantidad de bultos de la línea
# Relationship
header: Mapped["InvoiceHeader"] = relationship(back_populates="details")
@@ -237,23 +354,15 @@ class InvoiceSalesDetails(Base, TenantScopedMixin, TimestampMixin):
# --- 6. Collections (invoice_collections) ---
class InvoiceCollections(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_collections"
__table_args__ = (
{"schema": "a76"},
)
__table_args__ = ({"schema": "a76"},)
id: Mapped[int] = mapped_column(
BigInteger, primary_key=True, autoincrement=True)
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id"))
concept: Mapped[Optional[str]] = mapped_column(String(100)) # CONCEPTO
is_collected: Mapped[Optional[int]] = mapped_column(Integer) # COBRADO
collection_date: Mapped[Optional[datetime]
] = mapped_column(Date) # FECHACOBRANZA
amount: Mapped[Optional[float]] = mapped_column(Numeric(23, 8)) # VALOR
collector_user: Mapped[Optional[str]] = mapped_column(
String(20)) # FACTCOBRADOR
line_number: Mapped[int] = mapped_column(Integer) # LINEA / Número de línea
invoice_number: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURA / Número de factura
concept: Mapped[Optional[str]] = mapped_column(String(100)) # CONCEPTO / Concepto
# Relationship
header: Mapped["InvoiceHeader"] = relationship(
back_populates="collections")
header: Mapped["InvoiceHeader"] = relationship(back_populates="collections")
concept: Mapped[Optional[str]] = mapped_column(String(100)) # CONCEPTO / Conce

View File

@@ -8,8 +8,10 @@ from .models import OperationType
# --- Base Schemas ---
class InvoiceHeaderBase(BaseModel):
"""Base fields for Invoice Header"""
system: Optional[str] = Field(
None, max_length=10, description="System of origin")
operation_type: Optional[OperationType] = Field(
None, max_length=20, description="Operation type: imp/exp")
None, max_length=10, description="Operation type: imp/exp/sm/ctm")
invoice_type: Optional[str] = Field(
None, max_length=5, description="Invoice type key")
invoice_number: Optional[str] = Field(
@@ -20,20 +22,64 @@ class InvoiceHeaderBase(BaseModel):
None, max_length=50, description="Purchase order")
related_doc_id: Optional[int] = Field(
None, description="Related document ID for rectifications")
alternate_invoice: Optional[str] = Field(
None, max_length=99, description="Alternate invoice")
invoice_ref: Optional[str] = Field(
None, max_length=19, description="Invoice reference")
proforma_number: Optional[str] = Field(
None, max_length=20, description="Proforma number")
invoice_date: Optional[date] = Field(None, description="Invoice date")
emission_date: Optional[date] = Field(None, description="Emission date")
is_updated: Optional[bool] = Field(None, description="Status")
updated_date: Optional[datetime] = Field(None, description="Update date")
who_updated: Optional[str] = Field(
None, max_length=20, description="Who updated")
capture_user: Optional[str] = Field(
None, max_length=20, description="Capture user")
traffic_light_status: Optional[str] = Field(
None, max_length=50, description="Traffic light status (SEMAFORO)")
None, max_length=50, description="Traffic light status")
process_log: Optional[str] = Field(
None, max_length=300, description="Processing log")
status_rec: Optional[int] = Field(None, description="Reception status")
status_rep: Optional[str] = Field(
None, max_length=2, description="Report status")
observation_es: Optional[str] = Field(
None, description="Observations in Spanish")
observation_en: Optional[str] = Field(
None, description="Observations in English")
comments_status: Optional[str] = Field(
None, description="Comments and observations")
None, description="Comments status")
vu_observations: Optional[str] = Field(
None, max_length=500, description="VUCEM observations")
cfdi_uuid: Optional[str] = Field(
None, max_length=100, description="CFDI UUID")
path_pdf: Optional[str] = Field(
None, max_length=500, description="Path to PDF file")
path_xml: Optional[str] = Field(
None, max_length=500, description="Path to XML file")
subcompany: Optional[str] = Field(
None, max_length=5, description="Subcompany")
party_count: Optional[int] = Field(None, description="Quantity of parties")
generate_id: Optional[str] = Field(
None, max_length=1, description="Generate ID")
generate_desc_parties: Optional[str] = Field(
None, max_length=12, description="Generate description of parties")
apply_manual_discount: Optional[str] = Field(
None, max_length=1, description="Apply manual discount")
is_bulk: Optional[bool] = Field(None, description="Is bulk")
download_substance: Optional[bool] = Field(
None, description="Download substance")
download_class: Optional[bool] = Field(
None, description="Download class")
download_def: Optional[bool] = Field(
None, description="Definitive download")
payment_terms: Optional[str] = Field(
None, max_length=200, description="Payment terms")
handling_fees: Optional[Decimal] = Field(None, description="Handling fees")
option_iv18: Optional[str] = Field(
None, max_length=50, description="Option IV18")
enajenation_goods: Optional[bool] = Field(
None, description="Enajenation of goods")
class InvoiceComplianceMxBase(BaseModel):
@@ -41,21 +87,105 @@ class InvoiceComplianceMxBase(BaseModel):
pedimento: Optional[str] = Field(
None, max_length=19, description="Pedimento number")
pedimento_code: Optional[str] = Field(
None, max_length=5, description="Pedimento code (R1/K1)")
None, max_length=5, description="Pedimento code (R1)")
pedimento_k1: Optional[str] = Field(
None, max_length=15, description="Pedimento K1")
remesa: Optional[int] = Field(None, description="Remesa")
aduana: Optional[str] = Field(
None, max_length=5, description="Customs office")
customs_agent: Optional[str] = Field(
None, max_length=10, description="Customs agent")
port_of_entry: Optional[str] = Field(
None, max_length=6, description="Port of entry")
destination: Optional[str] = Field(
None, max_length=3, description="Destination code")
manifest_number: Optional[str] = Field(
None, max_length=15, description="Manifest number")
provider_header: Optional[str] = Field(
None, max_length=20, description="Provider header")
provider_id: Optional[str] = Field(
None, description="Provider ID")
sold_to_header: Optional[str] = Field(
None, max_length=20, description="Sold to header")
sold_to_id: Optional[str] = Field(
None, description="Sold to ID")
shipped_to_header: Optional[str] = Field(
None, max_length=20, description="Shipped to header")
shipped_to_id: Optional[str] = Field(
None, description="Shipped to ID")
shipped_by_header: Optional[str] = Field(
None, max_length=20, description="Shipped by header")
shipped_by_id: Optional[str] = Field(
None, description="Shipped by ID")
customs_broker_id: Optional[str] = Field(
None, description="Customs broker ID")
customs_broker_us_id: Optional[str] = Field(
None, description="US customs broker ID")
broker_invoice_num: Optional[str] = Field(
None, max_length=20, description="Broker invoice number")
broker_invoice_date: Optional[date] = Field(
None, description="Broker invoice date")
is_mixed: Optional[bool] = Field(
None, description="Is mixed operation")
waste_type: Optional[str] = Field(
None, max_length=1, description="Waste type")
scrap_type: Optional[str] = Field(
None, max_length=1, description="Scrap type")
appendix_17: Optional[int] = Field(None, description="Appendix 17")
is_regime_change: Optional[str] = Field(
None, max_length=1, description="Is regime change")
which_exchange_rate: Optional[str] = Field(
None, max_length=5, description="Which exchange rate")
value_method: Optional[str] = Field(
None, max_length=2, description="Value method")
act_value: Optional[str] = Field(
None, max_length=5, description="Act value")
is_pedimento_pending: Optional[bool] = Field(
None, description="Is pedimento pending")
is_owner_of_goods: Optional[str] = Field(
None, max_length=2, description="Is owner of goods")
generate_balances: Optional[str] = Field(
None, max_length=2, description="Generate balances")
was_reviewed_by_company: Optional[bool] = Field(
None, description="Was reviewed by company")
edocument: Optional[str] = Field(
None, max_length=50, description="E-document")
electronic_signature: Optional[str] = Field(
None, max_length=999, description="Electronic signature")
certificate_number: Optional[str] = Field(
None, max_length=99, description="Certificate number")
niu_number: Optional[str] = Field(
None, max_length=19, description="NIU number")
bill_of_lading_count: Optional[str] = Field(
None, max_length=12, description="Bill of lading count")
addendum_vu: Optional[str] = Field(
None, max_length=204, description="VUCEM addendum")
origin_destination_cove: Optional[str] = Field(
None, max_length=19, description="Origin/Destination COVE")
vucem_operation_num: Optional[str] = Field(
None, max_length=19, description="VUCEM operation number")
customs_person_line: Optional[int] = Field(
None, description="Customs person line")
contingency_mode: Optional[bool] = Field(
None, description="Contingency mode")
enclosure: Optional[str] = Field(
None, max_length=4, description="Enclosure")
guide_type_to_identify: Optional[str] = Field(
None, max_length=1, description="Guide type to identify")
location: Optional[str] = Field(
None, max_length=200, description="Location")
dot_code: Optional[str] = Field(
None, max_length=20, description="DOT code")
subdivision: Optional[str] = Field(
None, max_length=20, description="Subdivision")
acts_as: Optional[str] = Field(
None, max_length=20, description="Acts as")
movement_type: Optional[str] = Field(
None, max_length=31, description="Movement type")
office_document: Optional[str] = Field(
None, max_length=30, description="Office document")
reason_export: Optional[str] = Field(
None, max_length=1, description="Reason for export")
signature_key: Optional[str] = Field(
None, max_length=10, description="Signature key")
sem_id: Optional[int] = Field(None, description="SEM ID")
@@ -63,44 +193,148 @@ class InvoiceFinancialsBase(BaseModel):
"""Base fields for Financials"""
currency: Optional[str] = Field(
None, max_length=3, description="Currency code")
currency_type: Optional[str] = Field(
None, description="Currency type")
exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate")
exchange_rate_mm: Optional[Decimal] = Field(
None, description="Exchange rate currency to currency")
value_mn: Optional[Decimal] = Field(None, description="Value in MXN")
value_me: Optional[Decimal] = Field(
None, description="Value in foreign currency")
value_mc: Optional[Decimal] = Field(
None, description="Value in third currency")
customs_value_mn: Optional[Decimal] = Field(
None, description="Customs value in MXN")
customs_value_me: Optional[Decimal] = Field(
None, description="Customs value in foreign currency")
raw_material_value_mn: Optional[Decimal] = Field(
None, description="Raw material value in MXN")
raw_material_value_me: Optional[Decimal] = Field(
None, description="Raw material value in foreign currency")
aggregate_value_mn: Optional[Decimal] = Field(
None, description="Aggregate value in MXN")
aggregate_value_me: Optional[Decimal] = Field(
None, description="Aggregate value in foreign currency")
aggregate_value_mc: Optional[Decimal] = Field(
None, description="Aggregate value in third currency")
mexican_value_mn: Optional[Decimal] = Field(
None, description="Mexican merchandise value in MXN")
mexican_value_me: Optional[Decimal] = Field(
None, description="Mexican merchandise value in foreign currency")
mexican_value_mc: Optional[Decimal] = Field(
None, description="Mexican merchandise value in third currency")
national_packaging_mn: Optional[Decimal] = Field(
None, description="National packaging in MXN")
national_packaging_me: Optional[Decimal] = Field(
None, description="National packaging in foreign currency")
national_packaging_mc: Optional[Decimal] = Field(
None, description="National packaging in third currency")
freight: Optional[Decimal] = Field(None, description="Freight cost")
insurance: Optional[Decimal] = Field(None, description="Insurance cost")
insurance_value: Optional[Decimal] = Field(
None, description="Insurance value")
packaging: Optional[Decimal] = Field(None, description="Packaging")
other_increments: Optional[Decimal] = Field(
None, description="Other increments")
total_increments_mn: Optional[Decimal] = Field(
None, description="Total increments in MXN")
total_increments_me: Optional[Decimal] = Field(
None, description="Total increments in foreign currency")
iva_mn: Optional[Decimal] = Field(None, description="IVA in MXN")
iva_factor: Optional[Decimal] = Field(None, description="IVA factor")
iva_me: Optional[Decimal] = Field(
None, description="IVA in foreign currency")
iva_mc: Optional[Decimal] = Field(
None, description="IVA in third currency")
iva_factor: Optional[str] = Field(
None, max_length=10, description="IVA factor")
tax_value_me: Optional[Decimal] = Field(
None, description="Tax value in foreign currency")
seal_value_2500: Optional[bool] = Field(
None, description="Seal value 2500")
total_quantity: Optional[Decimal] = Field(
None, description="Total quantity")
gross_weight: Optional[Decimal] = Field(None, description="Gross weight")
net_weight: Optional[Decimal] = Field(None, description="Net weight")
bundle_count: Optional[int] = Field(None, description="Bundle count")
weight_factor: Optional[Decimal] = Field(None, description="Weight factor")
class InvoiceLogisticsBase(BaseModel):
"""Base fields for Logistics"""
carrier_id: Optional[str] = Field(
None, max_length=10, description="Carrier ID")
transport_id: Optional[str] = Field(
None, max_length=10, description="Transport ID")
transport_us_id: Optional[str] = Field(
None, max_length=10, description="US transport ID")
transport_type: Optional[str] = Field(
None, max_length=15, description="Transport type")
transport_num: Optional[str] = Field(
None, max_length=20, description="Transport number")
transport_mode: Optional[str] = Field(
None, max_length=15, description="Transport mode")
driver_name: Optional[str] = Field(
None, max_length=80, description="Driver name")
is_rail: Optional[str] = Field(
None, max_length=2, description="Is rail transport")
rail_id: Optional[str] = Field(None, max_length=31, description="Rail ID")
rail_id: Optional[str] = Field(
None, max_length=31, description="Rail ID")
vehicle_num: Optional[str] = Field(
None, max_length=20, description="Vehicle number")
license_plate: Optional[str] = Field(
None, max_length=20, description="License plate")
license_plate_complete: Optional[str] = Field(
None, max_length=40, description="Complete license plate")
trailer_num: Optional[str] = Field(
None, max_length=20, description="Trailer number")
seal_number: Optional[str] = Field(
None, max_length=15, description="Seal number")
guide_number: Optional[str] = Field(
None, max_length=20, description="Guide number")
bill_number: Optional[str] = Field(
None, max_length=15, description="Bill number")
reference_number: Optional[str] = Field(
None, max_length=14, description="Reference number")
shipment_number: Optional[str] = Field(
None, max_length=19, description="Shipment number")
incoterm: Optional[str] = Field(
None, max_length=5, description="Incoterm")
identifier_1: Optional[str] = Field(
None, max_length=2, description="Identifier 1")
complement_1: Optional[str] = Field(
None, max_length=30, description="Complement 1")
identifier_2: Optional[str] = Field(
None, max_length=2, description="Identifier 2")
complement_2: Optional[str] = Field(
None, max_length=30, description="Complement 2")
weight_type: Optional[str] = Field(
None, max_length=6, description="Weight type")
container_types: Optional[str] = Field(
None, max_length=500, description="Container types")
vehicle_data: Optional[str] = Field(
None, max_length=500, description="Vehicle data")
origin_location: Optional[str] = Field(
None, max_length=200, description="Origin location")
destination_location: Optional[str] = Field(
None, max_length=200, description="Destination location")
transport_itinerary: Optional[str] = Field(
None, max_length=1000, description="Transport itinerary")
destination_goods: Optional[str] = Field(
None, max_length=50, description="Destination of goods")
entry_exit_date: Optional[date] = Field(
None, description="Entry/Exit date")
delivery_date: Optional[date] = Field(
None, description="Delivery date")
delivered_status: Optional[str] = Field(
None, max_length=2, description="Delivered status")
received_by: Optional[str] = Field(
None, max_length=50, description="Received by")
payment_date: Optional[date] = Field(
None, description="Payment date")
payment_receipt_num: Optional[str] = Field(
None, max_length=20, description="Payment receipt number")
is_ctm_process: Optional[str] = Field(
None, max_length=2, description="Is CTM process")
class InvoiceSalesDetailsBase(BaseModel):
@@ -117,13 +351,10 @@ class InvoiceSalesDetailsBase(BaseModel):
class InvoiceCollectionsBase(BaseModel):
"""Base fields for Collections"""
line_number: int = Field(..., description="Line number")
invoice_number: Optional[str] = Field(
None, max_length=15, description="Invoice number")
concept: Optional[str] = Field(None, max_length=100, description="Concept")
is_collected: Optional[int] = Field(None, description="Is collected flag")
collection_date: Optional[date] = Field(
None, description="Collection date")
amount: Optional[Decimal] = Field(None, description="Amount")
collector_user: Optional[str] = Field(
None, max_length=20, description="Collector user")
# --- Create Schemas ---
@@ -186,7 +417,7 @@ class InvoiceSalesDetailsUpdate(InvoiceSalesDetailsBase):
class InvoiceCollectionsUpdate(InvoiceCollectionsBase):
"""Schema for updating Collections"""
pass
line_number: Optional[int] = None
class InvoiceHeaderUpdate(InvoiceHeaderBase):
@@ -210,6 +441,7 @@ class InvoiceComplianceMxResponse(InvoiceComplianceMxBase):
class InvoiceFinancialsResponse(InvoiceFinancialsBase):
"""Schema for Financials response"""
id: int
invoice_id: int
class Config:
@@ -218,7 +450,7 @@ class InvoiceFinancialsResponse(InvoiceFinancialsBase):
class InvoiceLogisticsResponse(InvoiceLogisticsBase):
"""Schema for Logistics response"""
logistics_id: int
id: int
invoice_id: int
class Config:
@@ -227,7 +459,7 @@ class InvoiceLogisticsResponse(InvoiceLogisticsBase):
class InvoiceSalesDetailsResponse(InvoiceSalesDetailsBase):
"""Schema for Sales Details response"""
detail_id: int
id: int
invoice_id: int
class Config:
@@ -236,7 +468,7 @@ class InvoiceSalesDetailsResponse(InvoiceSalesDetailsBase):
class InvoiceCollectionsResponse(InvoiceCollectionsBase):
"""Schema for Collections response"""
collection_id: int
id: int
invoice_id: int
class Config:

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))