From 20aff5ff7bc6769d456c568678477e4d77d20ede Mon Sep 17 00:00:00 2001 From: acazares Date: Sun, 11 Jan 2026 17:09:04 -0600 Subject: [PATCH] Refactor invoice schemas and services for improved clarity and structure - Updated schemas in `schemas.py` to enhance readability by aligning field definitions and descriptions. - Consolidated optional fields and improved default values for better data handling. - Modified the `InvoiceService` class in `services.py` to streamline error handling and data extraction for nested invoice components. - Ensured that nested data is processed correctly before creating invoice entries, improving overall service reliability. --- .../imports/temporary/validators/common.py | 9 +- backend/api/v1/modules/a76/invoices/models.py | 693 +++++++++++++----- .../api/v1/modules/a76/invoices/schemas.py | 487 ++++++------ .../api/v1/modules/a76/invoices/services.py | 23 +- 4 files changed, 793 insertions(+), 419 deletions(-) diff --git a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py index 14420299..fba8e4f2 100644 --- a/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py +++ b/backend/api/v1/modules/a76/invoices/imports/temporary/validators/common.py @@ -3,6 +3,7 @@ from .... import schemas from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate from api.v1.modules.a76.clients_and_providers.models import ClientProvider +from api.v1.modules.a76.customs_brokers.models import CustomsBroker from api.v1.modules.public.reference_data.incoterms.models import Incoterm from api.v1.modules.a76.items.models import Item from api.v1.modules.public.reference_data.currency_types.models import CurrencyType @@ -281,11 +282,11 @@ def validate_common( ) customs_broker_exists = ( - db.query(ClientProvider) + db.query(CustomsBroker) .filter( - ClientProvider.id == invoice.compliance_mx.customs_broker_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, + CustomsBroker.id == invoice.compliance_mx.customs_broker_id, + CustomsBroker.tenant_id == tenant_id, + CustomsBroker.company_id == company_id, ) .first() ) diff --git a/backend/api/v1/modules/a76/invoices/models.py b/backend/api/v1/modules/a76/invoices/models.py index 3bb23271..5aa90652 100644 --- a/backend/api/v1/modules/a76/invoices/models.py +++ b/backend/api/v1/modules/a76/invoices/models.py @@ -1,20 +1,33 @@ from enum import Enum from typing import Optional, List -from sqlalchemy import BigInteger, Boolean, Date, ForeignKey, Integer, Numeric, String, Text, TIMESTAMP +from sqlalchemy import ( + BigInteger, + Boolean, + Date, + ForeignKey, + Integer, + Numeric, + String, + Text, + TIMESTAMP, +) from sqlalchemy.orm import Mapped, mapped_column, relationship from core.database import Base from datetime import datetime from ....common.base_models import TenantScopedMixin, TimestampMixin + class Currency(str, Enum): FOREIGN = "foreign" LOCAL = "local" MANUAL = "manual" - + + class WeightUnit(str, Enum): KGS = "kgs" LBS = "lbs" - + + class DestinationOriginCove(str, Enum): EDO_BC_PARC_SON = "edo_bc_parc_son" ESTADO_BCS = "estado_bcs" @@ -24,6 +37,7 @@ class DestinationOriginCove(str, Enum): INTERIOR_PAIS = "interior_pais" MPIO_CABORCA_SON = "mpio_caborca_son" + class OperationType(str, Enum): IMP = "imp" # Importación EXP = "exp" # Exportación @@ -55,39 +69,73 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin): id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) # Identifiers - system: Mapped[str] = mapped_column(String(12)) # 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(11)) # TIPOMOVIMIENTO / Clasifica imp/exp/sm/ctm - invoice_type: Mapped[str] = mapped_column(ForeignKey("public.invoice_types.key")) # TIPOFACTURA / TIPODOC - document_type: Mapped[str] = mapped_column(ForeignKey("public.pedimento_regimens.code")) # CLAVEDOCUMENTO / Clave de documento - invoice_number: Mapped[str] = mapped_column(String(100)) # FACTURAIMPO/FACTURAEXPO/FACTURAREMISION/FACTURAENVIO/FACTURASALIDA + system: Mapped[str] = mapped_column( + String(12) + ) # 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(11) + ) # TIPOMOVIMIENTO / Clasifica imp/exp/sm/ctm + invoice_type: Mapped[str] = mapped_column( + ForeignKey("public.invoice_types.key") + ) # TIPOFACTURA / TIPODOC + document_type: Mapped[str] = mapped_column( + ForeignKey("public.pedimento_regimens.code") + ) # CLAVEDOCUMENTO / Clave de documento + invoice_number: Mapped[str] = mapped_column( + String(100) + ) # 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 + 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[datetime] = mapped_column(Date) # FECHAFACTURA - capture_date: Mapped[datetime] = mapped_column(TIMESTAMP(timezone=False), default=datetime.now) # FECHACAPTURA + HORAACTUAL + 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[bool] = mapped_column(Boolean) # ESTATUS - is_updated_rec: Mapped[Optional[bool]] = mapped_column(Boolean) # ESTATUSREC / Estatus de recepción - is_updated_rep: Mapped[Optional[bool]] = mapped_column(Boolean) # ESTATUSREP / Estatus de reporte - 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 - + is_updated_rec: Mapped[Optional[bool]] = mapped_column( + Boolean + ) # ESTATUSREC / Estatus de recepción + is_updated_rep: Mapped[Optional[bool]] = mapped_column( + Boolean + ) # ESTATUSREP / Estatus de reporte + 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 + # 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 + 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 + 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 @@ -96,36 +144,59 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin): # Control & Subcompany subcompany: Mapped[Optional[str]] = mapped_column(String(5)) # SUBEMPRESA - party_count: Mapped[Optional[int]] = mapped_column(Integer) # CANT_PARTIDAS / Cantidad de partidas - + party_count: Mapped[Optional[int]] = mapped_column( + Integer + ) # CANT_PARTIDAS / Cantidad de partidas + # Generation flags - generate_id: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # GENERAID - generate_desc_parties: Mapped[Optional[str]] = mapped_column(String(12)) # GENDESCPARTIDAS / Generar descripción de partidas - apply_manual_discount: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # APLICADESCMANUAL - + generate_id: Mapped[Optional[bool]] = mapped_column( + Boolean, default=False + ) # GENERAID + generate_desc_parties: Mapped[Optional[str]] = mapped_column( + String(12) + ) # GENDESCPARTIDAS / Generar descripción de partidas + apply_manual_discount: Mapped[Optional[bool]] = mapped_column( + Boolean, default=False + ) # 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 - + 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 + 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 + 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) + back_populates="header", cascade="all, delete-orphan", uselist=False + ) financials: Mapped[Optional["InvoiceFinancials"]] = relationship( - back_populates="header", cascade="all, delete-orphan", uselist=False) + back_populates="header", cascade="all, delete-orphan", uselist=False + ) details: Mapped[List["InvoiceSalesDetails"]] = relationship( - back_populates="header", cascade="all, delete-orphan") + back_populates="header", cascade="all, delete-orphan" + ) collections: Mapped[List["InvoiceCollections"]] = relationship( - back_populates="header", cascade="all, delete-orphan") - logistics: Mapped[List["InvoiceLogistics"]] = relationship( - back_populates="header", cascade="all, delete-orphan") + back_populates="header", cascade="all, delete-orphan" + ) + logistics: Mapped[Optional["InvoiceLogistics"]] = relationship( + back_populates="header", cascade="all, delete-orphan", uselist=False + ) # --- 2. Compliance MX (invoice_compliance_mx) --- @@ -133,78 +204,180 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin): __tablename__ = "invoice_compliance_mx" __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_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTO/PEDIMENTOIMPO/EXPO - pedimento_r1: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTOR1 - pedimento_k1: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.pedimentos.id")) # PEDIMENTOK1 + pedimento_id: Mapped[Optional[int]] = mapped_column( + ForeignKey("a76.pedimentos.id") + ) # PEDIMENTO/PEDIMENTOIMPO/EXPO + pedimento_r1: Mapped[Optional[int]] = mapped_column( + ForeignKey("a76.pedimentos.id") + ) # PEDIMENTOR1 + pedimento_k1: Mapped[Optional[int]] = mapped_column( + ForeignKey("a76.pedimentos.id") + ) # 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 + 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_id: Mapped[Optional[int]] = 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[int]] = 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[int]] = 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[int]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # VENDIDOPOR/ENVIADOPOR - customs_broker_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANAL / Agente aduanal - customs_broker_us_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANALAME / Agente aduanal americano + provider_header: Mapped[Optional[str]] = mapped_column( + String(20) + ) # PROVEEDOREXPORTADOR + provider_id: Mapped[Optional[int]] = 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[int]] = 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[int]] = 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[int]] = mapped_column( + ForeignKey("a76.clients_and_providers.id") + ) # VENDIDOPOR/ENVIADOPOR + customs_broker_id: Mapped[Optional[int]] = mapped_column( + ForeignKey("a76.customs_brokers.id") + ) # AADUANAL / Agente aduanal + customs_broker_us_id: Mapped[Optional[int]] = mapped_column( + ForeignKey("a76.customs_brokers.id") + ) # AADUANALAME / Agente aduanal americano # 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 + 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 / 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[bool]] = mapped_column(Boolean, default=False) # 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) + 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[bool]] = mapped_column( + Boolean, default=False + ) # 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[bool]] = mapped_column(Boolean, default=False) # ESDUENOMCIA / Es dueño de mercancía - generate_balances: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # GENERARSALDOS / Generar saldos - was_reviewed_by_company: Mapped[Optional[bool]] = mapped_column(Boolean) # FUEREVISADAMCIA / Fue revisada por la compañía + is_owner_of_goods: Mapped[Optional[bool]] = mapped_column( + Boolean, default=False + ) # ESDUENOMCIA / Es dueño de mercancía + generate_balances: Mapped[Optional[bool]] = mapped_column( + Boolean, default=False + ) # 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 / 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[DestinationOriginCove]] = mapped_column(String(20)) # 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 - + 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[DestinationOriginCove]] = mapped_column( + String(20) + ) # 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 - + 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 - + 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) + sem_id: Mapped[Optional[int]] = mapped_column( + Integer + ) # SEM / ID SEM (de SFacEntradaSM/SFacSalidaSM) # Relationship header: Mapped["InvoiceHeader"] = relationship(back_populates="compliance_mx") @@ -219,62 +392,138 @@ class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin): invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) # Currency - currency: Mapped[Currency] = mapped_column(String(7)) # 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 + currency: Mapped[Currency] = mapped_column( + String(7) + ) # 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 (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 - + 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 + 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 # 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 + 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 + 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 + 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 + 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 + 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 + 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 / 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 + 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") @@ -288,62 +537,136 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin): 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 / 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[bool]] = mapped_column(Boolean, default=False) # ESFERROCARRIL / Es ferrocarril - rail_id: Mapped[Optional[str]] = mapped_column(String(31)) # IDFERRORCARRIL / ID ferrocarril + # 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[bool]] = mapped_column( + Boolean, default=False + ) # 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 / 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 + 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 + 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 + 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[WeightUnit] = mapped_column(String(3)) # 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 + weight_type: Mapped[WeightUnit] = mapped_column( + String(3) + ) # 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 + 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/FECHARECIBO / Fecha entrada/salida - delivery_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAENTREGA / Fecha de entrega - + 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[bool]] = mapped_column(Boolean, default=False) # ENTREGADO / Estado de entrega - received_by: Mapped[Optional[str]] = mapped_column(String(50)) # RECIBIDOPOR / Recibido por + delivered_status: Mapped[Optional[bool]] = mapped_column( + Boolean, default=False + ) # 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 + 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[bool]] = mapped_column(Boolean, default=False) # SETRATAPROCESOCTM / Se trata de proceso CTM + is_ctm_process: Mapped[Optional[bool]] = mapped_column( + Boolean, default=False + ) # SETRATAPROCESOCTM / Se trata de proceso CTM # Relationship header: Mapped["InvoiceHeader"] = relationship(back_populates="logistics") @@ -358,12 +681,20 @@ class InvoiceSalesDetails(Base, TenantScopedMixin, TimestampMixin): invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) 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 + 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 / 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 + 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") @@ -378,9 +709,11 @@ class InvoiceCollections(Base, TenantScopedMixin, TimestampMixin): invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) 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 + 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") - concept: Mapped[Optional[str]] = mapped_column(String(100)) # CONCEPTO / Conce \ No newline at end of file + concept: Mapped[Optional[str]] = mapped_column(String(100)) # CONCEPTO / Conce diff --git a/backend/api/v1/modules/a76/invoices/schemas.py b/backend/api/v1/modules/a76/invoices/schemas.py index 964d4e16..2b71edc4 100644 --- a/backend/api/v1/modules/a76/invoices/schemas.py +++ b/backend/api/v1/modules/a76/invoices/schemas.py @@ -2,253 +2,267 @@ from typing import Literal, Optional, List from datetime import datetime, date from decimal import Decimal from pydantic import BaseModel, Field -from .models import DestinationOriginCove, OperationType, Currency, TransportType, WeightUnit +from .models import ( + DestinationOriginCove, + OperationType, + Currency, + TransportType, + WeightUnit, +) # --- Base Schemas --- class InvoiceHeaderBase(BaseModel): """Base fields for Invoice Header""" - system: Optional[str] = Field( - None, max_length=12, description="System of origin") + + system: Optional[str] = Field(None, max_length=12, description="System of origin") operation_type: Optional[OperationType] = Field( - ..., description="Operation type: imp/exp/sm/ctm") + ..., description="Operation type: imp/exp/sm/ctm" + ) invoice_type: Optional[str] = Field( - None, max_length=5, description="Invoice type key") + None, max_length=5, description="Invoice type key" + ) document_type: str = Field( - ..., max_length=3, description="Document type (Regimen Aduanero)") + ..., max_length=3, description="Document type (Regimen Aduanero)" + ) invoice_number: Optional[str] = Field( - None, max_length=100, description="Invoice number") + None, max_length=100, description="Invoice number" + ) project_number: Optional[str] = Field( - None, max_length=14, description="Project number") + None, max_length=14, description="Project number" + ) purchase_order: Optional[str] = Field( - None, max_length=50, description="Purchase order") + None, max_length=50, description="Purchase order" + ) related_doc_id: Optional[int] = Field( - None, description="Related document ID for rectifications") + None, description="Related document ID for rectifications" + ) alternate_invoice: Optional[str] = Field( - None, max_length=99, description="Alternate invoice") + None, max_length=99, description="Alternate invoice" + ) invoice_ref: Optional[str] = Field( - None, max_length=19, description="Invoice reference") + None, max_length=19, description="Invoice reference" + ) proforma_number: Optional[str] = Field( - None, max_length=20, description="Proforma number") + None, max_length=20, description="Proforma number" + ) invoice_date: date = Field(..., description="Invoice date") emission_date: Optional[date] = Field(None, description="Emission date") is_updated: bool = Field(False, 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") + 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") + None, max_length=50, description="Traffic light status" + ) process_log: Optional[str] = Field( - None, max_length=300, description="Processing log") + None, max_length=300, description="Processing log" + ) is_updated_rec: Optional[int] = Field(None, description="Reception status") is_updated_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 status") + 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 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") + 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") + 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") + 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[bool] = Field(False, description="Generate ID") generate_desc_parties: Optional[str] = Field( - None, max_length=12, description="Generate description of parties") - apply_manual_discount: Optional[bool] = Field(False, description="Apply manual discount") + None, max_length=12, description="Generate description of parties" + ) + apply_manual_discount: Optional[bool] = Field( + False, 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") + 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") + 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") + 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): """Base fields for Compliance MX""" - pedimento_id: Optional[int] = Field( - None, description="Pedimento id") - pedimento_r1: Optional[int] = Field( - None, description="Pedimento id (R1)") - pedimento_k1: Optional[int] = Field( - None, description="Pedimento id (K1)") + + pedimento_id: Optional[int] = Field(None, description="Pedimento id") + pedimento_r1: Optional[int] = Field(None, description="Pedimento id (R1)") + pedimento_k1: Optional[int] = Field(None, description="Pedimento id (K1)") remesa: Optional[int] = Field(None, description="Remesa") aduana: Optional[str] = Field(None, max_length=5, description="Customs office") port_of_entry: Optional[str] = Field( - None, max_length=6, description="Port of entry") + None, max_length=6, description="Port of entry" + ) destination: Optional[str] = Field( - None, max_length=3, description="Destination code") + None, max_length=3, description="Destination code" + ) manifest_number: Optional[str] = Field( - None, max_length=15, description="Manifest number") - provider_header: str = Field( - None, max_length=20, description="Provider header") - provider_id: int = Field( - None, description="Provider ID") - sold_to_header: str = Field( - None, max_length=20, description="Sold to header") - sold_to_id: int = Field( - None, description="Sold to ID") - shipped_to_header: str = Field( - None, max_length=20, description="Shipped to header") - shipped_to_id:int = Field( - None, description="Shipped to ID") + None, max_length=15, description="Manifest number" + ) + provider_header: str = Field(None, max_length=20, description="Provider header") + provider_id: int = Field(None, description="Provider ID") + sold_to_header: str = Field(None, max_length=20, description="Sold to header") + sold_to_id: int = Field(None, description="Sold to ID") + shipped_to_header: str = Field(None, max_length=20, description="Shipped to header") + shipped_to_id: int = Field(None, description="Shipped to ID") shipped_by_header: Optional[int] = Field( - None, max_length=20, description="Shipped by header") - shipped_by_id: Optional[int] = Field( - None, description="Shipped by ID") - customs_broker_id: int = Field( - None, description="Customs broker ID") + None, max_length=20, description="Shipped by header" + ) + shipped_by_id: Optional[int] = Field(None, description="Shipped by ID") + customs_broker_id: int = Field(None, description="Customs broker ID") customs_broker_us_id: Optional[int] = Field( - None, description="US customs broker ID") + 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( - False, 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") + None, max_length=20, description="Broker invoice number" + ) + broker_invoice_date: Optional[date] = Field(None, description="Broker invoice date") + is_mixed: Optional[bool] = Field(False, 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[bool] = Field( - False, description="Is regime change") + is_regime_change: Optional[bool] = Field(False, 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") + 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: bool = Field(..., description="Is pedimento pending") - is_owner_of_goods: Optional[bool] = Field( - False, description="Is owner of goods") - generate_balances: Optional[bool] = Field( - False, description="Generate balances") + is_owner_of_goods: Optional[bool] = Field(False, description="Is owner of goods") + generate_balances: Optional[bool] = Field(False, 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") + 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") + 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") + 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") + 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[DestinationOriginCove] = Field('franja_front_norte', max_length=20, description="Origin/Destination COVE") + None, max_length=204, description="VUCEM addendum" + ) + origin_destination_cove: Optional[DestinationOriginCove] = Field( + "franja_front_norte", max_length=20, 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") + 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") + 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") + None, max_length=31, description="Movement type" + ) office_document: Optional[str] = Field( - None, max_length=30, description="Office document") + None, max_length=30, description="Office document" + ) reason_export: Optional[str] = Field( - None, max_length=1, description="Reason for export") + None, max_length=1, description="Reason for export" + ) signature_key: Optional[str] = Field( - None, max_length=10, description="Signature key") + None, max_length=10, description="Signature key" + ) sem_id: Optional[int] = Field(None, description="SEM ID") class InvoiceFinancialsBase(BaseModel): """Base fields for Financials""" - currency: Currency = Field( - None, max_length=7, description="Currency code") - currency_type: Optional[str] = Field( - "USD", description="Currency type") + + currency: Currency = Field(None, max_length=7, description="Currency code") + currency_type: Optional[str] = Field("USD", description="Currency type") exchange_rate: Decimal = Field(0.00, description="Exchange rate") exchange_rate_mm: Optional[Decimal] = Field( - None, description="Exchange rate currency to currency") + 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") + 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") + None, description="Customs value in MXN" + ) customs_value_me: Optional[Decimal] = Field( - None, description="Customs value in foreign currency") + None, description="Customs value in foreign currency" + ) raw_material_value_mn: Optional[Decimal] = Field( - None, description="Raw material value in MXN") + None, description="Raw material value in MXN" + ) raw_material_value_me: Optional[Decimal] = Field( - None, description="Raw material value in foreign currency") + None, description="Raw material value in foreign currency" + ) aggregate_value_mn: Optional[Decimal] = Field( - None, description="Aggregate value in MXN") + None, description="Aggregate value in MXN" + ) aggregate_value_me: Optional[Decimal] = Field( - None, description="Aggregate value in foreign currency") + None, description="Aggregate value in foreign currency" + ) aggregate_value_mc: Optional[Decimal] = Field( - None, description="Aggregate value in third currency") + None, description="Aggregate value in third currency" + ) mexican_value_mn: Optional[Decimal] = Field( - None, description="Mexican merchandise value in MXN") + None, description="Mexican merchandise value in MXN" + ) mexican_value_me: Optional[Decimal] = Field( - None, description="Mexican merchandise value in foreign currency") + None, description="Mexican merchandise value in foreign currency" + ) mexican_value_mc: Optional[Decimal] = Field( - None, description="Mexican merchandise value in third currency") + None, description="Mexican merchandise value in third currency" + ) national_packaging_mn: Optional[Decimal] = Field( - None, description="National packaging in MXN") + None, description="National packaging in MXN" + ) national_packaging_me: Optional[Decimal] = Field( - None, description="National packaging in foreign currency") + None, description="National packaging in foreign currency" + ) national_packaging_mc: Optional[Decimal] = Field( - None, description="National packaging in third currency") + 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") + 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") + other_increments: Optional[Decimal] = Field(None, description="Other increments") total_increments_mn: Optional[Decimal] = Field( - None, description="Total increments in MXN") + None, description="Total increments in MXN" + ) total_increments_me: Optional[Decimal] = Field( - None, description="Total increments in foreign currency") + None, description="Total increments in foreign currency" + ) iva_mn: Optional[Decimal] = Field(None, description="IVA in MXN") - iva_me: Optional[Decimal] = Field( - None, description="IVA in foreign currency") - iva_mc: Optional[Decimal] = Field( - None, description="IVA in third currency") + 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[Decimal] = Field(None, 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") + 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") @@ -257,131 +271,142 @@ class InvoiceFinancialsBase(BaseModel): 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") + + 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") + None, max_length=10, description="US transport ID" + ) transport_type: TransportType = Field( - 'none', max_length=15, description="Transport type") + "none", max_length=15, description="Transport type" + ) transport_num: Optional[str] = Field( - None, max_length=20, description="Transport number") + None, max_length=20, description="Transport number" + ) transport_mode: Optional[str] = Field( - 30, max_length=15, description="Transport mode") - driver_name: Optional[str] = Field( - None, max_length=80, description="Driver name") - is_rail: Optional[bool] = Field( - False, description="Is rail transport") - rail_id: Optional[str] = Field( - None, max_length=31, description="Rail ID") + 30, max_length=15, description="Transport mode" + ) + driver_name: Optional[str] = Field(None, max_length=80, description="Driver name") + is_rail: Optional[bool] = Field(False, description="Is rail transport") + rail_id: Optional[str] = Field(None, max_length=31, description="Rail ID") vehicle_num: Optional[str] = Field( - None, max_length=20, description="Vehicle number") + None, max_length=20, description="Vehicle number" + ) license_plate: Optional[str] = Field( - None, max_length=20, description="License plate") + None, max_length=20, description="License plate" + ) license_plate_complete: Optional[str] = Field( - None, max_length=40, description="Complete license plate") + 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") + 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") + 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") + 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: WeightUnit = Field( - default="kgs", max_length=3, description="Weight type") + default="kgs", max_length=3, description="Weight type" + ) container_types: Optional[str] = Field( - None, max_length=500, description="Container types") + None, max_length=500, description="Container types" + ) vehicle_data: Optional[str] = Field( - None, max_length=500, description="Vehicle data") + None, max_length=500, description="Vehicle data" + ) origin_location: Optional[str] = Field( - None, max_length=200, description="Origin location") + None, max_length=200, description="Origin location" + ) destination_location: Optional[str] = Field( - None, max_length=200, description="Destination location") + None, max_length=200, description="Destination location" + ) transport_itinerary: Optional[str] = Field( - None, max_length=1000, description="Transport itinerary") + 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") + 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[bool] = Field(False, 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[bool] = Field( - False, description="Is CTM process") + None, max_length=20, description="Payment receipt number" + ) + is_ctm_process: Optional[bool] = Field(False, description="Is CTM process") class InvoiceSalesDetailsBase(BaseModel): """Base fields for Sales Details""" + line_number: int = Field(..., description="Line number") - sales_order: Optional[str] = Field( - None, max_length=20, description="Sales order") + sales_order: Optional[str] = Field(None, max_length=20, description="Sales order") colors_description: Optional[str] = Field( - None, max_length=49, description="Colors description") + None, max_length=49, description="Colors description" + ) square_color_code: Optional[str] = Field( - None, max_length=1, description="Square color code") + None, max_length=1, description="Square color code" + ) line_bundles: Optional[int] = Field(None, description="Line bundles count") 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") + None, max_length=15, description="Invoice number" + ) concept: Optional[str] = Field(None, max_length=100, description="Concept") # --- Create Schemas --- + class InvoiceComplianceMxCreate(InvoiceComplianceMxBase): """Schema for creating Compliance MX""" + pass class InvoiceFinancialsCreate(InvoiceFinancialsBase): """Schema for creating Financials""" + pass class InvoiceLogisticsCreate(InvoiceLogisticsBase): """Schema for creating Logistics""" + pass class InvoiceSalesDetailsCreate(InvoiceSalesDetailsBase): """Schema for creating Sales Details""" + pass class InvoiceCollectionsCreate(InvoiceCollectionsBase): """Schema for creating Collections""" + pass class InvoiceHeaderCreate(InvoiceHeaderBase): """Schema for creating Invoice Header with nested relations""" + compliance_mx: Optional[InvoiceComplianceMxCreate] = None financials: Optional[InvoiceFinancialsCreate] = None logistics: Optional[InvoiceLogisticsCreate] = None @@ -391,33 +416,40 @@ class InvoiceHeaderCreate(InvoiceHeaderBase): # --- Update Schemas --- + class InvoiceComplianceMxUpdate(InvoiceComplianceMxBase): """Schema for updating Compliance MX""" + pass class InvoiceFinancialsUpdate(InvoiceFinancialsBase): """Schema for updating Financials""" + pass class InvoiceLogisticsUpdate(InvoiceLogisticsBase): """Schema for updating Logistics""" + pass class InvoiceSalesDetailsUpdate(InvoiceSalesDetailsBase): """Schema for updating Sales Details""" + line_number: Optional[int] = None class InvoiceCollectionsUpdate(InvoiceCollectionsBase): """Schema for updating Collections""" + line_number: Optional[int] = None class InvoiceHeaderUpdate(InvoiceHeaderBase): """Schema for updating Invoice Header with nested relations""" + id: int compliance_mx: Optional[InvoiceComplianceMxUpdate] = None financials: Optional[InvoiceFinancialsUpdate] = None @@ -428,8 +460,10 @@ class InvoiceHeaderUpdate(InvoiceHeaderBase): # --- Response Schemas --- + class InvoiceComplianceMxResponse(InvoiceComplianceMxBase): """Schema for Compliance MX response""" + invoice_id: int class Config: @@ -438,6 +472,7 @@ class InvoiceComplianceMxResponse(InvoiceComplianceMxBase): class InvoiceFinancialsResponse(InvoiceFinancialsBase): """Schema for Financials response""" + id: int invoice_id: int @@ -447,6 +482,7 @@ class InvoiceFinancialsResponse(InvoiceFinancialsBase): class InvoiceLogisticsResponse(InvoiceLogisticsBase): """Schema for Logistics response""" + id: int invoice_id: int @@ -456,6 +492,7 @@ class InvoiceLogisticsResponse(InvoiceLogisticsBase): class InvoiceSalesDetailsResponse(InvoiceSalesDetailsBase): """Schema for Sales Details response""" + id: int invoice_id: int @@ -465,6 +502,7 @@ class InvoiceSalesDetailsResponse(InvoiceSalesDetailsBase): class InvoiceCollectionsResponse(InvoiceCollectionsBase): """Schema for Collections response""" + id: int invoice_id: int @@ -474,13 +512,14 @@ class InvoiceCollectionsResponse(InvoiceCollectionsBase): class InvoiceHeaderResponse(InvoiceHeaderBase): """Schema for Invoice Header response with nested relations""" + id: int capture_date: datetime compliance_mx: Optional[InvoiceComplianceMxResponse] = None financials: Optional[InvoiceFinancialsResponse] = None - logistics: Optional[InvoiceLogisticsResponse] = [] - details: Optional[InvoiceSalesDetailsResponse] = [] - collections: Optional[InvoiceCollectionsResponse] = [] + logistics: Optional[InvoiceLogisticsResponse] = None + details: Optional[List[InvoiceSalesDetailsResponse]] = [] + collections: Optional[List[InvoiceCollectionsResponse]] = [] class Config: from_attributes = True diff --git a/backend/api/v1/modules/a76/invoices/services.py b/backend/api/v1/modules/a76/invoices/services.py index 07a5fab9..81a37d23 100644 --- a/backend/api/v1/modules/a76/invoices/services.py +++ b/backend/api/v1/modules/a76/invoices/services.py @@ -90,19 +90,20 @@ class InvoiceService: errors = ErrorCollector() # Validar si la factura ya existe - invoice_exists(db, invoice_data.invoice_number, tenant_id, company_id, errors) - validate_create(db, invoice_data, tenant_id, company_id, errors) + invoice_exists(db, invoice_data.invoice_number, tenant_id, company_id, errors) + validate_create(db, invoice_data, tenant_id, company_id, errors) - # Si hay errores, lanzar excepción + # Si hay errores, lanzar excepción ANTES de intentar crear errors.raise_if_errors("Error al crear la factura") + # Extract nested data + compliance_data = invoice_data.compliance_mx + financials_data = invoice_data.financials + logistics_data = invoice_data.logistics + details_data = invoice_data.details or [] + collections_data = invoice_data.collections or [] + try: - # Extract nested data - compliance_data = invoice_data.compliance_mx - financials_data = invoice_data.financials - logistics_data = invoice_data.logistics or [] - details_data = invoice_data.details or [] - collections_data = invoice_data.collections or [] # Create main invoice header raw_invoice_dict = invoice_data.model_dump( @@ -148,8 +149,8 @@ class InvoiceService: db.add(new_financials) # Create logistics entries - for logistics_item in logistics_data: - raw_log_dict = logistics_item.model_dump() + if logistics_data: + raw_log_dict = logistics_data.model_dump() logistics_dict = clean_dict(raw_log_dict) logistics_dict["invoice_id"] = new_invoice.id