feat: Implement invoice management features including data table, dialogs for viewing, editing, and deleting invoices, and server-side loading of invoice data

This commit is contained in:
2025-12-11 11:30:44 -06:00
parent 76b9325713
commit e5f6162ffb
14 changed files with 2381 additions and 82 deletions

View File

@@ -1,6 +1,6 @@
from enum import Enum
from typing import Optional, List
from sqlalchemy import BigInteger, 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
@@ -11,21 +11,35 @@ class OperationType(str, Enum):
IMP = "imp" # Importación
EXP = "exp" # Exportación
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
# --- 1. Invoice Header (invoice_header) ---
class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_header"
__tableargs__ = {'schema': 'a76'}
__table_args__ = (
{"schema": "a76"},
)
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
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(
@@ -42,20 +56,22 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
TIMESTAMP(timezone=False), default=datetime.now) # FECHACAPTURA + HORAACTUAL
# Status & Control
status: Mapped[Optional[str]] = mapped_column(String(5)) # ESTATUS
traffic_light_status: Mapped[Optional[str]] = mapped_column(
String(50)) # SEMAFORO / SEMAFOROEXPO/IMPO
process_log: Mapped[Optional[str]] = mapped_column(
String(300)) # COMOFUEPROCESADA **<-- AÑADIDO**
# OBSERVACIONE + OBSERVACIONI + COMENTARIOSESTATUS
comments: Mapped[Optional[str]] = mapped_column(Text)
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
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
observation_en: Mapped[Optional[str]] = mapped_column(Text) #OBSERVACIONI
comments_status: Mapped[Optional[str]] = mapped_column(Text) #COMENTARIOSESTATUS
# Digital Archive Links
cfdi_uuid: Mapped[Optional[str]] = mapped_column(String(100)) # CFDIUUID
path_pdf: Mapped[Optional[str]] = mapped_column(
String(500)) # CFDIPATHPDF **<-- AÑADIDO**
path_xml: Mapped[Optional[str]] = mapped_column(
String(500)) # CFDIPATHXML **<-- AÑADIDO**
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(
@@ -73,10 +89,12 @@ class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin):
# --- 2. Compliance MX (invoice_compliance_mx) ---
class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_compliance_mx"
__tableargs__ = {'schema': 'a76'}
__table_args__ = (
{"schema": "a76"},
)
invoice_id: Mapped[int] = mapped_column(
ForeignKey("invoice_header.id"), primary_key=True)
ForeignKey("a76.invoice_header.id"), primary_key=True)
# Core Customs Data
pedimento: Mapped[Optional[str]] = mapped_column(
@@ -84,23 +102,29 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
pedimento_code: Mapped[Optional[str]] = mapped_column(
String(5)) # PEDIMENTOR1, K1
remesa: Mapped[Optional[int]] = mapped_column(Integer)
aduana: Mapped[Optional[str]] = mapped_column(String(5)) # ADUANA_CRUCE
customs_agent: Mapped[Optional[str]] = mapped_column(
String(10)) # AADUANAL
aduana: Mapped[Optional[str]] = mapped_column(ForeignKey("public.customs_sections.customs_code")) # ADUANA_CRUCE
# Clients & Providers
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_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
customs_broker_id: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.customs_brokers.id")) # AADUANAL
# Flags & Specific Regimes
is_mixed: Mapped[Optional[str]] = mapped_column(
String(2)) # ESMIXTO **<-- AÑADIDO**
waste_type: Mapped[Optional[str]] = mapped_column(
String(1)) # TIPODESPERDICIO **<-- AÑADIDO**
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
# 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)
electronic_signature: Mapped[Optional[str]] = mapped_column(String(999)) # FIRMAELECTRONICA
sem_id: Mapped[Optional[int]] = mapped_column(Integer) # SEM (de SFacEntradaSM/SFacSalidaSM)
# Relationship
header: Mapped["InvoiceHeader"] = relationship(
@@ -110,40 +134,33 @@ class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin):
# --- 3. Financials (invoice_financials) ---
class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_financials"
__tableargs__ = {'schema': 'a76'}
__table_args__ = (
{"schema": "a76"},
)
invoice_id: Mapped[int] = mapped_column(
ForeignKey("invoice_header.id"), primary_key=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
exchange_rate: Mapped[Optional[float]] = mapped_column(
Numeric(13, 6)) # TIPOCAMBIO
currency_type: Mapped[Optional[str]] = mapped_column(ForeignKey("public.currency_types.code")) # TIPOCLAVEMONEDA
exchange_rate: Mapped[Optional[float]] = mapped_column(Numeric(13, 6)) # TIPOCAMBIO
# 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 **<-- AÑADIDO**
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
# 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
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
# 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 **<-- AÑADIDO**
net_weight: Mapped[Optional[float]] = mapped_column(
Numeric(19, 8)) # PESONETO **<-- AÑADIDO**
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
# Relationship
@@ -153,17 +170,21 @@ class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin):
# --- 4. Logistics (invoice_logistics) ---
class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_logistics"
__tableargs__ = {'schema': 'a76'}
__table_args__ = (
{"schema": "a76"},
)
logistics_id: Mapped[int] = mapped_column(
id: Mapped[int] = mapped_column(
BigInteger, primary_key=True, autoincrement=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("invoice_header.id"))
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)) # TRANSPORTE
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(
@@ -189,11 +210,13 @@ class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin):
# --- 5. Sales Order Details (invoice_sales_details) ---
class InvoiceSalesDetails(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_sales_details"
__tableargs__ = {'schema': 'a76'}
__table_args__ = (
{"schema": "a76"},
)
detail_id: Mapped[int] = mapped_column(
id: Mapped[int] = mapped_column(
BigInteger, primary_key=True, autoincrement=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("invoice_header.id"))
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(
@@ -214,11 +237,13 @@ class InvoiceSalesDetails(Base, TenantScopedMixin, TimestampMixin):
# --- 6. Collections (invoice_collections) ---
class InvoiceCollections(Base, TenantScopedMixin, TimestampMixin):
__tablename__ = "invoice_collections"
__tableargs__ = {'schema': 'a76'}
__table_args__ = (
{"schema": "a76"},
)
collection_id: Mapped[int] = mapped_column(
id: Mapped[int] = mapped_column(
BigInteger, primary_key=True, autoincrement=True)
invoice_id: Mapped[int] = mapped_column(ForeignKey("invoice_header.id"))
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

View File

@@ -5,24 +5,35 @@ from pydantic import BaseModel, Field
from .models import OperationType
# --- Base Schemas ---
class InvoiceHeaderBase(BaseModel):
"""Base fields for Invoice Header"""
operation_type: Optional[OperationType] = Field(None, max_length=20, description="Operation type: imp/exp")
invoice_type: Optional[str] = Field(None, max_length=5, description="Invoice type key")
invoice_number: Optional[str] = Field(None, max_length=20, description="Invoice number")
project_number: Optional[str] = Field(None, max_length=14, description="Project number")
purchase_order: Optional[str] = Field(None, max_length=50, description="Purchase order")
related_doc_id: Optional[int] = Field(None, description="Related document ID for rectifications")
operation_type: Optional[OperationType] = Field(
None, max_length=20, description="Operation type: imp/exp")
invoice_type: Optional[str] = Field(
None, max_length=5, description="Invoice type key")
invoice_number: Optional[str] = Field(
None, max_length=20, description="Invoice number")
project_number: Optional[str] = Field(
None, max_length=14, description="Project number")
purchase_order: Optional[str] = Field(
None, max_length=50, description="Purchase order")
related_doc_id: Optional[int] = Field(
None, description="Related document ID for rectifications")
invoice_date: Optional[date] = Field(None, description="Invoice date")
status: Optional[str] = Field(None, max_length=5, description="Status")
traffic_light_status: Optional[str] = Field(None, max_length=50, description="Traffic light status (SEMAFORO)")
process_log: Optional[str] = Field(None, max_length=300, description="Processing log")
comments: Optional[str] = Field(None, description="Comments and 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")
is_updated: Optional[bool] = Field(None, description="Status")
traffic_light_status: Optional[str] = Field(
None, max_length=50, description="Traffic light status (SEMAFORO)")
process_log: Optional[str] = Field(
None, max_length=300, description="Processing log")
comments_status: Optional[str] = Field(
None, description="Comments and 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")
class InvoiceComplianceMxBase(BaseModel):
@@ -36,8 +47,8 @@ class InvoiceComplianceMxBase(BaseModel):
None, max_length=5, description="Customs office")
customs_agent: Optional[str] = Field(
None, max_length=10, description="Customs agent")
is_mixed: Optional[str] = Field(
None, max_length=2, description="Is mixed operation")
is_mixed: Optional[bool] = Field(
None, description="Is mixed operation")
waste_type: Optional[str] = Field(
None, max_length=1, description="Waste type")
appendix_17: Optional[int] = Field(None, description="Appendix 17")