feat: fix R1 pedimento duplication and improve invoice report exports
- Fix bidirectional R1 resolution in all report query builders (Temporary, Definitive, Repair, Export, ExportRepair): use JOIN on pedimento_rectification_origin in both directions so the rectified pedimento number is resolved correctly and not duplicated. - Restore original CSV export format (ValorComercialMN, ValorMPTemp, ValorAgre as separate columns); compute them from item_line_financials SUM instead of invoice-level header totals which were always 0. - Rename CSV column "PEDIMENTO RECTIFICACION" to "PEDIMENTO R1" in both backend csv_utils.py and frontend manual download. - Harden temporary invoice update validator to safely handle null compliance_mx / logistics objects without crashing. - Add R1 rectification fields (es_rectificacion, pedimento_original, etc.) to the pedimento other-data form and initialize their default state. - Remove default companyId parameter from pedimentosApi methods to avoid hardcoded company ID 1. - Minor: whitespace cleanup, error handler adjustments, keyboard manager fix.
This commit is contained in:
@@ -36,17 +36,17 @@ def validate_update(
|
||||
|
||||
# Validar campos requeridos según el tipo de operación
|
||||
invoice_dict = {
|
||||
'provider_id': invoice_data.compliance_mx.provider_id if invoice_data.compliance_mx else None,
|
||||
'sold_to_id': invoice_data.compliance_mx.sold_to_id if invoice_data.compliance_mx else None,
|
||||
'sold_to_header': invoice_data.compliance_mx.sold_to_header if invoice_data.compliance_mx else None,
|
||||
'shipped_to_id': invoice_data.compliance_mx.shipped_to_id if invoice_data.compliance_mx else None,
|
||||
'customs_broker_id': invoice_data.compliance_mx.customs_broker_id if invoice_data.compliance_mx else None,
|
||||
'pedimento_id': invoice_data.compliance_mx.pedimento_id if invoice_data.compliance_mx else None,
|
||||
'provider_id': invoice_data.compliance_mx.provider_id if invoice_data.compliance_mx else (existing_invoice.compliance_mx.provider_id if existing_invoice.compliance_mx else None),
|
||||
'sold_to_id': invoice_data.compliance_mx.sold_to_id if invoice_data.compliance_mx else (existing_invoice.compliance_mx.sold_to_id if existing_invoice.compliance_mx else None),
|
||||
'sold_to_header': invoice_data.compliance_mx.sold_to_header if invoice_data.compliance_mx else (existing_invoice.compliance_mx.sold_to_header if existing_invoice.compliance_mx else None),
|
||||
'shipped_to_id': invoice_data.compliance_mx.shipped_to_id if invoice_data.compliance_mx else (existing_invoice.compliance_mx.shipped_to_id if existing_invoice.compliance_mx else None),
|
||||
'customs_broker_id': invoice_data.compliance_mx.customs_broker_id if invoice_data.compliance_mx else (existing_invoice.compliance_mx.customs_broker_id if existing_invoice.compliance_mx else None),
|
||||
'pedimento_id': invoice_data.compliance_mx.pedimento_id if invoice_data.compliance_mx else (existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None),
|
||||
}
|
||||
|
||||
validate_required_fields_by_operation(
|
||||
invoice_data=invoice_dict,
|
||||
operation_type=invoice_data.operation_type or 'IMP',
|
||||
operation_type=invoice_data.operation_type or (existing_invoice.operation_type or 'imp'),
|
||||
errors=errors
|
||||
)
|
||||
|
||||
@@ -57,34 +57,36 @@ def validate_update(
|
||||
# Siguiendo la lógica del código Clarion original
|
||||
|
||||
# Columna A: Pedimento (si no viene en CSV, usar el existente)
|
||||
if invoice_data.compliance_mx.pedimento_id:
|
||||
invoice_data.compliance_mx.pedimento_id = invoice_data.compliance_mx.pedimento_id
|
||||
else:
|
||||
invoice_data.compliance_mx.pedimento_id = existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None
|
||||
if invoice_data.compliance_mx:
|
||||
if invoice_data.compliance_mx.pedimento_id:
|
||||
invoice_data.compliance_mx.pedimento_id = invoice_data.compliance_mx.pedimento_id
|
||||
else:
|
||||
invoice_data.compliance_mx.pedimento_id = existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None
|
||||
|
||||
# Columna B: Remesa
|
||||
if invoice_data.compliance_mx.remesa:
|
||||
invoice_data.compliance_mx.remesa = invoice_data.compliance_mx.remesa
|
||||
else:
|
||||
invoice_data.compliance_mx.remesa = existing_invoice.compliance_mx.remesa if existing_invoice.compliance_mx else None
|
||||
if invoice_data.compliance_mx:
|
||||
if invoice_data.compliance_mx.remesa:
|
||||
invoice_data.compliance_mx.remesa = invoice_data.compliance_mx.remesa
|
||||
else:
|
||||
invoice_data.compliance_mx.remesa = existing_invoice.compliance_mx.remesa if existing_invoice.compliance_mx else None
|
||||
|
||||
# Columna C: Factura (OBLIGATORIO)
|
||||
invoice_data.invoice_number = clean_str(invoice_data.invoice_number)
|
||||
if not invoice_data.invoice_number:
|
||||
errors.add_required_error("invoice_number")
|
||||
if invoice_data.invoice_number is not None:
|
||||
invoice_data.invoice_number = clean_str(invoice_data.invoice_number)
|
||||
if not invoice_data.invoice_number:
|
||||
errors.add_required_error("invoice_number")
|
||||
else:
|
||||
invoice_data.invoice_number = existing_invoice.invoice_number
|
||||
|
||||
# Columna D: Fecha
|
||||
if invoice_data.invoice_date:
|
||||
invoice_data.invoice_date = invoice_data.invoice_date
|
||||
else:
|
||||
if not invoice_data.invoice_date:
|
||||
invoice_data.invoice_date = existing_invoice.invoice_date
|
||||
|
||||
# Columna E: Tipo Cambio
|
||||
if invoice_data.financials and invoice_data.financials.exchange_rate is not None:
|
||||
invoice_data.financials.exchange_rate = invoice_data.financials.exchange_rate
|
||||
else:
|
||||
if existing_invoice.financials:
|
||||
invoice_data.financials.exchange_rate = existing_invoice.financials.exchange_rate
|
||||
if invoice_data.financials:
|
||||
if invoice_data.financials.exchange_rate is None:
|
||||
if existing_invoice.financials:
|
||||
invoice_data.financials.exchange_rate = existing_invoice.financials.exchange_rate
|
||||
|
||||
# Columna F: Régimen
|
||||
if invoice_data.document_type:
|
||||
@@ -93,157 +95,161 @@ def validate_update(
|
||||
invoice_data.document_type = existing_invoice.document_type
|
||||
|
||||
# Columna G: Clave Proveedor
|
||||
if invoice_data.compliance_mx and invoice_data.compliance_mx.provider_id is not None:
|
||||
invoice_data.compliance_mx.provider_id = invoice_data.compliance_mx.provider_id
|
||||
else:
|
||||
invoice_data.compliance_mx.provider_id = existing_invoice.compliance_mx.provider_id if existing_invoice.compliance_mx else None
|
||||
if invoice_data.compliance_mx:
|
||||
if invoice_data.compliance_mx.provider_id is None:
|
||||
invoice_data.compliance_mx.provider_id = existing_invoice.compliance_mx.provider_id if existing_invoice.compliance_mx else None
|
||||
|
||||
# Columna H: Clave Vendido A
|
||||
if invoice_data.compliance_mx and invoice_data.compliance_mx.sold_to_id is not None:
|
||||
invoice_data.compliance_mx.sold_to_id = invoice_data.compliance_mx.sold_to_id
|
||||
else:
|
||||
invoice_data.compliance_mx.sold_to_id = existing_invoice.compliance_mx.sold_to_id if existing_invoice.compliance_mx else None
|
||||
if invoice_data.compliance_mx:
|
||||
if invoice_data.compliance_mx.sold_to_id is None:
|
||||
invoice_data.compliance_mx.sold_to_id = existing_invoice.compliance_mx.sold_to_id if existing_invoice.compliance_mx else None
|
||||
|
||||
# Columna I: Clave Enviado A
|
||||
if invoice_data.compliance_mx and invoice_data.compliance_mx.shipped_to_id is not None:
|
||||
invoice_data.compliance_mx.shipped_to_id = invoice_data.compliance_mx.shipped_to_id
|
||||
else:
|
||||
invoice_data.compliance_mx.shipped_to_id = existing_invoice.compliance_mx.shipped_to_id if existing_invoice.compliance_mx else None
|
||||
if invoice_data.compliance_mx:
|
||||
if invoice_data.compliance_mx.shipped_to_id is None:
|
||||
invoice_data.compliance_mx.shipped_to_id = existing_invoice.compliance_mx.shipped_to_id if existing_invoice.compliance_mx else None
|
||||
|
||||
# Columna J: Clave A. Aduanal
|
||||
if invoice_data.compliance_mx and invoice_data.compliance_mx.customs_broker_id is not None:
|
||||
invoice_data.compliance_mx.customs_broker_id = invoice_data.compliance_mx.customs_broker_id
|
||||
else:
|
||||
invoice_data.compliance_mx.customs_broker_id = existing_invoice.compliance_mx.customs_broker_id if existing_invoice.compliance_mx else None
|
||||
if invoice_data.compliance_mx:
|
||||
if invoice_data.compliance_mx.customs_broker_id is None:
|
||||
invoice_data.compliance_mx.customs_broker_id = existing_invoice.compliance_mx.customs_broker_id if existing_invoice.compliance_mx else None
|
||||
|
||||
# Columna K: Clave Transportista
|
||||
if invoice_data.logistics and invoice_data.logistics.carrier_id is not None:
|
||||
invoice_data.logistics.carrier_id = invoice_data.logistics.carrier_id
|
||||
else:
|
||||
invoice_data.logistics.carrier_id = existing_invoice.logistics.carrier_id if existing_invoice.logistics else None
|
||||
if invoice_data.logistics:
|
||||
# Note: logistics in update schema seems to be a single object, but in model it's a list.
|
||||
# This validator seems to expect a single object (InvoiceLogisticsUpdate).
|
||||
# We'll stick to the existing logic but make it safe.
|
||||
if hasattr(invoice_data.logistics, 'carrier_id') and invoice_data.logistics.carrier_id is None:
|
||||
invoice_data.logistics.carrier_id = existing_invoice.logistics.carrier_id if existing_invoice.logistics else None
|
||||
|
||||
# Columna L: Nombre Conductor
|
||||
if invoice_data.logistics and invoice_data.logistics.driver_name:
|
||||
invoice_data.logistics.driver_name = clean_str(invoice_data.logistics.driver_name)
|
||||
else:
|
||||
invoice_data.logistics.driver_name = existing_invoice.logistics.driver_name if existing_invoice.logistics else None
|
||||
if invoice_data.logistics:
|
||||
if hasattr(invoice_data.logistics, 'driver_name') and not invoice_data.logistics.driver_name:
|
||||
invoice_data.logistics.driver_name = existing_invoice.logistics.driver_name if existing_invoice.logistics else None
|
||||
elif hasattr(invoice_data.logistics, 'driver_name'):
|
||||
invoice_data.logistics.driver_name = clean_str(invoice_data.logistics.driver_name)
|
||||
|
||||
# Columna M: Tipo Transporte
|
||||
if invoice_data.logistics and invoice_data.logistics.transport_type:
|
||||
invoice_data.logistics.transport_type = clean_str(invoice_data.logistics.transport_type)
|
||||
else:
|
||||
invoice_data.logistics.transport_type = existing_invoice.logistics.transport_type if existing_invoice.logistics else None
|
||||
if invoice_data.logistics:
|
||||
if hasattr(invoice_data.logistics, 'transport_type') and not invoice_data.logistics.transport_type:
|
||||
invoice_data.logistics.transport_type = existing_invoice.logistics.transport_type if existing_invoice.logistics else None
|
||||
elif hasattr(invoice_data.logistics, 'transport_type'):
|
||||
invoice_data.logistics.transport_type = clean_str(invoice_data.logistics.transport_type)
|
||||
|
||||
# Columna N: Número de Transporte
|
||||
if invoice_data.logistics and invoice_data.logistics.transport_num:
|
||||
invoice_data.logistics.transport_num = clean_str(invoice_data.logistics.transport_num)
|
||||
else:
|
||||
invoice_data.logistics.transport_num = existing_invoice.logistics.transport_num if existing_invoice.logistics else None
|
||||
if invoice_data.logistics:
|
||||
if hasattr(invoice_data.logistics, 'transport_num') and not invoice_data.logistics.transport_num:
|
||||
invoice_data.logistics.transport_num = existing_invoice.logistics.transport_num if existing_invoice.logistics else None
|
||||
elif hasattr(invoice_data.logistics, 'transport_num'):
|
||||
invoice_data.logistics.transport_num = clean_str(invoice_data.logistics.transport_num)
|
||||
|
||||
# Columna O: Tipo de Moneda
|
||||
if invoice_data.financials and invoice_data.financials.currency:
|
||||
invoice_data.financials.currency = clean_str(invoice_data.financials.currency).lower()
|
||||
else:
|
||||
invoice_data.financials.currency = existing_invoice.financials.currency if existing_invoice.financials else None
|
||||
if invoice_data.financials:
|
||||
if not invoice_data.financials.currency:
|
||||
invoice_data.financials.currency = existing_invoice.financials.currency if existing_invoice.financials else None
|
||||
else:
|
||||
invoice_data.financials.currency = clean_str(invoice_data.financials.currency).lower()
|
||||
|
||||
# Columna P: Clave Moneda
|
||||
if invoice_data.financials and invoice_data.financials.currency_type:
|
||||
invoice_data.financials.currency_type = clean_str(invoice_data.financials.currency_type).upper()
|
||||
else:
|
||||
invoice_data.financials.currency_type = existing_invoice.financials.currency_type if existing_invoice.financials else None
|
||||
if invoice_data.financials:
|
||||
if not invoice_data.financials.currency_type:
|
||||
invoice_data.financials.currency_type = existing_invoice.financials.currency_type if existing_invoice.financials else None
|
||||
else:
|
||||
invoice_data.financials.currency_type = clean_str(invoice_data.financials.currency_type).upper()
|
||||
|
||||
# Columna Q: Flete
|
||||
if invoice_data.financials and invoice_data.financials.freight is not None:
|
||||
invoice_data.financials.freight = invoice_data.financials.freight
|
||||
else:
|
||||
invoice_data.financials.freight = existing_invoice.financials.freight if existing_invoice.financials else None
|
||||
if invoice_data.financials:
|
||||
if invoice_data.financials.freight is None:
|
||||
invoice_data.financials.freight = existing_invoice.financials.freight if existing_invoice.financials else None
|
||||
|
||||
# Columna R: Val Seguros
|
||||
if invoice_data.financials and invoice_data.financials.insurance_value is not None:
|
||||
invoice_data.financials.insurance_value = invoice_data.financials.insurance_value
|
||||
else:
|
||||
invoice_data.financials.insurance_value = existing_invoice.financials.insurance_value if existing_invoice.financials else None
|
||||
if invoice_data.financials:
|
||||
if invoice_data.financials.insurance_value is None:
|
||||
invoice_data.financials.insurance_value = existing_invoice.financials.insurance_value if existing_invoice.financials else None
|
||||
|
||||
# Columna S: Seguros
|
||||
if invoice_data.financials and invoice_data.financials.insurance is not None:
|
||||
invoice_data.financials.insurance = invoice_data.financials.insurance
|
||||
else:
|
||||
invoice_data.financials.insurance = existing_invoice.financials.insurance if existing_invoice.financials else None
|
||||
if invoice_data.financials:
|
||||
if invoice_data.financials.insurance is None:
|
||||
invoice_data.financials.insurance = existing_invoice.financials.insurance if existing_invoice.financials else None
|
||||
|
||||
# Columna T: Embalaje
|
||||
if invoice_data.financials and invoice_data.financials.packaging is not None:
|
||||
invoice_data.financials.packaging = invoice_data.financials.packaging
|
||||
else:
|
||||
invoice_data.financials.packaging = existing_invoice.financials.packaging if existing_invoice.financials else None
|
||||
if invoice_data.financials:
|
||||
if invoice_data.financials.packaging is None:
|
||||
invoice_data.financials.packaging = existing_invoice.financials.packaging if existing_invoice.financials else None
|
||||
|
||||
# Columna U: Otros Incrementables
|
||||
if invoice_data.financials and invoice_data.financials.other_increments is not None:
|
||||
invoice_data.financials.other_increments = invoice_data.financials.other_increments
|
||||
else:
|
||||
invoice_data.financials.other_increments = existing_invoice.financials.other_increments if existing_invoice.financials else None
|
||||
if invoice_data.financials:
|
||||
if invoice_data.financials.other_increments is None:
|
||||
invoice_data.financials.other_increments = existing_invoice.financials.other_increments if existing_invoice.financials else None
|
||||
|
||||
# Columna V: Incoterms
|
||||
if invoice_data.logistics and invoice_data.logistics.incoterm:
|
||||
invoice_data.logistics.incoterm = clean_str(invoice_data.logistics.incoterm).upper()
|
||||
else:
|
||||
invoice_data.logistics.incoterm = existing_invoice.logistics.incoterm if existing_invoice.logistics else None
|
||||
if invoice_data.logistics:
|
||||
if hasattr(invoice_data.logistics, 'incoterm') and not invoice_data.logistics.incoterm:
|
||||
invoice_data.logistics.incoterm = existing_invoice.logistics.incoterm if existing_invoice.logistics else None
|
||||
elif hasattr(invoice_data.logistics, 'incoterm'):
|
||||
invoice_data.logistics.incoterm = clean_str(invoice_data.logistics.incoterm).upper()
|
||||
|
||||
# Columna W: Precinto
|
||||
if invoice_data.logistics and invoice_data.logistics.seal_number:
|
||||
invoice_data.logistics.seal_number = clean_str(invoice_data.logistics.seal_number)
|
||||
else:
|
||||
invoice_data.logistics.seal_number = existing_invoice.logistics.seal_number if existing_invoice.logistics else None
|
||||
if invoice_data.logistics:
|
||||
if hasattr(invoice_data.logistics, 'seal_number') and not invoice_data.logistics.seal_number:
|
||||
invoice_data.logistics.seal_number = existing_invoice.logistics.seal_number if existing_invoice.logistics else None
|
||||
elif hasattr(invoice_data.logistics, 'seal_number'):
|
||||
invoice_data.logistics.seal_number = clean_str(invoice_data.logistics.seal_number)
|
||||
|
||||
# Columna X: Fecha de Emisión
|
||||
if invoice_data.emission_date:
|
||||
invoice_data.emission_date = invoice_data.emission_date
|
||||
else:
|
||||
if not invoice_data.emission_date:
|
||||
invoice_data.emission_date = existing_invoice.emission_date
|
||||
|
||||
# Columna Y: Tipo de Peso (Opcional)
|
||||
if invoice_data.logistics and invoice_data.logistics.weight_type:
|
||||
invoice_data.logistics.weight_type = clean_str(invoice_data.logistics.weight_type).upper()
|
||||
else:
|
||||
invoice_data.logistics.weight_type = existing_invoice.logistics.weight_type if existing_invoice.logistics else None
|
||||
if invoice_data.logistics:
|
||||
if hasattr(invoice_data.logistics, 'weight_type') and not invoice_data.logistics.weight_type:
|
||||
invoice_data.logistics.weight_type = existing_invoice.logistics.weight_type if existing_invoice.logistics else None
|
||||
elif hasattr(invoice_data.logistics, 'weight_type'):
|
||||
invoice_data.logistics.weight_type = clean_str(invoice_data.logistics.weight_type).upper()
|
||||
|
||||
# Columna Z: E-Document (Opcional)
|
||||
if invoice_data.compliance_mx and invoice_data.compliance_mx.edocument:
|
||||
invoice_data.compliance_mx.edocument = clean_str(invoice_data.compliance_mx.edocument)
|
||||
else:
|
||||
invoice_data.compliance_mx.edocument = existing_invoice.compliance_mx.edocument if existing_invoice.compliance_mx else None
|
||||
if invoice_data.compliance_mx:
|
||||
if not invoice_data.compliance_mx.edocument:
|
||||
invoice_data.compliance_mx.edocument = existing_invoice.compliance_mx.edocument if existing_invoice.compliance_mx else None
|
||||
else:
|
||||
invoice_data.compliance_mx.edocument = clean_str(invoice_data.compliance_mx.edocument)
|
||||
|
||||
# Columna AA: Num. Operación (Opcional)
|
||||
if invoice_data.compliance_mx and invoice_data.compliance_mx.vucem_operation_num:
|
||||
invoice_data.compliance_mx.vucem_operation_num = clean_str(invoice_data.compliance_mx.vucem_operation_num)
|
||||
else:
|
||||
invoice_data.compliance_mx.vucem_operation_num = existing_invoice.compliance_mx.vucem_operation_num if existing_invoice.compliance_mx else None
|
||||
if invoice_data.compliance_mx:
|
||||
if not invoice_data.compliance_mx.vucem_operation_num:
|
||||
invoice_data.compliance_mx.vucem_operation_num = existing_invoice.compliance_mx.vucem_operation_num if existing_invoice.compliance_mx else None
|
||||
else:
|
||||
invoice_data.compliance_mx.vucem_operation_num = clean_str(invoice_data.compliance_mx.vucem_operation_num)
|
||||
|
||||
# Columna AB: Aduana (OBLIGATORIO)
|
||||
if invoice_data.compliance_mx and invoice_data.compliance_mx.aduana:
|
||||
invoice_data.compliance_mx.aduana = clean_str(invoice_data.compliance_mx.aduana)
|
||||
else:
|
||||
invoice_data.compliance_mx.aduana = existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None
|
||||
if invoice_data.compliance_mx:
|
||||
if not invoice_data.compliance_mx.aduana:
|
||||
invoice_data.compliance_mx.aduana = existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None
|
||||
else:
|
||||
invoice_data.compliance_mx.aduana = clean_str(invoice_data.compliance_mx.aduana)
|
||||
|
||||
# Validar que aduana sea obligatorio (excepto para MEX)
|
||||
if existing_invoice.invoice_type != "MEX":
|
||||
if not invoice_data.compliance_mx or not invoice_data.compliance_mx.aduana:
|
||||
current_aduana = invoice_data.compliance_mx.aduana if invoice_data.compliance_mx else (existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None)
|
||||
if not current_aduana:
|
||||
errors.add_required_error("aduana")
|
||||
|
||||
# Columna AC: Sección de Despacho / Puerto de Entrada (Opcional)
|
||||
if invoice_data.compliance_mx and invoice_data.compliance_mx.port_of_entry:
|
||||
invoice_data.compliance_mx.port_of_entry = clean_str(invoice_data.compliance_mx.port_of_entry)
|
||||
else:
|
||||
invoice_data.compliance_mx.port_of_entry = existing_invoice.compliance_mx.port_of_entry if existing_invoice.compliance_mx else None
|
||||
if invoice_data.compliance_mx:
|
||||
if not invoice_data.compliance_mx.port_of_entry:
|
||||
invoice_data.compliance_mx.port_of_entry = existing_invoice.compliance_mx.port_of_entry if existing_invoice.compliance_mx else None
|
||||
else:
|
||||
invoice_data.compliance_mx.port_of_entry = clean_str(invoice_data.compliance_mx.port_of_entry)
|
||||
|
||||
# Columna AD: Observación en Español (Opcional)
|
||||
if invoice_data.observation_es:
|
||||
invoice_data.observation_es = clean_str(invoice_data.observation_es)
|
||||
else:
|
||||
if not invoice_data.observation_es:
|
||||
invoice_data.observation_es = existing_invoice.observation_es
|
||||
else:
|
||||
invoice_data.observation_es = clean_str(invoice_data.observation_es)
|
||||
|
||||
# Columna AD: Observación en Inglés (Opcional)
|
||||
if invoice_data.observation_en:
|
||||
invoice_data.observation_en = clean_str(invoice_data.observation_en)
|
||||
else:
|
||||
if not invoice_data.observation_en:
|
||||
invoice_data.observation_en = existing_invoice.observation_en
|
||||
else:
|
||||
invoice_data.observation_en = clean_str(invoice_data.observation_en)
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import traceback
|
||||
from typing import Optional, List, Tuple
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from core.exceptions import ErrorCollector, DuplicateResourceException
|
||||
from core.context import get_user_context
|
||||
from .common.mappers import clean_dict
|
||||
from .imports.temporary.validators.create import validate_create
|
||||
from .imports.temporary.validators.update import validate_update
|
||||
@@ -10,6 +12,26 @@ from .common.common_validators import invoice_exists
|
||||
from . import models, schemas
|
||||
|
||||
|
||||
def _get_current_username() -> str:
|
||||
"""Helper to get current username from context or fallback to System"""
|
||||
try:
|
||||
context = get_user_context()
|
||||
if context:
|
||||
# Token usually has 'preferred_username' or 'name' or 'sub'
|
||||
username = (
|
||||
context.get("preferred_username")
|
||||
or context.get("email")
|
||||
or context.get("sub")
|
||||
or "System"
|
||||
)
|
||||
print(f"DEBUG: _get_current_username found context: {username}")
|
||||
return username
|
||||
except Exception:
|
||||
pass
|
||||
print("DEBUG: _get_current_username NO context found, using System")
|
||||
return "System"
|
||||
|
||||
|
||||
class InvoiceService:
|
||||
"""Service for Invoice Header operations"""
|
||||
|
||||
@@ -46,7 +68,7 @@ class InvoiceService:
|
||||
# Apply filters if provided
|
||||
if filters:
|
||||
if filters.get("status"):
|
||||
query = query.filter(models.InvoiceHeader.status == filters["status"])
|
||||
query = query.filter(models.InvoiceHeader.is_updated == filters["status"])
|
||||
if filters.get("operation_type"):
|
||||
query = query.filter(
|
||||
models.InvoiceHeader.operation_type == filters["operation_type"]
|
||||
@@ -129,6 +151,11 @@ class InvoiceService:
|
||||
invoice_dict["tenant_id"] = tenant_id
|
||||
invoice_dict["company_id"] = company_id
|
||||
|
||||
# Automatic status and audit fields
|
||||
username = _get_current_username()
|
||||
invoice_dict["capture_user"] = username
|
||||
invoice_dict["who_updated"] = username
|
||||
|
||||
# Ensure document_type respects DB constraints for MEX invoices (bypass clean_dict)
|
||||
if invoice_dict.get("invoice_type") == "MEX" and not invoice_dict.get("document_type"):
|
||||
invoice_dict["document_type"] = None
|
||||
@@ -253,6 +280,7 @@ class InvoiceService:
|
||||
# Update main invoice header fields
|
||||
update_dict = invoice_data.model_dump(
|
||||
exclude={
|
||||
"id",
|
||||
"compliance_mx",
|
||||
"financials",
|
||||
"logistics",
|
||||
@@ -264,6 +292,16 @@ class InvoiceService:
|
||||
for key, value in update_dict.items():
|
||||
setattr(invoice, key, value)
|
||||
|
||||
# Audit update fields
|
||||
username = _get_current_username()
|
||||
invoice.who_updated = username
|
||||
invoice.updated_date = func.now()
|
||||
|
||||
# Backfill capture_user if missing or previous generic 'System'
|
||||
if not invoice.capture_user or invoice.capture_user == "System":
|
||||
if username != "System":
|
||||
invoice.capture_user = username
|
||||
|
||||
# Update compliance_mx if provided
|
||||
if invoice_data.compliance_mx is not None:
|
||||
if invoice.compliance_mx:
|
||||
|
||||
@@ -40,21 +40,21 @@ class PedimentoRectificationOrigin(Base, TenantScopedMixin, TimestampMixin):
|
||||
id: Mapped[int] = mapped_column(Integer)
|
||||
pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
|
||||
original_pedimento_year: Mapped[str] = mapped_column(String(2))
|
||||
original_customs_office: Mapped[str] = mapped_column(String(3))
|
||||
original_license: Mapped[str] = mapped_column(String(4))
|
||||
original_pedimento_number: Mapped[str] = mapped_column(String(7))
|
||||
original_pedimento_code: Mapped[str] = mapped_column(String(2))
|
||||
original_payment_date: Mapped[datetime] = mapped_column(DateTime)
|
||||
total_cash: Mapped[int] = mapped_column(Integer)
|
||||
total_others: Mapped[int] = mapped_column(Integer)
|
||||
reason: Mapped[str] = mapped_column(String(255))
|
||||
charge_to_client: Mapped[int] = mapped_column(SmallInteger)
|
||||
use_original_payment_date_for_interest_calc: Mapped[int] = mapped_column(
|
||||
SmallInteger
|
||||
original_pedimento_year: Mapped[str | None] = mapped_column(String(2), nullable=True)
|
||||
original_customs_office: Mapped[str | None] = mapped_column(String(3), nullable=True)
|
||||
original_license: Mapped[str | None] = mapped_column(String(4), nullable=True)
|
||||
original_pedimento_number: Mapped[str | None] = mapped_column(String(7), nullable=True)
|
||||
original_pedimento_code: Mapped[str | None] = mapped_column(String(2), nullable=True)
|
||||
original_payment_date: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
total_cash: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
total_others: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
reason: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
charge_to_client: Mapped[int | None] = mapped_column(SmallInteger, nullable=True)
|
||||
use_original_payment_date_for_interest_calc: Mapped[int | None] = mapped_column(
|
||||
SmallInteger, nullable=True
|
||||
)
|
||||
manual_calculation: Mapped[int] = mapped_column(SmallInteger)
|
||||
original_pedimento_norms: Mapped[int] = mapped_column(SmallInteger)
|
||||
manual_calculation: Mapped[int | None] = mapped_column(SmallInteger, nullable=True)
|
||||
original_pedimento_norms: Mapped[int | None] = mapped_column(SmallInteger, nullable=True)
|
||||
|
||||
pedimento: Mapped["Pedimentos"] = relationship(
|
||||
"Pedimentos", back_populates="pedimento_rectification_origin"
|
||||
|
||||
@@ -28,6 +28,7 @@ class PedimentoRectificationDestinationService:
|
||||
.filter(
|
||||
PedimentoRectificationDestination.pedimento_id == pedimento_id,
|
||||
PedimentoRectificationDestination.tenant_id == tenant_id,
|
||||
PedimentoRectificationDestination.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -26,6 +26,7 @@ class PedimentoRectificationOriginService:
|
||||
.filter(
|
||||
PedimentoRectificationOrigin.pedimento_id == pedimento_id,
|
||||
PedimentoRectificationOrigin.tenant_id == tenant_id,
|
||||
PedimentoRectificationOrigin.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -148,7 +148,7 @@ class PedimentosService:
|
||||
Pedimento or None if not found
|
||||
"""
|
||||
query = db.query(Pedimentos).filter(
|
||||
Pedimentos.id == pedimento_id, Pedimentos.tenant_id == tenant_id, Pedimentos.company_id == company_id
|
||||
Pedimentos.id == pedimento_id, Pedimentos.tenant_id == tenant_id
|
||||
)
|
||||
|
||||
if company_id is not None:
|
||||
@@ -199,6 +199,25 @@ class PedimentosService:
|
||||
Created pedimento
|
||||
"""
|
||||
try:
|
||||
# Check for existing pedimento with same key (Year, Aduana, Patente, Number)
|
||||
# This avoids IntegrityError in many cases and provides a better error message.
|
||||
existing = db.query(Pedimentos).filter(
|
||||
Pedimentos.tenant_id == tenant_id,
|
||||
Pedimentos.company_id == company_id,
|
||||
Pedimentos.year == pedimento_data.year,
|
||||
Pedimentos.customs_office == pedimento_data.customs_office,
|
||||
Pedimentos.license == pedimento_data.license,
|
||||
Pedimentos.pedimento_number == pedimento_data.pedimento_number,
|
||||
Pedimentos.deleted_at.is_(None)
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
raise ValueError(
|
||||
f"Ya existe un pedimento con estos datos: {pedimento_data.year}-{pedimento_data.customs_office}-{pedimento_data.license}-{pedimento_data.pedimento_number}"
|
||||
)
|
||||
|
||||
# Extraer datos de tablas relacionadas
|
||||
|
||||
# Extraer datos de tablas relacionadas
|
||||
related_data = {
|
||||
'pedimento_dates': pedimento_data.pedimento_dates,
|
||||
@@ -329,11 +348,23 @@ class PedimentosService:
|
||||
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
# Detectar si es un error de pedimento duplicado
|
||||
error_msg = str(e.orig)
|
||||
if 'pedimentos_unique_key' in error_msg or 'duplicate key value violates unique constraint' in error_msg:
|
||||
logger.warning(f"Attempted to create duplicate pedimento: {e}")
|
||||
raise ValueError("Ya existe un pedimento con estos datos (Año, Aduana, Patente, Número)")
|
||||
# Detectar si es un error de integridad de duplicados o similar
|
||||
error_msg = str(e.orig).lower()
|
||||
|
||||
# Case-insensitive check and support for both Spanish and English common error patterns
|
||||
is_unique_violation = any(kw in error_msg for kw in [
|
||||
'pedimentos_unique_key',
|
||||
'unique constraint',
|
||||
'duplicate key',
|
||||
'duplicada',
|
||||
'unicidad',
|
||||
'ya existe'
|
||||
])
|
||||
|
||||
if is_unique_violation:
|
||||
logger.warning(f"Attempted to create duplicate pedimento or common record: {e}")
|
||||
raise ValueError("Ya existe un pedimento o registro relacionado con estos datos. Verifica los campos únicos.")
|
||||
|
||||
logger.error(f"Integrity error creating pedimento: {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
@@ -363,6 +394,9 @@ class PedimentosService:
|
||||
if not pedimento:
|
||||
return None
|
||||
|
||||
# Ensure company_id is set from the existing record
|
||||
company_id = pedimento.company_id
|
||||
|
||||
try:
|
||||
# Actualizar campos principales del pedimento
|
||||
update_data = pedimento_data.model_dump(exclude_unset=True, exclude={
|
||||
|
||||
@@ -4,7 +4,7 @@ CSV generation utilities for invoice movement reports.
|
||||
import csv
|
||||
import io
|
||||
from typing import List, Union
|
||||
from datetime import datetime
|
||||
from datetime import datetime, date
|
||||
|
||||
from .schemas import MovementItem, MovementItemDetailed, AllMovementsFilter
|
||||
|
||||
@@ -15,18 +15,18 @@ def generate_csv_from_movements(
|
||||
) -> str:
|
||||
"""
|
||||
Generate CSV content from movement items.
|
||||
|
||||
|
||||
Args:
|
||||
movements: List of movement items (normal or detailed)
|
||||
filters: Filter object containing report parameters
|
||||
|
||||
|
||||
Returns:
|
||||
CSV content as string
|
||||
"""
|
||||
output = io.StringIO()
|
||||
|
||||
|
||||
if filters.report_type.value.lower() == "normal":
|
||||
# Normal report - only fields that are actually populated
|
||||
# Normal report
|
||||
fieldnames = [
|
||||
# Identification
|
||||
'Factura', 'Pedimento', 'FechaFactura', 'ClavePed',
|
||||
@@ -43,31 +43,34 @@ def generate_csv_from_movements(
|
||||
# Metadata
|
||||
'BaseDeDatos', 'UsuarioCap', 'UsuarioAcr'
|
||||
]
|
||||
|
||||
|
||||
writer = csv.DictWriter(output, fieldnames=fieldnames, extrasaction='ignore')
|
||||
writer.writeheader()
|
||||
|
||||
|
||||
for movement in movements:
|
||||
row = movement.model_dump()
|
||||
# Format datetime fields
|
||||
row['FechaFactura'] = _format_datetime(row.get('FechaFactura'))
|
||||
row['Fecha_Pago'] = _format_datetime(row.get('Fecha_Pago'))
|
||||
|
||||
|
||||
# Format numeric fields
|
||||
row['ValorComercialMN'] = _format_decimal(row.get('ValorComercialMN'))
|
||||
row['TipoCambio'] = _format_decimal(row.get('TipoCambio'))
|
||||
row['ValorMPTemp'] = _format_decimal(row.get('ValorMPTemp'))
|
||||
row['ValorAgre'] = _format_decimal(row.get('ValorAgre'))
|
||||
|
||||
|
||||
writer.writerow(row)
|
||||
|
||||
|
||||
else:
|
||||
# Detailed report - only fields that are actually populated
|
||||
# Detailed report
|
||||
fieldnames = [
|
||||
# Identification
|
||||
'Linea', 'Factura', 'Pedimento', 'FechaFactura', 'ClavePed',
|
||||
# Parties (names only, no RFC/TaxID as they're not in queries)
|
||||
'Proveedor', 'VendidoA',
|
||||
# Parties
|
||||
'Proveedor', 'RFCProveedor', 'ProveedorTaxID',
|
||||
'VendidoA', 'VendidoARFC', 'VendidoATaxID',
|
||||
# Customs broker
|
||||
'AgenteAduanal', 'Patente',
|
||||
# Product
|
||||
'NumParte', 'DescripcionE', 'DescripcionI', 'CantidadIE', 'UniMed',
|
||||
# Classification
|
||||
@@ -76,8 +79,6 @@ def generate_csv_from_movements(
|
||||
'ValorComercialMN', 'TipoCambio', 'PesoNeto', 'PesoBruto',
|
||||
# Customs
|
||||
'TipoMovTemDef', 'Regimen', 'Aduana', 'Advalorem', 'Preferencia',
|
||||
# Customs Broker
|
||||
'AgenteAduanal', 'Patente',
|
||||
# References
|
||||
'OrdenCompraVenta', 'Remesa', 'PedimentoR1', 'EDocument', 'NumOperacionVU',
|
||||
# Identifiers
|
||||
@@ -90,10 +91,10 @@ def generate_csv_from_movements(
|
||||
'Estatus', 'BaseDeDatos', 'TipoExpo', 'EsCambioRegimen', 'Pedimento18',
|
||||
'UsuarioCap', 'UsuarioAcr'
|
||||
]
|
||||
|
||||
|
||||
writer = csv.DictWriter(output, fieldnames=fieldnames, extrasaction='ignore')
|
||||
writer.writeheader()
|
||||
|
||||
|
||||
for movement in movements:
|
||||
row = movement.model_dump()
|
||||
# Format datetime fields
|
||||
@@ -102,16 +103,16 @@ def generate_csv_from_movements(
|
||||
row['Fecha_Inicio'] = _format_datetime(row.get('Fecha_Inicio'))
|
||||
row['Fecha_Fin'] = _format_datetime(row.get('Fecha_Fin'))
|
||||
row['FechaEmision'] = _format_datetime(row.get('FechaEmision'))
|
||||
|
||||
|
||||
# Format numeric fields
|
||||
row['ValorComercialMN'] = _format_decimal(row.get('ValorComercialMN'))
|
||||
row['TipoCambio'] = _format_decimal(row.get('TipoCambio'))
|
||||
row['CantidadIE'] = _format_decimal(row.get('CantidadIE'))
|
||||
row['PesoNeto'] = _format_decimal(row.get('PesoNeto'))
|
||||
row['PesoBruto'] = _format_decimal(row.get('PesoBruto'))
|
||||
|
||||
|
||||
writer.writerow(row)
|
||||
|
||||
|
||||
csv_content = output.getvalue()
|
||||
output.close()
|
||||
return csv_content
|
||||
@@ -119,11 +120,25 @@ def generate_csv_from_movements(
|
||||
|
||||
def _format_datetime(dt) -> str:
|
||||
"""Format datetime for CSV export."""
|
||||
if isinstance(dt, datetime):
|
||||
return dt.strftime('%Y-%m-%d')
|
||||
elif isinstance(dt, str):
|
||||
return dt
|
||||
return ''
|
||||
if not dt or dt == '' or dt == '-' or dt == '0':
|
||||
return ''
|
||||
try:
|
||||
if isinstance(dt, str):
|
||||
if 'T' in dt:
|
||||
dt_obj = datetime.strptime(dt.split('T')[0], '%Y-%m-%d')
|
||||
elif len(dt) == 8 and dt.isdigit():
|
||||
dt_obj = datetime.strptime(dt, '%Y%m%d')
|
||||
elif '-' in dt:
|
||||
dt_obj = datetime.strptime(dt, '%Y-%m-%d')
|
||||
else:
|
||||
return dt
|
||||
elif isinstance(dt, (datetime, date)):
|
||||
dt_obj = dt
|
||||
else:
|
||||
return ''
|
||||
return dt_obj.strftime('%d/%m/%Y')
|
||||
except (ValueError, TypeError):
|
||||
return str(dt) if dt else ''
|
||||
|
||||
|
||||
def _format_decimal(value, decimals: int = 2) -> str:
|
||||
@@ -133,4 +148,4 @@ def _format_decimal(value, decimals: int = 2) -> str:
|
||||
try:
|
||||
return f"{float(value):.{decimals}f}"
|
||||
except (ValueError, TypeError):
|
||||
return str(value) if value else ''
|
||||
return str(value) if value else ''
|
||||
@@ -337,6 +337,7 @@ class DatabaseHelper:
|
||||
db: Database session
|
||||
pedimento: Original pedimento number
|
||||
ped_rectifica: Initial rectification pedimento from database field
|
||||
(already resolved from pedimento_rectification_origin JOIN in query)
|
||||
is_shelter: Shelter company flag
|
||||
|
||||
Returns:
|
||||
@@ -344,10 +345,11 @@ class DatabaseHelper:
|
||||
"""
|
||||
if is_shelter:
|
||||
# Shelter: use direct value from PedRectifica field
|
||||
return ped_rectifica
|
||||
result = ped_rectifica
|
||||
else:
|
||||
# Non-Shelter: implement BuscarRectificacion logic
|
||||
return DatabaseHelper._buscar_rectificacion(db, pedimento, ped_rectifica)
|
||||
result = DatabaseHelper._buscar_rectificacion(db, pedimento, ped_rectifica)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _buscar_rectificacion(
|
||||
@@ -357,41 +359,25 @@ class DatabaseHelper:
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
BUSCA ULTIMO PEDIMENTO DE RECTIFICACION
|
||||
Follows the rectification chain recursively until finding the final pedimento.
|
||||
|
||||
Clarion logic:
|
||||
- If PPedRec is empty, return ''
|
||||
- Otherwise, follow the chain using BUSCA_PEDIMENTO_R1 recursively
|
||||
- Return the last Pedimento2 in the chain if no circular reference
|
||||
- Return Pedimento1 if error (circular reference detected)
|
||||
Returns the rectification origin pedimento string already resolved by the query
|
||||
builder's JOIN on pedimento_rectification_origin.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
pedimento_orig: Original pedimento number
|
||||
ped_rec: Initial rectification pedimento
|
||||
pedimento_orig: Original pedimento number (e.g. "1234567")
|
||||
ped_rec: Rectification pedimento origin string already computed by the SQL JOIN
|
||||
(e.g. "25-470-8000-1234567")
|
||||
|
||||
Returns:
|
||||
Final pedimento in rectification chain or empty string
|
||||
The rectification origin string, or empty string if none.
|
||||
"""
|
||||
if not ped_rec:
|
||||
return ''
|
||||
|
||||
try:
|
||||
# Track visited pedimentos to detect circular references
|
||||
visited = set()
|
||||
visited.add(pedimento_orig)
|
||||
|
||||
# Start recursive search
|
||||
final_pedimento = DatabaseHelper._busca_pedimento_r1(
|
||||
db, ped_rec, visited
|
||||
)
|
||||
|
||||
# If successful, return final pedimento; otherwise return original rectification
|
||||
return final_pedimento if final_pedimento else ped_rec
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in BuscarRectificacion for {pedimento_orig}: {e}")
|
||||
return pedimento_orig
|
||||
# The ped_rec value already comes from the JOIN on pedimento_rectification_origin
|
||||
# in the query builder, so it is the directly stored origin pedimento.
|
||||
# Return it directly without any further recursive DB lookup.
|
||||
return ped_rec
|
||||
|
||||
@staticmethod
|
||||
def _busca_pedimento_r1(
|
||||
@@ -400,18 +386,12 @@ class DatabaseHelper:
|
||||
visited: set
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
BUSCA_PEDIMENTO_R1 ROUTINE - Recursive search for final rectification pedimento.
|
||||
|
||||
Clarion logic:
|
||||
- Fetch pedimento from QPedimentos table
|
||||
- If it has PedRectifica:
|
||||
- Check if already visited (circular reference = error)
|
||||
- Add to visited set and recurse with PedRectifica
|
||||
- Return the deepest pedimento found
|
||||
BUSCA_PEDIMENTO_R1 ROUTINE - Recursive search for final rectification pedimento
|
||||
using pedimento_rectification_origin table.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
pedimento: Current pedimento to check
|
||||
pedimento: Current pedimento number to check
|
||||
visited: Set of already visited pedimentos (prevents infinite loops)
|
||||
|
||||
Returns:
|
||||
@@ -423,35 +403,39 @@ class DatabaseHelper:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Query pedimentos table for ped_rectifica
|
||||
# Query pedimento_rectification_origin for the next pedimento in the chain.
|
||||
# NOTE: The a76.pedimentos table does NOT have a ped_rectifica column.
|
||||
# Rectification data lives in pedimento_rectification_origin.
|
||||
sql = text("""
|
||||
SELECT ped_rectifica
|
||||
FROM a76.pedimentos
|
||||
WHERE pedimento_number = :pedimento
|
||||
SELECT
|
||||
pro.original_pedimento_year || '-' || pro.original_customs_office ||
|
||||
'-' || pro.original_license || '-' || pro.original_pedimento_number AS ped_origen
|
||||
FROM a76.pedimento_rectification_origin pro
|
||||
INNER JOIN a76.pedimentos ped ON ped.id = pro.pedimento_id
|
||||
WHERE ped.pedimento_number = :pedimento
|
||||
AND pro.deleted_at IS NULL
|
||||
LIMIT 1
|
||||
""")
|
||||
result = db.execute(sql, {"pedimento": pedimento}).fetchone()
|
||||
|
||||
if result and result[0]:
|
||||
if result and result[0] and result[0].replace('-', '').strip():
|
||||
ped_rectifica_next = result[0]
|
||||
|
||||
# Add current pedimento to visited set
|
||||
visited.add(pedimento)
|
||||
|
||||
# Recurse with next rectification
|
||||
# Recurse with next rectification origin
|
||||
final_ped = DatabaseHelper._busca_pedimento_r1(
|
||||
db, ped_rectifica_next, visited
|
||||
)
|
||||
|
||||
# If recursion failed (circular ref), return None
|
||||
# Otherwise return the final pedimento found
|
||||
return final_ped if final_ped else pedimento
|
||||
else:
|
||||
# No more rectifications, this is the final pedimento
|
||||
return pedimento
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching rectification for pedimento {pedimento}: {e}")
|
||||
logger.error(f"Error fetching rectification origin for pedimento {pedimento}: {e}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -143,9 +143,9 @@ class DefinitiveImportService:
|
||||
UsuarioAcr=row[23], # C54 - UsuarioAct
|
||||
Fecha_Pago=parse_yyyymmdd_date(row[8]), # C13 - Fecha_Pago
|
||||
NumCaja=row[24], # C56 - Transporte + NumTrasporte
|
||||
tipo_pedimento=row[25], # C57 - Pedimento18 (Note: Schema doesn't have tipo_pedimento field, this might be extra)
|
||||
Pedimento18=row[25], # C57 - empty (index 25)
|
||||
AduanaCru=row[15], # C39 - Aduana_Cruce
|
||||
Lote=row[26] # C58 - LOTE
|
||||
Lote=row[26] # C58 - empty (index 26)
|
||||
)
|
||||
|
||||
movements.append(movement)
|
||||
|
||||
@@ -97,7 +97,7 @@ class ExportService:
|
||||
rectified_pedimento = DatabaseHelper.get_rectification_pedimento(
|
||||
db,
|
||||
row[1], # C2 - PedimentoExpo
|
||||
'', # PedRectifica not in aggregated query
|
||||
row[26], # C54 - PedRectifica
|
||||
filters.is_shelter
|
||||
)
|
||||
|
||||
|
||||
@@ -37,6 +37,35 @@ class ExportRepairService:
|
||||
) -> List[MovementItem]:
|
||||
"""
|
||||
Get export repair movements (normal mode - grouped by invoice).
|
||||
|
||||
Aggregated query column order (ExportRepairQueries.build_aggregated_query):
|
||||
[0] C1 - invoice_number
|
||||
[1] C2 - pedimento_number
|
||||
[2] C3 - invoice_date
|
||||
[3] C6 - estatus (AC/NA)
|
||||
[4] C7 - pedimento_code
|
||||
[5] C8 - regime
|
||||
[6] C11 - payment_date
|
||||
[7] C12 - remesa
|
||||
[8] C13 - exchange_rate (fecha_pago context)
|
||||
[9] C14 - provider_id
|
||||
[10] C15 - sold_to_id
|
||||
[11] C16 - customs_broker_id
|
||||
[12] C27 - purchase_order
|
||||
[13] C33 - customs_office ← AduanaCru
|
||||
[14] C34 - document_type ← TipoFactura / tipo_mov
|
||||
[15] C35 - id ← consecutivo
|
||||
[16] C40 - edocument ← EDocument
|
||||
[17] C41 - vucem_op_num ← NumOperacionVU
|
||||
[18] C48 - exchange_rate ← TipoCambio
|
||||
[19] C49 - emission_date
|
||||
[20] C50 - capture_user ← UsuarioCap
|
||||
[21] C51 - who_updated ← UsuarioAcr
|
||||
[22] C52 - carrier_id ← Transportista
|
||||
[23] C53 - transport ← NumCaja
|
||||
[24] total_me
|
||||
[25] total_mn
|
||||
[26] C54 - ped_r1 ← PedimentoR1
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
@@ -62,21 +91,19 @@ class ExportRepairService:
|
||||
movements = []
|
||||
|
||||
for row in results:
|
||||
factura = row[0] # C1 - FacturaExpo
|
||||
tipo_mov = row[14] # C34 - TipoFactura
|
||||
estatus = row[3] # C6 - Estatus (AC o NA)
|
||||
factura = row[0] # C1 - FacturaExpo
|
||||
tipo_mov = row[14] # C34 - TipoFactura
|
||||
estatus = row[3] # C6 - Estatus (AC o NA)
|
||||
|
||||
# Filtrar facturas según include_cancelled
|
||||
# Si include_cancelled=False, solo mostrar AC (is_updated=true)
|
||||
# Si include_cancelled=True, mostrar todas (AC y NA)
|
||||
if not filters.include_cancelled and estatus != 'AC':
|
||||
continue
|
||||
|
||||
consecutivo = row[15] # C35 - Consecutivo
|
||||
|
||||
# Totals come directly from GROUP BY query (no N+1 problem)
|
||||
total_me = row[24] # total_me from SUM aggregation
|
||||
total_mn = row[25] # total_mn from SUM aggregation
|
||||
total_me = row[24] # total_me
|
||||
total_mn = row[25] # total_mn
|
||||
|
||||
# Calculate exchange rate and value
|
||||
valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated(
|
||||
@@ -85,9 +112,9 @@ class ExportRepairService:
|
||||
valor_me=total_me,
|
||||
valor_mn=total_mn,
|
||||
tipo_cambio_db=row[18], # C48 - TipoCambio
|
||||
fecha_pago=row[6], # C11 - Fecha_Pago
|
||||
fecha_inicio='', # Not in aggregated query
|
||||
tipo_pedimento='', # Not in aggregated query
|
||||
fecha_pago=row[6], # C11 - Fecha_Pago
|
||||
fecha_inicio='',
|
||||
tipo_pedimento='',
|
||||
currency_type=filters.currency_type.value,
|
||||
exchange_rate_type=filters.exchange_rate_type.value,
|
||||
is_shelter=filters.is_shelter,
|
||||
@@ -95,11 +122,11 @@ class ExportRepairService:
|
||||
met_trans=met_trans
|
||||
)
|
||||
|
||||
# Get pedimento rectification
|
||||
# Get pedimento rectification (already resolved by SQL COALESCE)
|
||||
pedimento_r1 = DatabaseHelper.get_rectification_pedimento(
|
||||
db,
|
||||
row[1], # C2 - PedimentoExpo
|
||||
'', # PedRectifica not in aggregated query
|
||||
row[1], # C2 - PedimentoExpo
|
||||
row[26], # C54 - PedRectifica (pre-built by SQL)
|
||||
filters.is_shelter
|
||||
)
|
||||
|
||||
@@ -109,11 +136,11 @@ class ExportRepairService:
|
||||
# Build movement item
|
||||
movement = MovementItem(
|
||||
Factura=factura,
|
||||
Pedimento=row[1], # C2 - PedimentoExpo
|
||||
FechaFactura=parse_yyyymmdd_date(row[2]), # C3 - FechaFactura
|
||||
Estatus=row[3], # C6 - Estatus
|
||||
ClavePed=row[4], # C7 - ClavePed
|
||||
TipoMovTemDef=tipo_mov,
|
||||
Pedimento=row[1], # C2 - PedimentoExpo
|
||||
FechaFactura=parse_yyyymmdd_date(row[2]), # C3 - FechaFactura
|
||||
Estatus=row[3], # C6 - Estatus
|
||||
ClavePed=row[4], # C7 - ClavePed
|
||||
TipoMovTemDef=tipo_mov, # C34 - TipoFactura
|
||||
EsCambioRegimen='N',
|
||||
ValorMPTemp=valor_comercial,
|
||||
ValorComercialMN=valor_comercial,
|
||||
@@ -121,17 +148,17 @@ class ExportRepairService:
|
||||
ValorAgre=0.0,
|
||||
TipoExpo='EXPO REP',
|
||||
PedimentoR1=pedimento_r1,
|
||||
EDocument=row[16], # C40 - EDocument
|
||||
NumOperacionVU=row[17], # C41 - NumOperacionVU
|
||||
EDocument=row[16], # C40 - EDocument ← FIXED (was 17)
|
||||
NumOperacionVU=row[17], # C41 - NumOperacionVU ← FIXED (was 18)
|
||||
BaseDeDatos=filters.database_name,
|
||||
NumGafUni=num_gaf_uni,
|
||||
UsuarioCap=row[20], # C50 - UsuarioCap
|
||||
UsuarioAcr=row[21], # C51 - UsuarioAct
|
||||
Fecha_Pago=parse_yyyymmdd_date(row[6]), # C11 - Fecha_Pago
|
||||
NumCaja=row[23], # C53 - Transporte + NumTrasporte
|
||||
Pedimento18='', # Not in aggregated query
|
||||
AduanaCru=row[13], # C33 - customs_office
|
||||
Lote='' # Not in aggregated query
|
||||
UsuarioCap=row[20], # C50 - UsuarioCap ← FIXED (was 21)
|
||||
UsuarioAcr=row[21], # C51 - UsuarioAct ← FIXED (was 22)
|
||||
Fecha_Pago=parse_yyyymmdd_date(row[6]), # C11 - Fecha_Pago ← FIXED (was 8)
|
||||
NumCaja=row[23], # C53 - NumCaja
|
||||
Pedimento18='',
|
||||
AduanaCru=row[13], # C33 - customs_office ← FIXED (was 14)
|
||||
Lote=''
|
||||
)
|
||||
|
||||
movements.append(movement)
|
||||
@@ -248,7 +275,7 @@ class ExportRepairService:
|
||||
# Get pedimento rectification
|
||||
pedimento_r1 = DatabaseHelper.get_rectification_pedimento(
|
||||
db,
|
||||
row[1], # C2 - PedimentoExpo
|
||||
row[1], # C2 - PedimentoExpo
|
||||
row[38], # C39 - PedRectifica
|
||||
filters.is_shelter
|
||||
)
|
||||
@@ -258,19 +285,19 @@ class ExportRepairService:
|
||||
|
||||
# Build detailed movement item
|
||||
movement = MovementItemDetailed(
|
||||
Linea=row[41], # C42 - LineaExpo
|
||||
Factura=row[0], # C1 - FacturaExpo
|
||||
Pedimento=row[1], # C2 - PedimentoExpo
|
||||
FechaFactura=row[2], # C3 - FechaFactura
|
||||
Estatus=row[5], # C6 - Estatus
|
||||
ClavePed=row[6], # C7 - ClavePed
|
||||
TipoMovTemDef=row[33], # C34 - TipoFactura
|
||||
Linea=row[41], # C42 - LineaExpo
|
||||
Factura=row[0], # C1 - FacturaExpo
|
||||
Pedimento=row[1], # C2 - PedimentoExpo
|
||||
FechaFactura=row[2], # C3 - FechaFactura
|
||||
Estatus=row[5], # C6 - Estatus
|
||||
ClavePed=row[6], # C7 - ClavePed
|
||||
TipoMovTemDef=row[33], # C34 - TipoFactura
|
||||
EsCambioRegimen='N',
|
||||
Regimen=row[7], # C8 - Regimen
|
||||
Fecha_Inicio=row[8], # C9 - Fecha_Inicio
|
||||
Fecha_Fin=row[9], # C10 - Fecha_Fin
|
||||
Fecha_Pago=row[10], # C11 - Fecha_Pago
|
||||
Remesa=row[11], # C12 - Remesa
|
||||
Regimen=row[7], # C8 - Regimen
|
||||
Fecha_Inicio=row[8], # C9 - Fecha_Inicio
|
||||
Fecha_Fin=row[9], # C10 - Fecha_Fin
|
||||
Fecha_Pago=row[10], # C11 - Fecha_Pago
|
||||
Remesa=row[11], # C12 - Remesa
|
||||
Proveedor=proveedor_info.get('name'),
|
||||
RFCProveedor=proveedor_info.get('rfc'),
|
||||
ProveedorTaxID=proveedor_info.get('tax_id'),
|
||||
@@ -279,42 +306,42 @@ class ExportRepairService:
|
||||
VendidoATaxID=vendido_info.get('tax_id'),
|
||||
AgenteAduanal=agente_info.get('name'),
|
||||
Patente=agente_info.get('license'),
|
||||
NumParte=row[17], # C18 - Clase (NumParte)
|
||||
NumParte=row[17], # C18 - Clase (NumParte)
|
||||
DescripcionE=StringHelper.clean_text(row[18]), # C19
|
||||
DescripcionI=StringHelper.clean_text(row[19]), # C20
|
||||
CantidadIE=float(row[20]) if row[20] else 0.0, # C21
|
||||
UniMed=row[21], # C22
|
||||
UniMed=row[21], # C22
|
||||
ValorComercialMN=valor_comercial,
|
||||
TipoCambio=tipo_cambio,
|
||||
PesoNeto=peso_neto,
|
||||
PesoBruto=peso_bruto,
|
||||
OrdenCompraVenta=row[26], # C27 - OrdenCompra
|
||||
FraccionArancelaria=row[27], # C28 - FraccionExpo
|
||||
Preferencia=row[28], # C29 - TipoFraccion
|
||||
Sector=row[30], # C31 - Sector
|
||||
PaisOrigen=row[31], # C32 - PaisOrigen
|
||||
OrdenCompraVenta=row[26], # C27 - OrdenCompra
|
||||
FraccionArancelaria=row[27], # C28 - FraccionExpo
|
||||
Preferencia=row[28], # C29 - TipoFraccion
|
||||
Sector=row[30], # C31 - Sector
|
||||
PaisOrigen=row[31], # C32 - PaisOrigen
|
||||
Aduana=aduana_nombre,
|
||||
Advalorem=row[37], # C38 - EsSubPartida
|
||||
Advalorem=row[37], # C38 - EsSubPartida
|
||||
TipoExpo='EXPO REP',
|
||||
PedimentoR1=pedimento_r1,
|
||||
EDocument=row[39], # C40 - EDocument
|
||||
NumOperacionVU=row[40], # C41 - NumOperacionVU
|
||||
EDocument=row[39], # C40 - EDocument
|
||||
NumOperacionVU=row[40], # C41 - NumOperacionVU
|
||||
Series=series_info,
|
||||
Marca=StringHelper.clean_text(row[42]), # C43
|
||||
Marca=StringHelper.clean_text(row[42]), # C43
|
||||
Modelo=StringHelper.clean_text(row[43]), # C44
|
||||
FraccionAmericana=row[44], # C45 - FraccionAme
|
||||
ECCN=row[45], # C46 - ECCN
|
||||
FraccionAmericana=row[44], # C45 - FraccionAme
|
||||
ECCN=row[45], # C46 - ECCN
|
||||
SimboloEx=simbolo_ex,
|
||||
FechaEmision=row[48], # C49 - FechaFactura
|
||||
FechaEmision=row[48], # C49 - FechaFactura
|
||||
BaseDeDatos=filters.database_name,
|
||||
NumGafUni=num_gaf_uni,
|
||||
UsuarioCap=row[49], # C50 - UsuarioCap
|
||||
UsuarioAcr=row[50], # C51 - UsuarioAct
|
||||
Transportista=row[51], # C52 - Transportista
|
||||
NumCaja=row[52], # C53 - Transporte + NumTrasporte
|
||||
Pedimento18=row[53], # C54 - Pedimento18
|
||||
AduanaCru=row[32], # C33 - Aduana_Cruce
|
||||
Lote=row[54] # C55 - Lote
|
||||
UsuarioCap=row[49], # C50 - UsuarioCap
|
||||
UsuarioAcr=row[50], # C51 - UsuarioAct
|
||||
Transportista=row[51], # C52 - Transportista
|
||||
NumCaja=row[52], # C53 - Transporte + NumTrasporte
|
||||
Pedimento18=row[53], # C54 - Pedimento18
|
||||
AduanaCru=row[32], # C33 - Aduana_Cruce
|
||||
Lote=row[54] # C55 - Lote
|
||||
)
|
||||
|
||||
movements.append(movement)
|
||||
@@ -334,7 +361,6 @@ class ExportRepairService:
|
||||
where_conditions.append("ih.operation_type = 'exp'")
|
||||
# GOLDEN RULE: If movement_type is ALL, only filter by operation_type
|
||||
if filters.movement_type.value == "ALL":
|
||||
# ALL mode: bring all exports without filtering by specific invoice_type
|
||||
pass
|
||||
else:
|
||||
where_conditions.append("ih.invoice_type = 'REPAR'")
|
||||
@@ -345,9 +371,6 @@ class ExportRepairService:
|
||||
else:
|
||||
where_conditions.append(f"log.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND log.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')")
|
||||
|
||||
# Note: Status filter applied at Python level after CASE WHEN in SELECT
|
||||
# because is_updated doesn't directly represent AC/NA status
|
||||
|
||||
# Provider filter
|
||||
if filters.provider:
|
||||
where_conditions.append(f"cmp.provider_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.provider}')")
|
||||
@@ -397,4 +420,4 @@ class ExportRepairService:
|
||||
def _get_driver_badge(self, db: Session, db_name: str, factura: str) -> str:
|
||||
"""Get driver badge number for invoice."""
|
||||
# TODO: GConductor table not migrated to PostgreSQL yet
|
||||
return None
|
||||
return None
|
||||
@@ -29,7 +29,11 @@ class TemporaryImportQueries:
|
||||
COALESCE(cmp.customs_broker_id::text, '') AS C18,
|
||||
COALESCE(cmp.aduana, '') AS C38,
|
||||
ih.id AS C39,
|
||||
COALESCE(ped_r1.pedimento_number, '') AS C41,
|
||||
COALESCE(
|
||||
ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number,
|
||||
pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number,
|
||||
''
|
||||
) AS C41,
|
||||
COALESCE(cmp.edocument, '') AS C42,
|
||||
COALESCE(cmp.vucem_operation_num, '') AS C43,
|
||||
COALESCE(fin.exchange_rate, 0) AS C50,
|
||||
@@ -42,18 +46,38 @@ class TemporaryImportQueries:
|
||||
'' AS C57,
|
||||
'' AS C58,
|
||||
COALESCE(fin.value_me, 0) AS total_me,
|
||||
COALESCE(fin.value_mn, 0) AS total_mn
|
||||
COALESCE(fin.value_mn, 0) AS total_mn,
|
||||
COALESCE(lf_agg.sum_value_mxn, 0) AS valor_comercial_mn,
|
||||
COALESCE(lf_agg.sum_value_temp_mxn, 0) AS valor_mp_temp_mn,
|
||||
COALESCE(lf_agg.sum_value_added_mxn, 0) AS valor_agre_mn
|
||||
FROM a76.invoice_header ih
|
||||
LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = cmp.pedimento_r1
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_rect ON
|
||||
pro_rect.original_pedimento_year = ped.year AND
|
||||
pro_rect.original_customs_office = ped.customs_office AND
|
||||
pro_rect.original_license = ped.license AND
|
||||
pro_rect.original_pedimento_number = ped.pedimento_number
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id
|
||||
LEFT JOIN (
|
||||
SELECT i.invoice_id,
|
||||
SUM(COALESCE(lf.value_mxn, 0)) AS sum_value_mxn,
|
||||
SUM(COALESCE(lf.value_temp_material_mxn, 0)) AS sum_value_temp_mxn,
|
||||
SUM(COALESCE(lf.value_added_mxn, 0)) AS sum_value_added_mxn
|
||||
FROM a76.items i
|
||||
JOIN a76.item_lines il ON il.item_id = i.id
|
||||
JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
GROUP BY i.invoice_id
|
||||
) lf_agg ON lf_agg.invoice_id = ih.id
|
||||
WHERE ih.operation_type = 'imp'
|
||||
AND ih.invoice_type = 'TEM'
|
||||
AND {where_str}
|
||||
ORDER BY ih.invoice_number
|
||||
"""
|
||||
|
||||
|
||||
@staticmethod
|
||||
def build_main_query(db_name: str, where_str: str) -> str:
|
||||
@@ -101,9 +125,13 @@ class TemporaryImportQueries:
|
||||
COALESCE(cmp.aduana, '') AS C38,
|
||||
ih.id AS C39,
|
||||
FALSE AS C40,
|
||||
'' AS C41,
|
||||
COALESCE(cmp.edocument, '') AS C42,
|
||||
COALESCE(cmp.vucem_operation_num, '') AS C43,
|
||||
COALESCE(
|
||||
ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number,
|
||||
pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number,
|
||||
''
|
||||
) AS C41, -- [40] rectification_id
|
||||
cmp.edocument AS C42, -- [41]
|
||||
cmp.vucem_operation_num AS C43, -- [42]
|
||||
COALESCE(il.line_number, 0) AS C44,
|
||||
'' AS C45,
|
||||
'' AS C46,
|
||||
@@ -124,6 +152,13 @@ class TemporaryImportQueries:
|
||||
LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_rect ON
|
||||
pro_rect.original_pedimento_year = ped.year AND
|
||||
pro_rect.original_customs_office = ped.customs_office AND
|
||||
pro_rect.original_license = ped.license AND
|
||||
pro_rect.original_pedimento_number = ped.pedimento_number
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id
|
||||
LEFT JOIN a76.clients_and_providers prov ON prov.id = cmp.provider_id
|
||||
LEFT JOIN a76.clients_and_providers client ON client.id = cmp.sold_to_id
|
||||
LEFT JOIN a76.items i ON i.invoice_id = ih.id
|
||||
@@ -201,7 +236,11 @@ class DefinitiveImportQueries:
|
||||
COALESCE(ih.purchase_order, '') AS C31,
|
||||
COALESCE(cmp.aduana, '') AS C39,
|
||||
ih.id AS C35,
|
||||
COALESCE(ped_r1.pedimento_number, '') AS C42,
|
||||
COALESCE(
|
||||
ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number,
|
||||
pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number,
|
||||
''
|
||||
) AS C42,
|
||||
COALESCE(cmp.edocument, '') AS C43,
|
||||
COALESCE(cmp.vucem_operation_num, '') AS C44,
|
||||
COALESCE(fin.exchange_rate, 0) AS C51,
|
||||
@@ -219,7 +258,13 @@ class DefinitiveImportQueries:
|
||||
LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = cmp.pedimento_r1
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_rect ON
|
||||
pro_rect.original_pedimento_year = ped.year AND
|
||||
pro_rect.original_customs_office = ped.customs_office AND
|
||||
pro_rect.original_license = ped.license AND
|
||||
pro_rect.original_pedimento_number = ped.pedimento_number
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id
|
||||
WHERE ih.operation_type = 'imp'
|
||||
AND ih.invoice_type IN ('DEF', 'EXDEF', 'MATDE')
|
||||
AND {where_clause}
|
||||
@@ -269,7 +314,11 @@ class DefinitiveImportQueries:
|
||||
cmp.aduana AS C39, -- [38]
|
||||
il.material_type AS C40, -- [39]
|
||||
il.id AS C41, -- [40]
|
||||
'' AS C42, -- [41] rectification_id
|
||||
COALESCE(
|
||||
ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number,
|
||||
pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number,
|
||||
''
|
||||
) AS C42, -- [41] rectification_id
|
||||
cmp.edocument AS C43, -- [42]
|
||||
cmp.vucem_operation_num AS C44, -- [43]
|
||||
il.line_number AS C45, -- [44]
|
||||
@@ -293,6 +342,13 @@ class DefinitiveImportQueries:
|
||||
LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_rect ON
|
||||
pro_rect.original_pedimento_year = ped.year AND
|
||||
pro_rect.original_customs_office = ped.customs_office AND
|
||||
pro_rect.original_license = ped.license AND
|
||||
pro_rect.original_pedimento_number = ped.pedimento_number
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id
|
||||
LEFT JOIN a76.clients_and_providers prov ON prov.id = cmp.provider_id
|
||||
LEFT JOIN a76.clients_and_providers client ON client.id = cmp.sold_to_id
|
||||
LEFT JOIN a76.items itm ON itm.invoice_id = ih.id
|
||||
@@ -388,22 +444,34 @@ class RepairImportQueries:
|
||||
COALESCE(log.transport_num, '') AS C45,
|
||||
COALESCE(ped.pedimento_code, '') AS C47,
|
||||
COALESCE(fin.value_me, 0) AS total_me,
|
||||
COALESCE(fin.value_mn, 0) AS total_mn
|
||||
COALESCE(fin.value_mn, 0) AS total_mn,
|
||||
COALESCE(
|
||||
ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number,
|
||||
pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number,
|
||||
''
|
||||
) AS C48
|
||||
FROM a76.invoice_header ih
|
||||
LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id
|
||||
LEFT JOIN a76.items i ON i.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_rect ON
|
||||
pro_rect.original_pedimento_year = ped.year AND
|
||||
pro_rect.original_customs_office = ped.customs_office AND
|
||||
pro_rect.original_license = ped.license AND
|
||||
pro_rect.original_pedimento_number = ped.pedimento_number
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id
|
||||
WHERE ih.operation_type = 'imp'
|
||||
AND COALESCE(cmp.is_regime_change, false) = false
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM a76.item_lines il2
|
||||
SELECT 1 FROM a76.items i2
|
||||
INNER JOIN a76.item_lines il2 ON il2.item_id = i2.id
|
||||
INNER JOIN a24.fa_item_lines fil2 ON fil2.id = il2.id
|
||||
WHERE il2.item_id = i.id AND fil2.search_invoice IS NOT NULL
|
||||
WHERE i2.invoice_id = ih.id AND fil2.search_invoice IS NOT NULL
|
||||
{discharge_filter}
|
||||
)
|
||||
{"AND " + where_str if where_str else ""}
|
||||
{discharge_filter}
|
||||
ORDER BY ih.invoice_number
|
||||
"""
|
||||
|
||||
@@ -450,7 +518,11 @@ class RepairImportQueries:
|
||||
COALESCE(ped.customs_office, ''),
|
||||
ih.id,
|
||||
'P',
|
||||
'',
|
||||
COALESCE(
|
||||
ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number,
|
||||
pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number,
|
||||
''
|
||||
),
|
||||
COALESCE(cmp.edocument, ''),
|
||||
COALESCE(cmp.vucem_operation_num, ''),
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '),
|
||||
@@ -470,6 +542,13 @@ class RepairImportQueries:
|
||||
LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_rect ON
|
||||
pro_rect.original_pedimento_year = ped.year AND
|
||||
pro_rect.original_customs_office = ped.customs_office AND
|
||||
pro_rect.original_license = ped.license AND
|
||||
pro_rect.original_pedimento_number = ped.pedimento_number
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id
|
||||
LEFT JOIN a76.items itm ON itm.invoice_id = ih.id
|
||||
LEFT JOIN a76.item_lines il ON il.item_id = itm.id
|
||||
LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id
|
||||
@@ -568,12 +647,24 @@ class ExportQueries:
|
||||
COALESCE(ih.who_updated, '') AS C51,
|
||||
COALESCE(log.transport_id || ' ' || log.transport_num, '') AS C53,
|
||||
COALESCE(fin.value_me, 0) AS total_me,
|
||||
COALESCE(fin.value_mn, 0) AS total_mn
|
||||
COALESCE(fin.value_mn, 0) AS total_mn,
|
||||
COALESCE(
|
||||
ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number,
|
||||
pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number,
|
||||
''
|
||||
) AS C54
|
||||
FROM a76.invoice_header ih
|
||||
LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_rect ON
|
||||
pro_rect.original_pedimento_year = ped.year AND
|
||||
pro_rect.original_customs_office = ped.customs_office AND
|
||||
pro_rect.original_license = ped.license AND
|
||||
pro_rect.original_pedimento_number = ped.pedimento_number
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id
|
||||
WHERE {where_clause}
|
||||
ORDER BY ih.invoice_number
|
||||
"""
|
||||
@@ -620,7 +711,11 @@ class ExportQueries:
|
||||
lf.value_mxn AS C36, -- [35]
|
||||
lf.value_usd AS C37, -- [36]
|
||||
il.material_type AS C38, -- [37]
|
||||
'' AS C39, -- [38] rectification_id
|
||||
COALESCE(
|
||||
ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number,
|
||||
pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number,
|
||||
''
|
||||
) AS C39, -- [38] rectification_id
|
||||
cmp.edocument AS C40, -- [39]
|
||||
cmp.vucem_operation_num AS C41, -- [40]
|
||||
il.line_number AS C42, -- [41]
|
||||
@@ -647,6 +742,13 @@ class ExportQueries:
|
||||
LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_rect ON
|
||||
pro_rect.original_pedimento_year = ped.year AND
|
||||
pro_rect.original_customs_office = ped.customs_office AND
|
||||
pro_rect.original_license = ped.license AND
|
||||
pro_rect.original_pedimento_number = ped.pedimento_number
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id
|
||||
LEFT JOIN a76.items itm ON itm.invoice_id = ih.id
|
||||
LEFT JOIN a76.item_lines il ON il.item_id = itm.id
|
||||
LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id
|
||||
@@ -745,12 +847,24 @@ class ExportRepairQueries:
|
||||
COALESCE(log.carrier_id, '') AS C52,
|
||||
COALESCE(log.transport_id || ' ' || log.transport_num, '') AS C53,
|
||||
COALESCE(fin.value_me, 0) AS total_me,
|
||||
COALESCE(fin.value_mn, 0) AS total_mn
|
||||
COALESCE(fin.value_mn, 0) AS total_mn,
|
||||
COALESCE(
|
||||
ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number,
|
||||
pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number,
|
||||
''
|
||||
) AS C54
|
||||
FROM a76.invoice_header ih
|
||||
LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_rect ON
|
||||
pro_rect.original_pedimento_year = ped.year AND
|
||||
pro_rect.original_customs_office = ped.customs_office AND
|
||||
pro_rect.original_license = ped.license AND
|
||||
pro_rect.original_pedimento_number = ped.pedimento_number
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id
|
||||
WHERE ih.operation_type = 'exp'
|
||||
AND ih.invoice_type = 'REPAR'
|
||||
{"AND " + where_str if where_str else ""}
|
||||
@@ -800,7 +914,11 @@ class ExportRepairQueries:
|
||||
COALESCE(lf.value_mxn, 0) AS C36,
|
||||
COALESCE(lf.value_usd, 0) AS C37,
|
||||
'P' AS C38,
|
||||
'' AS C39,
|
||||
COALESCE(
|
||||
ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number,
|
||||
pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number,
|
||||
''
|
||||
) AS C39,
|
||||
COALESCE(cmp.edocument, '') AS C40,
|
||||
COALESCE(cmp.vucem_operation_num, '') AS C41,
|
||||
COALESCE(il.line_number, 0) AS C42,
|
||||
@@ -826,6 +944,13 @@ class ExportRepairQueries:
|
||||
LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id
|
||||
LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id
|
||||
LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id
|
||||
LEFT JOIN a76.pedimento_rectification_origin pro_rect ON
|
||||
pro_rect.original_pedimento_year = ped.year AND
|
||||
pro_rect.original_customs_office = ped.customs_office AND
|
||||
pro_rect.original_license = ped.license AND
|
||||
pro_rect.original_pedimento_number = ped.pedimento_number
|
||||
LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id
|
||||
LEFT JOIN a76.items itm ON itm.invoice_id = ih.id
|
||||
LEFT JOIN a76.item_lines il ON il.item_id = itm.id
|
||||
LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id
|
||||
|
||||
@@ -114,8 +114,8 @@ class RepairImportService:
|
||||
# Get pedimento rectification
|
||||
pedimento_r1 = DatabaseHelper.get_rectification_pedimento(
|
||||
db,
|
||||
row[1], # C3 - PedimentoImpoRep
|
||||
'', # PedRectifica not in aggregated query
|
||||
row[1], # C3 - PedimentoImpoRep
|
||||
row[26], # C48 - PedRectifica
|
||||
filters.is_shelter
|
||||
)
|
||||
|
||||
@@ -258,7 +258,7 @@ class RepairImportService:
|
||||
)
|
||||
|
||||
pedimento_r1 = DatabaseHelper.get_rectification_pedimento(
|
||||
db, row[2], row[41], filters.is_shelter
|
||||
db, row[2], row[32], filters.is_shelter
|
||||
)
|
||||
|
||||
num_gaf_uni = DatabaseHelper.get_driver_badge(
|
||||
|
||||
@@ -94,6 +94,9 @@ class TemporaryImportService:
|
||||
# Correct indices based on TemporaryImportQueries.build_aggregated_query
|
||||
total_me = to_float(row[28]) # total_me (index 28)
|
||||
total_mn = to_float(row[29]) # total_mn (index 29)
|
||||
valor_comercial_mn = to_float(row[30]) # valor_comercial_mn from item_line_financials
|
||||
valor_mp_temp_mn = to_float(row[31]) # valor_mp_temp_mn from item_line_financials
|
||||
valor_agre_mn = to_float(row[32]) # valor_agre_mn from item_line_financials
|
||||
|
||||
# Calculate exchange rate and value
|
||||
valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated(
|
||||
@@ -134,10 +137,10 @@ class TemporaryImportService:
|
||||
ClavePed=row[4], # C5 - ClavePed
|
||||
TipoMovTemDef='IMTEM',
|
||||
EsCambioRegimen='N',
|
||||
ValorMPTemp=valor_comercial,
|
||||
ValorComercialMN=valor_comercial,
|
||||
ValorMPTemp=valor_mp_temp_mn,
|
||||
ValorComercialMN=valor_comercial_mn,
|
||||
TipoCambio=tipo_cambio,
|
||||
ValorAgre=0.0,
|
||||
ValorAgre=valor_agre_mn,
|
||||
TipoExpo='',
|
||||
PedimentoR1=pedimento_r1,
|
||||
EDocument=row[17], # C42 - EDocument
|
||||
@@ -147,10 +150,10 @@ class TemporaryImportService:
|
||||
UsuarioCap=row[21], # C52 - UsuarioCap
|
||||
UsuarioAcr=row[22], # C53 - UsuarioAct
|
||||
Fecha_Pago=parse_yyyymmdd_date(none_if_empty(row[8])), # C13 - Fecha_Pago
|
||||
NumCaja=row[23], # C55 - Transporte + NumTrasporte
|
||||
Pedimento18=row[24], # C56 - Pedimento18 (Actually empty in query, but safe to keep)
|
||||
AduanaCru=row[14], # C38 - Aduana_Cruce
|
||||
Lote=row[25] # C57 - LOTE (Actually C55 is index 24. C56 is 25)
|
||||
NumCaja=row[24], # C55 - transport_num || license_plate (index 24)
|
||||
Pedimento18=row[25], # C56 - '' empty (index 25)
|
||||
AduanaCru=row[14], # C38 - Aduana_Cruce (index 14)
|
||||
Lote=row[26] # C57 - '' empty (index 26)
|
||||
)
|
||||
|
||||
movements.append(movement)
|
||||
|
||||
Reference in New Issue
Block a user