Merge pull request 'feature/items-validations' (#199) from feature/items-validations into development
Reviewed-on: ADUANASOFT/anexo76#199
This commit is contained in:
@@ -126,7 +126,10 @@ class InvoiceService:
|
||||
|
||||
# Validar si la factura ya existe
|
||||
invoice_exists(db, invoice_data.invoice_number, tenant_id, company_id, errors)
|
||||
validate_create_import(db, invoice_data, tenant_id, company_id, errors)
|
||||
if invoice_data.operation_type == "exp":
|
||||
validate_create_export(db, invoice_data, tenant_id, company_id, errors)
|
||||
else:
|
||||
validate_create_import(db, invoice_data, tenant_id, company_id, errors)
|
||||
|
||||
# Si hay errores, lanzar excepción ANTES de intentar crear
|
||||
errors.raise_if_errors("Error al crear la factura")
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from api.v1.modules.a76.invoices.models import InvoiceFinancials, InvoiceHeader, InvoiceLogistics
|
||||
|
||||
from ...models import LineItem
|
||||
from ...series.models import Serie
|
||||
from api.v1.modules.a76.items.models import LineItem
|
||||
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
|
||||
|
||||
def apply_calculations(
|
||||
db: Session, line: LineItem, tenant_id: int, company_id: int, line_number: int
|
||||
):
|
||||
#TODO: SSisGen Logic
|
||||
# if ssisgen.calcularcostounitarioenbaseavalortotalscaf = 1:
|
||||
# unit_cost_capture = line.financial.total_value / line.financial.total_value <-- habria que revisar por que esta asi, por que para mi no tiene sentido, pero es lo que esta en clarion
|
||||
calculate_values(db, line, tenant_id, company_id)
|
||||
|
||||
# ==========================================
|
||||
# LLEVASERIE / LlevaCodFDA defaults
|
||||
# ==========================================
|
||||
# EqiPex:LlevaCodFDA = 'N'
|
||||
line.has_fda_code = False
|
||||
|
||||
# ==========================================
|
||||
# PAGO IMPUESTO default: 'N' (False)
|
||||
# ==========================================
|
||||
if line.tax_payment is None:
|
||||
# TODO: Leer de SisExp:PagoImpuesto (preferencias del sistema)
|
||||
line.tax_payment = False
|
||||
|
||||
# ==========================================
|
||||
# FORMA DE PAGO default: '5'
|
||||
# ==========================================
|
||||
if not line.payment_method:
|
||||
# TODO: Leer de SisExp:FormaPago (preferencias del sistema)
|
||||
line.payment_method = "5"
|
||||
|
||||
# ==========================================
|
||||
# SUBPARTIDAS: EsSubPartida / ContieneSubP / IncuyeSubPartidas
|
||||
# ==========================================
|
||||
fa_data: FaLineItem = getattr(line, "fa_data", None)
|
||||
if fa_data is not None:
|
||||
if fa_data.is_subitem is None:
|
||||
fa_data.is_subitem = False
|
||||
|
||||
if not fa_data.is_subitem:
|
||||
# Es partida principal — subitem_number se fuerza a 0
|
||||
fa_data.subitem_number = 0
|
||||
|
||||
# ContieneSubP: verificar si ya existen subitems en DB que referencian esta línea
|
||||
# (útil en updates; en create siempre será False porque la línea aún no existe)
|
||||
existing_subitems = (
|
||||
db.query(FaLineItem)
|
||||
.join(LineItem, FaLineItem.id == LineItem.id)
|
||||
.filter(
|
||||
LineItem.invoice_id == line.invoice_id,
|
||||
LineItem.line_number == line_number,
|
||||
LineItem.tenant_id == tenant_id,
|
||||
LineItem.company_id == company_id,
|
||||
FaLineItem.is_subitem == True,
|
||||
FaLineItem.subitem_number == line_number,
|
||||
)
|
||||
.count()
|
||||
)
|
||||
fa_data.contains_subitems = existing_subitems > 0
|
||||
|
||||
invoice_date = db.query(InvoiceHeader.invoice_date).filter(InvoiceHeader.id == line.invoice_id, InvoiceHeader.tenant_id == tenant_id, InvoiceHeader.company_id == company_id).scalar()
|
||||
|
||||
line.depreciation_date = invoice_date
|
||||
|
||||
if (not line.description.description_spanish and not line.description.description_english) and (line.part_info.description_spanish and line.part_info.description_english):
|
||||
line.description.description_spanish = line.part_info.description_spanish
|
||||
line.description.description_english = line.part_info.description_english
|
||||
else:
|
||||
if not line.description.description_spanish:
|
||||
class_desc = (
|
||||
db.query(Class.description_es, Class.description_en)
|
||||
.filter(Class.id == line.class_id, Class.tenant_id == tenant_id, Class.company_id == company_id)
|
||||
.first()
|
||||
)
|
||||
if class_desc:
|
||||
line.description.description_spanish, line.description.description_english = class_desc
|
||||
|
||||
|
||||
def calculate_values(
|
||||
db: Session,
|
||||
line: LineItem,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
) -> None:
|
||||
"""
|
||||
Calcula valores financieros y copia campos de la línea de importación referenciada.
|
||||
|
||||
Traduce el CALCULOS ROUTINE de Clarion:
|
||||
- Busca la factura de importación por fa_data.search_invoice (TEM → DEF como fallback)
|
||||
- Copia clase, unidad de medida, fracción (si fa_data.download), país, tipo fracción,
|
||||
bultos y descripción inglés desde la línea de importación encontrada
|
||||
- Calcula valores en moneda (USD/MXN/MC) según la moneda de la factura
|
||||
"""
|
||||
fa_data: FaLineItem = getattr(line, "fa_data", None)
|
||||
|
||||
# ==========================================
|
||||
# BUSCAR FACTURA DE IMPORTACIÓN (TEM → DEF)
|
||||
# EqiFim:FacturaImpo = fa_data.search_invoice / EqiPim:LineaImpo = fa_data.search_line
|
||||
# ==========================================
|
||||
import_line = None
|
||||
|
||||
import_invoice_number = fa_data.search_invoice if fa_data else None
|
||||
import_line_number = fa_data.search_line if fa_data else None
|
||||
|
||||
|
||||
if import_invoice_number and import_line_number:
|
||||
# 1. Intentar TEM (Importación Temporal)
|
||||
tem_invoice = (
|
||||
db.query(InvoiceHeader)
|
||||
.filter(
|
||||
InvoiceHeader.invoice_number == import_invoice_number,
|
||||
InvoiceHeader.invoice_type == "TEM",
|
||||
InvoiceHeader.tenant_id == tenant_id,
|
||||
InvoiceHeader.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if tem_invoice:
|
||||
import_line = (
|
||||
db.query(LineItem)
|
||||
.filter(
|
||||
LineItem.invoice_id == tem_invoice.id,
|
||||
LineItem.line_number == import_line_number,
|
||||
LineItem.tenant_id == tenant_id,
|
||||
LineItem.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
# 2. Si no hay TEM, intentar DEF (Importación Definitiva)
|
||||
if not import_line:
|
||||
def_invoice = (
|
||||
db.query(InvoiceHeader)
|
||||
.filter(
|
||||
InvoiceHeader.invoice_number == import_invoice_number,
|
||||
InvoiceHeader.invoice_type == "DEF",
|
||||
InvoiceHeader.tenant_id == tenant_id,
|
||||
InvoiceHeader.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if def_invoice:
|
||||
import_line = (
|
||||
db.query(LineItem)
|
||||
.filter(
|
||||
LineItem.invoice_id == def_invoice.id,
|
||||
LineItem.line_number == import_line_number,
|
||||
LineItem.tenant_id == tenant_id,
|
||||
LineItem.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
# ==========================================
|
||||
# COPIAR CAMPOS DESDE LÍNEA DE IMPORTACIÓN
|
||||
# EqiPex:Clase, UnidadMedida, FraccionExpo (condicional), PaisOrigen,
|
||||
# TipoFraccion, CantBultos, ClaveBultos, DescripcionE
|
||||
# ==========================================
|
||||
if import_line:
|
||||
line.class_id = import_line.class_id
|
||||
line.unit_of_measure = import_line.unit_of_measure
|
||||
|
||||
# Fracción: copiar solo si fa_data.download == True (≡ ColumnaV != '')
|
||||
if fa_data and fa_data.download and import_line.customs:
|
||||
line.customs.fraction = import_line.customs.fraction
|
||||
|
||||
if import_line.customs:
|
||||
line.customs.origin_country = import_line.customs.origin_country
|
||||
line.customs.fraction_type = import_line.customs.fraction_type
|
||||
|
||||
if import_line.quantity:
|
||||
line.quantity.package_quantity = import_line.quantity.package_quantity
|
||||
line.quantity.package_id = import_line.quantity.package_id
|
||||
|
||||
if import_line.description:
|
||||
line.description.description_english = import_line.description.description_english
|
||||
|
||||
# ==========================================
|
||||
# CÁLCULOS DE VALORES EN MONEDA
|
||||
# foreign=ME, local=MN, manual=MC
|
||||
# ==========================================
|
||||
result = (
|
||||
db.query(InvoiceFinancials.currency, InvoiceFinancials.exchange_rate)
|
||||
.filter(
|
||||
InvoiceFinancials.invoice_id == line.invoice_id,
|
||||
InvoiceFinancials.tenant_id == tenant_id,
|
||||
InvoiceFinancials.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not result:
|
||||
return
|
||||
|
||||
currency, exchange_rate = result
|
||||
|
||||
if currency == "foreign": # ME
|
||||
line.financial.unit_cost_usd = line.financial.unit_cost_capture
|
||||
line.financial.value_usd = line.financial.unit_cost_usd * line.quantity.quantity
|
||||
line.financial.unit_cost_mxn = line.financial.unit_cost_capture * exchange_rate
|
||||
line.financial.value_mxn = line.financial.unit_cost_mxn * line.quantity.quantity
|
||||
line.financial.value_mc = line.financial.unit_cost_usd * line.quantity.quantity
|
||||
elif currency == "local": # MN
|
||||
line.financial.unit_cost_mxn = line.financial.unit_cost_capture
|
||||
line.financial.value_mxn = line.financial.unit_cost_mxn * line.quantity.quantity
|
||||
line.financial.unit_cost_usd = line.financial.unit_cost_capture / exchange_rate
|
||||
line.financial.value_usd = line.financial.unit_cost_usd * line.quantity.quantity
|
||||
line.financial.value_mc = line.financial.unit_cost_usd * line.quantity.quantity
|
||||
elif currency == "manual": # MC
|
||||
line.financial.unit_cost_usd = line.financial.unit_cost_capture / exchange_rate
|
||||
line.financial.value_usd = line.financial.unit_cost_usd * line.quantity.quantity
|
||||
line.financial.unit_cost_mxn = line.financial.unit_cost_usd * exchange_rate
|
||||
line.financial.value_mxn = line.financial.unit_cost_mxn * line.quantity.quantity
|
||||
line.financial.value_mc = line.financial.unit_cost_capture * line.quantity.quantity
|
||||
417
backend/api/v1/modules/a76/items/exports/validators/common.py
Normal file
417
backend/api/v1/modules/a76/items/exports/validators/common.py
Normal file
@@ -0,0 +1,417 @@
|
||||
from sqlalchemy import exists
|
||||
from sqlalchemy.orm import Session
|
||||
from api.v1.modules.a76.invoices.common.common_validators import invoice_exists_by_id
|
||||
from core.exceptions import ErrorCollector
|
||||
from sqlalchemy import func
|
||||
|
||||
from ...common.fractions import search_fraction_preference
|
||||
from ...common.common_validators import item_exists
|
||||
from ...models import LineItem
|
||||
from ...line_customs.models import FractionType, LineCustom
|
||||
from api.v1.modules.a76.items.schemas import LineItemCreate
|
||||
from api.v1.modules.a76.items.exports.validators.calculations import apply_calculations
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure
|
||||
from api.v1.modules.a76.general_catalogs.packages.models import Package
|
||||
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
from api.v1.modules.public.reference_data.countries.models import Country
|
||||
from api.v1.modules.public.reference_data.sectors.models import Sector
|
||||
from api.v1.modules.public.reference_data.valuation_methods.models import (
|
||||
ValuationMethod,
|
||||
)
|
||||
from api.v1.modules.a76.parts.models import Part
|
||||
from api.v1.modules.a76.general_catalogs.company.models import Company
|
||||
|
||||
|
||||
def validate_common(
|
||||
db: Session,
|
||||
line: LineItemCreate,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
errors: ErrorCollector,
|
||||
line_number: int,
|
||||
):
|
||||
invoice: InvoiceHeader = invoice_exists_by_id(
|
||||
db, line.invoice_id, tenant_id, company_id, errors
|
||||
)
|
||||
line_item: LineItem = item_exists(db, line.line_number, tenant_id, company_id)
|
||||
|
||||
fecha_factura = invoice.invoice_date if invoice else None
|
||||
fraction = None
|
||||
|
||||
class_ = db.query(Class).filter(Class.id == line.class_id).first()
|
||||
if not class_:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].class_id",
|
||||
message="La clase especificada no existe.",
|
||||
solution=["Darla de alta en el catalogo de clases."],
|
||||
code="CLASS_NOT_FOUND",
|
||||
)
|
||||
else:
|
||||
if not line.unit_of_measure and not class_.unit_of_measure:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].unit_of_measure",
|
||||
message="La unidad de medida es obligatoria para la clase especificada.",
|
||||
solution=["Proporciona una unidad de medida valida."],
|
||||
code="UNIT_OF_MEASURE_REQUIRED",
|
||||
)
|
||||
|
||||
if not line.customs.fraction:
|
||||
if not line_item:
|
||||
if not class_.fraction:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.fraction",
|
||||
message="La fracción arancelaria es obligatoria para la clase especificada.",
|
||||
solution=["Proporciona una fracción arancelaria valida."],
|
||||
code="FRACTION_REQUIRED",
|
||||
)
|
||||
else:
|
||||
fraction = class_.fraction
|
||||
else:
|
||||
if not line.customs.fraction:
|
||||
if not class_.fraction:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.fraction",
|
||||
message="La fracción arancelaria es obligatoria para la clase especificada.",
|
||||
solution=["Proporciona una fracción arancelaria valida."],
|
||||
code="FRACTION_REQUIRED",
|
||||
)
|
||||
else:
|
||||
fraction = class_.fraction
|
||||
else:
|
||||
if line_item:
|
||||
fraction = line.customs.fraction
|
||||
|
||||
if not line.description.description_spanish and not class_.description_es:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].description.description_spanish",
|
||||
message="La descripción en español es obligatoria para la clase especificada.",
|
||||
solution=["Proporciona una descripción en español valida."],
|
||||
code="DESCRIPTION_SPANISH_REQUIRED",
|
||||
)
|
||||
|
||||
if not line.description.description_english and not class_.description_en:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].description.description_english",
|
||||
message="La descripción en inglés es obligatoria para la clase especificada.",
|
||||
solution=["Proporciona una descripción en inglés valida."],
|
||||
code="DESCRIPTION_ENGLISH_REQUIRED",
|
||||
)
|
||||
|
||||
if line.quantity.quantity and line.quantity.quantity <= 0:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].quantity.quantity",
|
||||
message="La cantidad debe ser mayor a cero.",
|
||||
solution=["Proporciona una cantidad valida."],
|
||||
code="QUANTITY_MUST_BE_GREATER_THAN_ZERO",
|
||||
)
|
||||
|
||||
if line.unit_of_measure:
|
||||
um = (
|
||||
db.query(func.count(UnitOfMeasure.id))
|
||||
.filter(
|
||||
UnitOfMeasure.id == line.unit_of_measure,
|
||||
UnitOfMeasure.tenant_id == tenant_id,
|
||||
UnitOfMeasure.company_id == company_id,
|
||||
)
|
||||
.scalar()
|
||||
)
|
||||
if um == 0:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].unit_of_measure",
|
||||
message="La unidad de medida especificada no existe.",
|
||||
solution=["Proporciona una unidad de medida valida."],
|
||||
code="UNIT_OF_MEASURE_NOT_FOUND",
|
||||
)
|
||||
|
||||
if line.quantity.package_id:
|
||||
package = (
|
||||
db.query(func.count(Package.id))
|
||||
.filter(
|
||||
Package.id == line.quantity.package_id,
|
||||
Package.tenant_id == tenant_id,
|
||||
Package.company_id == company_id,
|
||||
)
|
||||
.scalar()
|
||||
)
|
||||
if package == 0:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].quantity.package_id",
|
||||
message="El paquete especificado no existe.",
|
||||
solution=["Proporciona un paquete valido."],
|
||||
code="PACKAGE_NOT_FOUND",
|
||||
)
|
||||
if not line.quantity.package_quantity:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].quantity.package_quantity",
|
||||
message="La cantidad de paquetes es obligatoria cuando se proporciona el paquete.",
|
||||
solution=["Proporciona una cantidad de paquetes valida."],
|
||||
code="PACKAGE_QUANTITY_REQUIRED",
|
||||
)
|
||||
if line.quantity.package_quantity <= 0:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].quantity.package_quantity",
|
||||
message="La cantidad de paquetes debe ser mayor a cero.",
|
||||
solution=["Proporciona una cantidad de paquetes valida."],
|
||||
code="PACKAGE_QUANTITY_MUST_BE_GREATER_THAN_ZERO",
|
||||
)
|
||||
else:
|
||||
if line.quantity.package_quantity and (line.quantity.package_quantity > 0 and not line.quantity.package_id):
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].quantity.package_id",
|
||||
message="El paquete es obligatorio cuando se proporciona la cantidad de paquetes.",
|
||||
solution=["Proporciona un paquete valido."],
|
||||
code="PACKAGE_ID_REQUIRED",
|
||||
)
|
||||
|
||||
# ==========================================
|
||||
# FA DATA: PROCEDENCIA Y REFERENCIA DE IMPORTACIÓN (Col. C / D / E / H)
|
||||
# ==========================================
|
||||
fa_data: FaLineItem = getattr(line, "fa_data", None)
|
||||
|
||||
if fa_data and fa_data.movement_type_import:
|
||||
# Col. C: debe ser 'TEM' o 'DEF'
|
||||
if fa_data.movement_type_import.upper() not in ("TEM", "DEF"):
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].fa_data.movement_type_import",
|
||||
message=f"La Procedencia '{fa_data.movement_type_import}' no es válida. Debe ser 'TEM' o 'DEF'.",
|
||||
solution=["Capturar una procedencia válida como TEM o DEF."],
|
||||
code="MOVEMENT_TYPE_IMPORT_INVALID",
|
||||
)
|
||||
elif fa_data.search_invoice:
|
||||
tipo_label = "Temporales" if fa_data.movement_type_import.upper() == "TEM" else "Definitivas"
|
||||
tipo_label_sg = "Temporal" if fa_data.movement_type_import.upper() == "TEM" else "Definitiva"
|
||||
|
||||
# Col. D: validar que la factura de importación exista
|
||||
import_invoice: InvoiceHeader = (
|
||||
db.query(InvoiceHeader)
|
||||
.filter(
|
||||
InvoiceHeader.invoice_number == fa_data.search_invoice,
|
||||
InvoiceHeader.invoice_type == fa_data.movement_type_import.upper(),
|
||||
InvoiceHeader.tenant_id == tenant_id,
|
||||
InvoiceHeader.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not import_invoice:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].fa_data.search_invoice",
|
||||
message=f"La Factura '{fa_data.search_invoice}' de Importación {tipo_label_sg} no existe.",
|
||||
solution=[f"Capturar un Número de Factura que exista en el Catálogo de Importaciones {tipo_label}."],
|
||||
code="IMPORT_INVOICE_NOT_FOUND",
|
||||
)
|
||||
elif fa_data.search_line:
|
||||
# Col. D + E: validar que la línea de importación exista dentro de esa factura
|
||||
import_line: LineItem = (
|
||||
db.query(LineItem)
|
||||
.filter(
|
||||
LineItem.invoice_id == import_invoice.id,
|
||||
LineItem.line_number == fa_data.search_line,
|
||||
LineItem.tenant_id == tenant_id,
|
||||
LineItem.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not import_line:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].fa_data.search_line",
|
||||
message=f"La Factura '{fa_data.search_invoice}' con línea '{fa_data.search_line}' de Importación {tipo_label_sg} no existe.",
|
||||
solution=["Capturar un Número de Factura con diferente línea que esté en el Catálogo de Importaciones correspondiente."],
|
||||
code="IMPORT_LINE_NOT_FOUND",
|
||||
)
|
||||
elif (
|
||||
# Col. H: valida unidad de medida sólo cuando hay descarga
|
||||
fa_data.download is True
|
||||
and line.unit_of_measure
|
||||
and import_line.unit_of_measure
|
||||
and line.unit_of_measure != import_line.unit_of_measure
|
||||
):
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].unit_of_measure",
|
||||
message=(
|
||||
f"La Unidad de Medida '{line.unit_of_measure}' es diferente de "
|
||||
f"'{import_line.unit_of_measure}', que es la U.M. de la Factura "
|
||||
f"'{fa_data.search_invoice}' con línea '{fa_data.search_line}' "
|
||||
f"de Importación {tipo_label_sg}."
|
||||
),
|
||||
solution=["Capturar la Unidad de Medida correcta para el descargo de esta línea de Importación."],
|
||||
code="UNIT_OF_MEASURE_MISMATCH",
|
||||
)
|
||||
|
||||
country = None
|
||||
fraction_type = None
|
||||
sector = None
|
||||
if fraction:
|
||||
fraction = line.customs.fraction if line.customs.fraction else fraction
|
||||
|
||||
country = line.customs.origin_country
|
||||
if line_item and line_item.customs:
|
||||
country = (
|
||||
line_item.customs.origin_country
|
||||
if line_item.customs.origin_country
|
||||
else country
|
||||
)
|
||||
|
||||
fraction_type = line.customs.fraction_type.upper()
|
||||
if line_item and line_item.customs:
|
||||
fraction_type = (
|
||||
line_item.customs.fraction_type
|
||||
if line_item.customs.fraction_type
|
||||
else fraction_type
|
||||
)
|
||||
|
||||
sector = line.customs.sector
|
||||
if line_item and line_item.customs:
|
||||
sector = line_item.customs.sector if line_item.customs.sector else sector
|
||||
|
||||
country_m3 = db.query(Country.m3_key).filter(Country.m3_key == country).scalar()
|
||||
if not country_m3:
|
||||
country_m3 = (
|
||||
db.query(Country.m3_key).filter(Country.ame_key == country).scalar()
|
||||
)
|
||||
|
||||
country = country_m3
|
||||
if not country:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.origin_country",
|
||||
message="El país de origen especificado no existe.",
|
||||
solution=["Proporciona un país de origen valido."],
|
||||
code="ORIGIN_COUNTRY_NOT_FOUND",
|
||||
)
|
||||
else:
|
||||
if fraction_type.strip().upper() not in vars(FractionType).values():
|
||||
valid_types = [
|
||||
v
|
||||
for k, v in vars(FractionType).items()
|
||||
if not k.startswith("_") and isinstance(v, str)
|
||||
]
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.fraction_type",
|
||||
message="El tipo de fracción especificado no es válido.",
|
||||
solution=[
|
||||
f"Proporciona un tipo de fracción válido. Valores permitidos: {', '.join(valid_types)}"
|
||||
],
|
||||
code="FRACTION_TYPE_INVALID",
|
||||
value=fraction_type,
|
||||
)
|
||||
else:
|
||||
if fraction_type.strip().upper() == FractionType.PROSEC and not sector:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.sector",
|
||||
message="El sector es obligatorio cuando el tipo de fracción es 'PROSEC'.",
|
||||
solution=["Proporciona un sector valido."],
|
||||
code="SECTOR_REQUIRED_FOR_PROSEC",
|
||||
)
|
||||
elif fraction_type.strip().upper() != FractionType.PROSEC and sector:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.sector",
|
||||
message="El sector solo es aplicable cuando el tipo de fracción es 'PROSEC'.",
|
||||
solution=[
|
||||
"Elimina el sector o cambia el tipo de fracción a 'PROSEC'."
|
||||
],
|
||||
code="SECTOR_ONLY_FOR_PROSEC",
|
||||
)
|
||||
elif fraction_type.strip().upper() == FractionType.PROSEC and sector:
|
||||
sector_db: Sector = (
|
||||
db.query(Sector).filter(Sector.key == sector).scalar()
|
||||
)
|
||||
if sector_db:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.sector",
|
||||
message="El sector especificado no existe.",
|
||||
solution=["Proporciona un sector valido."],
|
||||
code="SECTOR_NOT_FOUND",
|
||||
)
|
||||
else:
|
||||
if not sector_db.authorized:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.sector",
|
||||
message="El sector especificado no está autorizado.",
|
||||
solution=["Proporciona un sector autorizado."],
|
||||
code="SECTOR_NOT_AUTHORIZED",
|
||||
)
|
||||
|
||||
company_db = db.query(Company).filter(Company.id == company_id).first()
|
||||
if not company_db.prosec:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.sector",
|
||||
message=" La empresa no cuenta con autorización PROSEC.",
|
||||
solution=[
|
||||
"Accese a los datos de la empresa y selecione la opción Pertenece al Programa de Promoción Sectorial y capture el número de permiso PROSEC."
|
||||
],
|
||||
code="COMPANY_NOT_AUTHORIZED_FOR_PROSEC",
|
||||
)
|
||||
|
||||
if fraction:
|
||||
search_fraction_preference(
|
||||
db=db,
|
||||
country=country,
|
||||
fraccion=fraction,
|
||||
fraction_type=fraction_type,
|
||||
sector=sector,
|
||||
invoice_date=fecha_factura,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
if line.customs.american_fraction:
|
||||
american_fraction_exists = db.query(
|
||||
exists().where(
|
||||
LineCustom.american_fraction == line.customs.american_fraction
|
||||
)
|
||||
).scalar()
|
||||
if not american_fraction_exists:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].customs.american_fraction",
|
||||
message="La fracción americana especificada no existe.",
|
||||
solution=["Proporciona una fracción americana valida."],
|
||||
code="AMERICAN_FRACTION_NOT_FOUND",
|
||||
)
|
||||
|
||||
if line.order:
|
||||
if len(line.order) > 20:
|
||||
errors.add_error(
|
||||
field=f"item.order",
|
||||
message="El campo orden no debe exceder los 20 caracteres.",
|
||||
solution=["Proporciona un valor valido para el campo orden."],
|
||||
code="ORDER_EXCEEDS_MAX_LENGTH",
|
||||
)
|
||||
|
||||
unit_of_measure = line.unit_of_measure or (
|
||||
class_.unit_of_measure if class_ else None
|
||||
)
|
||||
|
||||
#TODO: SSisGen Logic Restringer cantidades decimales para piezas, revisar si es necesario agregar validación similar para otras unidades de medida
|
||||
#TODO: SSisGen Logic Seguridad
|
||||
|
||||
if unit_of_measure == "PZA" and line.quantity.quantity % 1 != 0:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].quantity.quantity",
|
||||
message="La cantidad debe ser un número entero cuando la unidad de medida es PZA.",
|
||||
solution=["Proporciona una cantidad entera."],
|
||||
code="QUANTITY_MUST_BE_INTEGER_FOR_PIECES",
|
||||
)
|
||||
|
||||
if line.valuation_method:
|
||||
valuation_method_exists = db.query(
|
||||
exists().where(ValuationMethod.key == line.valuation_method)
|
||||
).scalar()
|
||||
if not valuation_method_exists:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].valuation_method",
|
||||
message="El método de valoración especificado no existe.",
|
||||
solution=["Proporciona un método de valoración valido."],
|
||||
code="VALUATION_METHOD_NOT_FOUND",
|
||||
)
|
||||
|
||||
if line.part_number_id:
|
||||
part_exists = db.query(exists().where(Part.id == line.part_number_id)).scalar()
|
||||
if not part_exists:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].part_number_id",
|
||||
message="El número de parte especificado no existe.",
|
||||
solution=["Proporciona un número de parte valido."],
|
||||
code="PART_NUMBER_NOT_FOUND",
|
||||
)
|
||||
|
||||
apply_calculations(db, line, tenant_id, company_id, line_number)
|
||||
394
backend/api/v1/modules/a76/items/exports/validators/create.py
Normal file
394
backend/api/v1/modules/a76/items/exports/validators/create.py
Normal file
@@ -0,0 +1,394 @@
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import func, exists
|
||||
from sqlalchemy.orm import Session
|
||||
from ...common.common_validators import count_items
|
||||
from core.exceptions import ErrorCollector
|
||||
|
||||
from ...models import LineItem
|
||||
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
from ...models import LineItem
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
from api.v1.modules.a76.general_catalogs.packages.models import Package
|
||||
from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import (
|
||||
USTariffFraction,
|
||||
)
|
||||
from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod
|
||||
from .common import validate_common
|
||||
|
||||
|
||||
def validate_create(
|
||||
db: Session,
|
||||
line: LineItem,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
errors: ErrorCollector,
|
||||
line_number: int,
|
||||
):
|
||||
"""
|
||||
Validates and calculates fields for a new line item before DB creation.
|
||||
Works with Pydantic schemas, modifying them in-place.
|
||||
|
||||
Args:
|
||||
line: LineItemCreate schema with nested data (financial, quantity, customs, etc.)
|
||||
invoice_id: ID of the invoice this line belongs to
|
||||
fa_data: FaLineItemCreateDTO or None (None for INV system)
|
||||
"""
|
||||
|
||||
# Access fa_data safely
|
||||
fa_data: FaLineItem = getattr(line, "fa_data", None)
|
||||
|
||||
# Required field validations
|
||||
if not line.class_id:
|
||||
errors.add_required_error(field=f"line[{line_number}].class_id")
|
||||
|
||||
if not line.quantity.quantity or line.quantity.quantity <= 0:
|
||||
errors.add_required_error(field=f"line[{line_number}].quantity.quantity")
|
||||
|
||||
# TODO: Añadir validacion SSisGen:CalcularCostoUnitarioEnBaseAValorTotalScaf <-- de la tabla de preferencias de el sistema
|
||||
# if SSisGen:CalcularCostoUnitarioEnBaseAValorTotalScaf == False:
|
||||
if fa_data and not fa_data.is_subitem:
|
||||
if (
|
||||
not line.financial.unit_cost_capture
|
||||
or line.financial.unit_cost_capture <= 0
|
||||
):
|
||||
errors.add_required_error(
|
||||
field=f"line[{line_number}].financial.unit_cost_capture"
|
||||
)
|
||||
|
||||
if not line.quantity.net_weight or line.quantity.net_weight <= 0:
|
||||
errors.add_required_error(field=f"line[{line_number}].quantity.net_weight")
|
||||
|
||||
if not line.customs.origin_country:
|
||||
errors.add_required_error(field=f"line[{line_number}].customs.origin_country")
|
||||
|
||||
if not line.customs.fraction_type:
|
||||
errors.add_required_error(field=f"line[{line_number}].customs.fraction_type")
|
||||
|
||||
# FA-specific validations
|
||||
if fa_data:
|
||||
# Col. C: Procedencia de la Importación (TipoMovImpo) — obligatorio
|
||||
if not fa_data.movement_type_import:
|
||||
errors.add_required_error(field=f"line[{line_number}].fa_data.movement_type_import")
|
||||
|
||||
# Col. F: ¿Descarga la línea? (DescargaPartida) — obligatorio
|
||||
if fa_data.download is None:
|
||||
errors.add_required_error(field=f"line[{line_number}].fa_data.download")
|
||||
|
||||
# Col. D / E: Factura y Línea de Impo — obligatorios sólo si hay descarga
|
||||
if fa_data.download is True:
|
||||
if not fa_data.search_invoice:
|
||||
errors.add_required_error(field=f"line[{line_number}].fa_data.search_invoice")
|
||||
if not fa_data.search_line:
|
||||
errors.add_required_error(field=f"line[{line_number}].fa_data.search_line")
|
||||
|
||||
if (
|
||||
fa_data.is_subitem and fa_data.contains_subitems
|
||||
) and not fa_data.subitem_number:
|
||||
errors.add_required_error(
|
||||
field=f"line[{line_number}].fa_data.subitem_number"
|
||||
)
|
||||
|
||||
# Validar que si es un subitem, existe un item principal correspondiente
|
||||
if (
|
||||
fa_data.is_subitem
|
||||
and fa_data.subitem_number
|
||||
and fa_data.subitem_number != 0
|
||||
):
|
||||
principal_item_exists = db.query(
|
||||
exists().where(
|
||||
(LineItem.id == FaLineItem.id)
|
||||
& (LineItem.id == LineItem.id)
|
||||
& (LineItem.invoice_id == line.invoice_id)
|
||||
& (LineItem.line_number == line_number)
|
||||
& (FaLineItem.is_subitem == False)
|
||||
& (FaLineItem.contains_subitems == True)
|
||||
& (LineItem.tenant_id == tenant_id)
|
||||
& (LineItem.company_id == company_id)
|
||||
)
|
||||
).scalar()
|
||||
|
||||
if not principal_item_exists:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}]",
|
||||
message=f"No existe un item principal registrado para esta linea {line_number} con subitem {fa_data.subitem_number}",
|
||||
solution=[
|
||||
"Registrar el item principal correspondiente a esta linea antes de registrar subitems."
|
||||
],
|
||||
code="SUBITEM_WITHOUT_PRINCIPAL_ITEM",
|
||||
)
|
||||
|
||||
if fa_data.is_subitem and (
|
||||
fa_data.subitem_number == 0 or not fa_data.subitem_number
|
||||
):
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}]",
|
||||
message=f"El número de subitem no puede ser 0 si la línea es un subitem.",
|
||||
solution=["Asignar un número de subitem mayor a 0 para esta línea."],
|
||||
code="SUBITEM_NUMBER_INVALID",
|
||||
)
|
||||
|
||||
validate_common(db, line, tenant_id, company_id, errors, line_number)
|
||||
|
||||
if not errors.has_errors():
|
||||
# Obtener la factura para acceder a tipo de cambio, moneda y peso
|
||||
invoice: InvoiceHeader = (
|
||||
db.query(InvoiceHeader)
|
||||
.filter(
|
||||
InvoiceHeader.id == line.invoice_id,
|
||||
InvoiceHeader.tenant_id == tenant_id,
|
||||
InvoiceHeader.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not invoice or not invoice.financials or not invoice.logistics:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}]",
|
||||
message="No se pudo obtener información de la factura",
|
||||
solution=[
|
||||
"Verificar que la factura existe y tiene datos financieros y logísticos"
|
||||
],
|
||||
code="INVOICE_DATA_MISSING",
|
||||
)
|
||||
return
|
||||
|
||||
# Obtener la clase para valores por defecto
|
||||
class_info: Class = (
|
||||
db.query(Class)
|
||||
.filter(
|
||||
Class.id == line.class_id,
|
||||
Class.tenant_id == tenant_id,
|
||||
Class.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
# ==========================================
|
||||
# ASIGNAR TIPO DE CAMBIO
|
||||
# ==========================================
|
||||
exchange_rate = invoice.financials.exchange_rate or Decimal("1.0")
|
||||
|
||||
# ==========================================
|
||||
# ASIGNAR UNIDAD DE MEDIDA
|
||||
# ==========================================
|
||||
# Si no se proporcionó unidad de medida, usar la de la clase
|
||||
if not line.unit_of_measure and class_info:
|
||||
line.unit_of_measure = class_info.unit_of_measure
|
||||
|
||||
# ==========================================
|
||||
# ASIGNAR TIPOS DE MONEDA Y CALCULAR COSTOS
|
||||
# ==========================================
|
||||
currency_type = invoice.financials.currency_type
|
||||
unit_cost_capture = line.financial.unit_cost_capture or Decimal("0")
|
||||
|
||||
# Calcular costos según tipo de moneda
|
||||
if currency_type == "USD" or currency_type == "ME": # Moneda Extranjera (ME)
|
||||
line.financial.unit_cost_capture = unit_cost_capture
|
||||
line.financial.unit_cost_usd = unit_cost_capture
|
||||
line.financial.unit_cost_mxn = unit_cost_capture * exchange_rate
|
||||
elif currency_type == "MXN" or currency_type == "MN": # Moneda Nacional (MN)
|
||||
line.financial.unit_cost_capture = unit_cost_capture
|
||||
line.financial.unit_cost_usd = (
|
||||
unit_cost_capture / exchange_rate if exchange_rate else Decimal("0")
|
||||
)
|
||||
line.financial.unit_cost_mxn = unit_cost_capture
|
||||
# Si es otro tipo de moneda, dejamos el costo como está
|
||||
|
||||
# Calcular valores totales basados en cantidad y costo unitario
|
||||
quantity = line.quantity.quantity or Decimal("0")
|
||||
|
||||
# Valor Comercial
|
||||
if line.financial.unit_cost_usd is not None:
|
||||
line.financial.value_usd = line.financial.unit_cost_usd * quantity
|
||||
if line.financial.unit_cost_mxn is not None:
|
||||
line.financial.value_mxn = line.financial.unit_cost_mxn * quantity
|
||||
|
||||
# Valor Aduanas (asumiendo que es igual al Valor Comercial por defecto)
|
||||
line.financial.customs_value_usd = line.financial.value_usd
|
||||
line.financial.customs_value_mxn = line.financial.value_mxn
|
||||
|
||||
# Valor MP Temp (Materia Prima Temporal)
|
||||
line.financial.value_temp_material_usd = line.financial.value_usd
|
||||
line.financial.value_temp_material_mxn = line.financial.value_mxn
|
||||
|
||||
# ==========================================
|
||||
# VALIDAR Y CONVERTIR PESOS NETOS
|
||||
# ==========================================
|
||||
invoice_weight_type = invoice.logistics.weight_type # 'kgs' o 'lbs'
|
||||
quantity = line.quantity.quantity or Decimal("0")
|
||||
net_weight_input = line.quantity.net_weight or Decimal("0")
|
||||
|
||||
# Determinar si la unidad de medida es de peso
|
||||
unit_is_kgs = line.unit_of_measure and line.unit_of_measure == "24" #KGS
|
||||
unit_is_lbs = line.unit_of_measure and line.unit_of_measure == "25" #LBS
|
||||
|
||||
# Calcular peso neto en kilogramos (estándar interno)
|
||||
if unit_is_kgs:
|
||||
if invoice_weight_type == "KGS":
|
||||
line.quantity.net_weight = quantity
|
||||
else: # invoice en libras
|
||||
line.quantity.net_weight = quantity * Decimal("2.204624")
|
||||
elif unit_is_lbs:
|
||||
if invoice_weight_type == "KGS":
|
||||
line.quantity.net_weight = quantity / Decimal("2.204624")
|
||||
else: # invoice en libras
|
||||
line.quantity.net_weight = quantity
|
||||
else:
|
||||
# Otra unidad de medida - usar peso capturado y convertir si es necesario
|
||||
if invoice_weight_type == "KGS":
|
||||
# El peso capturado está en kilos
|
||||
line.quantity.net_weight = net_weight_input
|
||||
else:
|
||||
# El peso capturado está en libras, convertir a kilos
|
||||
line.quantity.net_weight = net_weight_input / Decimal("2.204624")
|
||||
|
||||
# ==========================================
|
||||
# CALCULAR PESO BRUTO
|
||||
# ==========================================
|
||||
gross_weight_input = line.quantity.gross_weight
|
||||
package_quantity = line.quantity.package_quantity or 0
|
||||
package_weight_unit = Decimal("0")
|
||||
|
||||
# Obtener peso unitario del bulto si existe
|
||||
if line.quantity.package_id:
|
||||
package: Package = (
|
||||
db.query(Package)
|
||||
.filter(
|
||||
Package.id == line.quantity.package_id,
|
||||
Package.tenant_id == tenant_id,
|
||||
Package.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if package and package.weight_unit:
|
||||
package_weight_unit = package.weight_unit
|
||||
|
||||
# Si no se proporcionó peso bruto, calcularlo
|
||||
if not gross_weight_input or gross_weight_input == 0:
|
||||
if invoice_weight_type == "KGS":
|
||||
line.quantity.gross_weight = line.quantity.net_weight + (
|
||||
package_weight_unit * package_quantity
|
||||
)
|
||||
else: # libras
|
||||
line.quantity.gross_weight = line.quantity.net_weight + (
|
||||
(package_weight_unit * Decimal("2.204624")) * package_quantity
|
||||
)
|
||||
else:
|
||||
# Convertir peso bruto capturado según tipo de factura
|
||||
if invoice_weight_type == "KGS":
|
||||
line.quantity.gross_weight = gross_weight_input
|
||||
else: # libras
|
||||
line.quantity.gross_weight = gross_weight_input / Decimal("2.204624")
|
||||
|
||||
# ==========================================
|
||||
# VALIDAR PESO BRUTO < PESO NETO
|
||||
# ==========================================
|
||||
if line.quantity.gross_weight < line.quantity.net_weight:
|
||||
line.quantity.gross_weight = line.quantity.net_weight + (
|
||||
package_weight_unit * package_quantity
|
||||
)
|
||||
|
||||
# ==========================================
|
||||
# ASIGNAR DESCRIPCIÓN DE BULTOS
|
||||
# ==========================================
|
||||
if package_quantity and package_quantity > 0 and line.quantity.package_id:
|
||||
package: Package = (
|
||||
db.query(Package)
|
||||
.filter(
|
||||
Package.id == line.quantity.package_id,
|
||||
Package.tenant_id == tenant_id,
|
||||
Package.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if package:
|
||||
line.description.package_description = package.description_es
|
||||
else:
|
||||
line.quantity.package_quantity = 0
|
||||
line.quantity.package_id = None
|
||||
line.description.package_description = None
|
||||
|
||||
# ==========================================
|
||||
# ASIGNAR FRACCIÓN AMERICANA POR DEFECTO
|
||||
# ==========================================
|
||||
if not line.customs.american_fraction and class_info and class_info.us_fraction:
|
||||
line.customs.american_fraction = class_info.us_fraction
|
||||
|
||||
# Buscar el advalorem de la fracción americana
|
||||
if line.customs.american_fraction:
|
||||
us_fraction: USTariffFraction = (
|
||||
db.query(USTariffFraction)
|
||||
.filter(
|
||||
USTariffFraction.code == line.customs.american_fraction,
|
||||
USTariffFraction.tenant_id == tenant_id,
|
||||
USTariffFraction.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if us_fraction:
|
||||
# Si el tipo es 'ME' (Moneda Extranjera), usar costo fijo
|
||||
# De lo contrario, usar ad valorem
|
||||
if us_fraction.type_code == "foreign":
|
||||
line.customs.advalorem_american = us_fraction.fixed_cost
|
||||
else:
|
||||
line.customs.advalorem_american = us_fraction.ad_valorem
|
||||
|
||||
# ==========================================
|
||||
# ASIGNAR DESCRIPCIONES POR DEFECTO
|
||||
# ==========================================
|
||||
if not line.description.description_spanish and class_info:
|
||||
line.description.description_spanish = class_info.description_es
|
||||
|
||||
if not line.description.description_english and class_info:
|
||||
line.description.description_english = class_info.description_en
|
||||
|
||||
# ==========================================
|
||||
# NORMALIZAR CAMPOS DE TEXTO
|
||||
# ==========================================
|
||||
# Convertir a mayúsculas campos que lo requieran
|
||||
if line.description.brand:
|
||||
line.description.brand = line.description.brand.upper().strip()
|
||||
|
||||
if line.description.model:
|
||||
line.description.model = line.description.model.upper().strip()
|
||||
|
||||
# ==========================================
|
||||
# VALIDAR Y ASIGNAR PAGO DE IMPUESTO
|
||||
# ==========================================
|
||||
# Col. L: Se Pagó Impuesto — opcional, defaults a preferencia del sistema
|
||||
if line.tax_payment is not None:
|
||||
# Ya viene como bool desde Pydantic; valor válido por definición de tipo
|
||||
pass
|
||||
else:
|
||||
# TODO: Asignar desde SisExp:PagoImpuesto (preferencias del sistema)
|
||||
pass
|
||||
|
||||
# ==========================================
|
||||
# VALIDAR Y ASIGNAR FORMA DE PAGO
|
||||
# ==========================================
|
||||
# Col. M: Forma de Pago — opcional, debe existir en catálogo si se proporciona
|
||||
if line.payment_method:
|
||||
payment_method_exists = (
|
||||
db.query(PaymentMethod)
|
||||
.filter(PaymentMethod.key == line.payment_method)
|
||||
.first()
|
||||
)
|
||||
if not payment_method_exists:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].payment_method",
|
||||
message=f"La Forma de Pago '{line.payment_method}' no es válida.",
|
||||
solution=[
|
||||
"Capturar una Forma de Pago dentro del Catálogo General de Formas de Pago."
|
||||
],
|
||||
code="PAYMENT_METHOD_INVALID",
|
||||
)
|
||||
else:
|
||||
# TODO: Asignar desde SisExp:FormaPago (preferencias del sistema)
|
||||
pass
|
||||
|
||||
# ==========================================
|
||||
# ASIGNAR MÉTODO DE VALORACIÓN POR DEFECTO
|
||||
# ==========================================
|
||||
# TODO: Si no se especificó método de valoración, tomar de preferencias del sistema (SisImp:MetValor)
|
||||
251
backend/api/v1/modules/a76/items/exports/validators/update.py
Normal file
251
backend/api/v1/modules/a76/items/exports/validators/update.py
Normal file
@@ -0,0 +1,251 @@
|
||||
from decimal import Decimal
|
||||
from sqlalchemy.orm import Session
|
||||
from core.exceptions import ErrorCollector
|
||||
|
||||
from ...models import LineItem
|
||||
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import (
|
||||
USTariffFraction,
|
||||
)
|
||||
from .common import validate_common
|
||||
|
||||
|
||||
def validate_update(
|
||||
db: Session,
|
||||
line: LineItem,
|
||||
existing_line: LineItem,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
errors: ErrorCollector,
|
||||
line_number: int,
|
||||
):
|
||||
"""
|
||||
Validar y procesar actualización parcial de línea de importación temporal.
|
||||
Si un campo no se proporciona, se mantiene el valor existente.
|
||||
"""
|
||||
validate_common(db, line, tenant_id, company_id, errors, line_number)
|
||||
|
||||
if not errors.has_errors():
|
||||
# Obtener la factura para acceder a tipo de cambio, moneda y peso
|
||||
invoice: InvoiceHeader = (
|
||||
db.query(InvoiceHeader)
|
||||
.filter(
|
||||
InvoiceHeader.id == line.invoice_id,
|
||||
InvoiceHeader.tenant_id == tenant_id,
|
||||
InvoiceHeader.company_id == company_id
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not invoice or not invoice.financials or not invoice.logistics:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}]",
|
||||
message="No se pudo obtener información de la factura",
|
||||
solution=[
|
||||
"Verificar que la factura existe y tiene datos financieros y logísticos"
|
||||
],
|
||||
code="INVOICE_DATA_MISSING",
|
||||
)
|
||||
return
|
||||
|
||||
# ==========================================
|
||||
# ACTUALIZACIÓN PARCIAL DE CAMPOS
|
||||
# Si no se proporciona, mantener valor existente
|
||||
# ==========================================
|
||||
|
||||
# Tipo de cambio de la factura
|
||||
exchange_rate = invoice.financials.exchange_rate or Decimal("1.0")
|
||||
|
||||
# Unidad de medida
|
||||
if not line.unit_of_measure:
|
||||
line.unit_of_measure = existing_line.unit_of_measure
|
||||
|
||||
# Costo unitario
|
||||
if line.financial.unit_cost_capture is None:
|
||||
line.financial.unit_cost_capture = existing_line.financial.unit_cost_capture
|
||||
|
||||
# Recalcular valores monetarios si el costo o la cantidad cambian
|
||||
currency_type = invoice.financials.currency_type
|
||||
unit_cost_capture = line.financial.unit_cost_capture or Decimal("0")
|
||||
|
||||
if currency_type in ["USD", "ME"]:
|
||||
line.financial.unit_cost_usd = unit_cost_capture
|
||||
line.financial.unit_cost_mxn = unit_cost_capture * exchange_rate
|
||||
elif currency_type in ["MXN", "MN"]:
|
||||
line.financial.unit_cost_usd = (unit_cost_capture / exchange_rate) if exchange_rate else Decimal("0")
|
||||
line.financial.unit_cost_mxn = unit_cost_capture
|
||||
|
||||
quantity = line.quantity.quantity if line.quantity.quantity is not None else existing_line.quantity.quantity
|
||||
|
||||
if line.financial.unit_cost_usd is not None:
|
||||
line.financial.value_usd = line.financial.unit_cost_usd * quantity
|
||||
if line.financial.unit_cost_mxn is not None:
|
||||
line.financial.value_mxn = line.financial.unit_cost_mxn * quantity
|
||||
|
||||
line.financial.customs_value_usd = line.financial.value_usd
|
||||
line.financial.customs_value_mxn = line.financial.value_mxn
|
||||
|
||||
line.financial.value_temp_material_usd = line.financial.value_usd
|
||||
line.financial.value_temp_material_mxn = line.financial.value_mxn
|
||||
|
||||
# Convertir peso neto si se proporcionó
|
||||
invoice_weight_type = invoice.logistics.weight_type
|
||||
if line.quantity.net_weight is not None:
|
||||
# Se proporcionó nuevo peso neto, convertir según tipo
|
||||
net_weight_input = line.quantity.net_weight
|
||||
|
||||
if invoice_weight_type == "KGS":
|
||||
line.quantity.net_weight = net_weight_input
|
||||
else: # libras, convertir a kilos
|
||||
line.quantity.net_weight = net_weight_input / Decimal("2.204624")
|
||||
else:
|
||||
# Mantener peso existente
|
||||
line.quantity.net_weight = existing_line.quantity.net_weight
|
||||
|
||||
print(f"After weight conversion: net_weight={line.quantity.net_weight}, gross_weight={line.quantity.gross_weight}, weight_type={invoice_weight_type}")
|
||||
|
||||
# Convertir peso bruto si se proporcionó
|
||||
if line.quantity.gross_weight is not None:
|
||||
gross_weight_input = line.quantity.gross_weight
|
||||
|
||||
if invoice_weight_type == "KGS":
|
||||
line.quantity.gross_weight = gross_weight_input
|
||||
else: # libras, convertir a kilos
|
||||
line.quantity.gross_weight = gross_weight_input / Decimal("2.204624")
|
||||
else:
|
||||
# Mantener peso existente
|
||||
line.quantity.gross_weight = existing_line.quantity.gross_weight
|
||||
|
||||
# Cantidad de bultos
|
||||
if line.quantity.package_quantity is None:
|
||||
line.quantity.package_quantity = existing_line.quantity.package_quantity
|
||||
|
||||
# Clave de bultos
|
||||
if not line.quantity.package_id:
|
||||
line.quantity.package_id = existing_line.quantity.package_id
|
||||
|
||||
# País de origen
|
||||
if not line.customs.origin_country:
|
||||
line.customs.origin_country = existing_line.customs.origin_country
|
||||
|
||||
# Fracción arancelaria
|
||||
if not line.customs.fraction:
|
||||
line.customs.fraction = existing_line.customs.fraction
|
||||
|
||||
# Tipo de fracción
|
||||
if not line.customs.fraction_type:
|
||||
line.customs.fraction_type = existing_line.customs.fraction_type
|
||||
|
||||
# Sector
|
||||
if not line.customs.sector:
|
||||
line.customs.sector = existing_line.customs.sector
|
||||
|
||||
# Fracción americana y su advalorem
|
||||
if line.customs.american_fraction:
|
||||
# Se proporcionó nueva fracción americana, buscar su advalorem
|
||||
us_fraction: USTariffFraction = (
|
||||
db.query(USTariffFraction)
|
||||
.filter(
|
||||
USTariffFraction.code == line.customs.american_fraction,
|
||||
USTariffFraction.tenant_id == tenant_id,
|
||||
USTariffFraction.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if us_fraction:
|
||||
if us_fraction.type_code == "ME":
|
||||
line.customs.advalorem_american = us_fraction.fixed_cost
|
||||
else:
|
||||
line.customs.advalorem_american = us_fraction.ad_valorem
|
||||
else:
|
||||
# Mantener fracción americana existente
|
||||
line.customs.american_fraction = existing_line.customs.american_fraction
|
||||
line.customs.advalorem_american = existing_line.customs.advalorem_american
|
||||
|
||||
# Orden de compra
|
||||
if not line.order:
|
||||
line.order = existing_line.order
|
||||
|
||||
# Descripciones
|
||||
if not line.description.description_spanish:
|
||||
line.description.description_spanish = (
|
||||
existing_line.description.description_spanish
|
||||
)
|
||||
|
||||
if not line.description.description_english:
|
||||
line.description.description_english = (
|
||||
existing_line.description.description_english
|
||||
)
|
||||
|
||||
if not line.description.extra_description:
|
||||
line.description.extra_description = (
|
||||
existing_line.description.extra_description
|
||||
)
|
||||
|
||||
# Marca y modelo
|
||||
if line.description.brand:
|
||||
line.description.brand = line.description.brand.upper().strip()
|
||||
else:
|
||||
line.description.brand = existing_line.description.brand
|
||||
|
||||
if line.description.model:
|
||||
line.description.model = line.description.model.upper().strip()
|
||||
else:
|
||||
line.description.model = existing_line.description.model
|
||||
|
||||
# ==========================================
|
||||
# DATOS FA (Activo Fijo) — ACTUALIZACIÓN PARCIAL
|
||||
# ==========================================
|
||||
fa_data: FaLineItem = getattr(line, "fa_data", None)
|
||||
existing_fa_data: FaLineItem = getattr(existing_line, "fa_data", None)
|
||||
|
||||
if fa_data is not None and existing_fa_data is not None:
|
||||
# Col. C: Procedencia de la Importación (TipoMovImpo)
|
||||
if not fa_data.movement_type_import:
|
||||
fa_data.movement_type_import = existing_fa_data.movement_type_import
|
||||
|
||||
# Col. F: ¿Descarga la línea? (Descarga)
|
||||
if fa_data.download is None:
|
||||
fa_data.download = existing_fa_data.download
|
||||
|
||||
# Col. D: Factura de Importación — obligatoria sólo si hay descarga
|
||||
if not fa_data.search_invoice:
|
||||
fa_data.search_invoice = existing_fa_data.search_invoice
|
||||
|
||||
# Col. E: Línea de Importación — obligatoria sólo si hay descarga
|
||||
if fa_data.search_line is None:
|
||||
fa_data.search_line = existing_fa_data.search_line
|
||||
|
||||
# Subpartidas (EsSubPartida / SubPartida)
|
||||
if fa_data.is_subitem is None:
|
||||
fa_data.is_subitem = existing_fa_data.is_subitem
|
||||
if fa_data.subitem_number is None:
|
||||
fa_data.subitem_number = existing_fa_data.subitem_number
|
||||
|
||||
# Número de parte
|
||||
if not line.part_number_id:
|
||||
line.part_number_id = existing_line.part_number_id
|
||||
|
||||
# Pago de impuesto
|
||||
if line.tax_payment is None:
|
||||
line.tax_payment = existing_line.tax_payment
|
||||
|
||||
# Forma de pago
|
||||
if not line.payment_method:
|
||||
line.payment_method = existing_line.payment_method
|
||||
|
||||
# Método de valoración
|
||||
if not line.valuation_method:
|
||||
if existing_line.valuation_method:
|
||||
line.valuation_method = existing_line.valuation_method
|
||||
# else: TODO: Tomar de SisImp:MetValor (preferencias del sistema)
|
||||
|
||||
# Número de entrada
|
||||
if not line.description.entry_number:
|
||||
line.description.entry_number = existing_line.description.entry_number
|
||||
|
||||
# Lote
|
||||
if not line.description.lot:
|
||||
line.description.lot = existing_line.description.lot
|
||||
@@ -2,18 +2,8 @@ from sqlalchemy.orm import Session
|
||||
from api.v1.modules.a76.invoices.models import InvoiceFinancials, InvoiceHeader, InvoiceLogistics
|
||||
from core.exceptions import ErrorCollector
|
||||
|
||||
from ....models import LineItem
|
||||
from ....line_financials.models import LineFinancial
|
||||
from ....line_financials.schemas import LineFinancialCreate
|
||||
from ....line_quantities.models import LineQuantity
|
||||
from ....line_quantities.schemas import LineQuantityCreate
|
||||
from ....line_customs.models import LineCustom
|
||||
from ....line_customs.schemas import LineCustomCreate
|
||||
from ....line_descriptions.models import LineDescription
|
||||
from ....line_descriptions.schemas import LineDescriptionCreate
|
||||
from ....line_references.models import LineReference
|
||||
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
from ....models import LineItem
|
||||
from ...models import LineItem
|
||||
from ...models import LineItem
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from sqlalchemy import exists
|
||||
from sqlalchemy.orm import Session
|
||||
from api.v1.modules.a76.invoices.common.common_validators import invoice_exists_by_id
|
||||
from api.v1.modules.a76.items.imports.validators.calculations import apply_calculations
|
||||
from core.exceptions import ErrorCollector
|
||||
from sqlalchemy import func
|
||||
|
||||
from ....common.fractions import search_fraction_preference
|
||||
from ....common.common_validators import item_exists
|
||||
from ....models import LineItem
|
||||
from ....line_customs.models import FractionType, LineCustom
|
||||
from ...common.fractions import search_fraction_preference
|
||||
from ...common.common_validators import item_exists
|
||||
from ...models import LineItem
|
||||
from ...line_customs.models import FractionType, LineCustom
|
||||
from api.v1.modules.a76.items.schemas import LineItemCreate
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
@@ -304,6 +305,10 @@ def validate_common(
|
||||
unit_of_measure = line.unit_of_measure or (
|
||||
class_.unit_of_measure if class_ else None
|
||||
)
|
||||
|
||||
#TODO: SSisGen Logic Restringer cantidades decimales para piezas, revisar si es necesario agregar validación similar para otras unidades de medida
|
||||
#TODO: SSisGen Logic Seguridad
|
||||
|
||||
if unit_of_measure == "PZA" and line.quantity.quantity % 1 != 0:
|
||||
errors.add_error(
|
||||
field=f"line[{line_number}].quantity.quantity",
|
||||
@@ -333,3 +338,5 @@ def validate_common(
|
||||
solution=["Proporciona un número de parte valido."],
|
||||
code="PART_NUMBER_NOT_FOUND",
|
||||
)
|
||||
|
||||
apply_calculations(db, line, tenant_id, company_id, line_number)
|
||||
@@ -1,21 +1,12 @@
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import func, exists
|
||||
from sqlalchemy.orm import Session
|
||||
from ....common.common_validators import count_items
|
||||
from ...common.common_validators import count_items
|
||||
from core.exceptions import ErrorCollector
|
||||
|
||||
from ....models import LineItem
|
||||
from ....line_financials.models import LineFinancial
|
||||
from ....line_financials.schemas import LineFinancialCreate
|
||||
from ....line_quantities.models import LineQuantity
|
||||
from ....line_quantities.schemas import LineQuantityCreate
|
||||
from ....line_customs.models import LineCustom
|
||||
from ....line_customs.schemas import LineCustomCreate
|
||||
from ....line_descriptions.models import LineDescription
|
||||
from ....line_descriptions.schemas import LineDescriptionCreate
|
||||
from ....line_references.models import LineReference
|
||||
from ...models import LineItem
|
||||
from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem
|
||||
from ....models import LineItem
|
||||
from ...models import LineItem
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.classes.models import Class
|
||||
from api.v1.modules.a76.general_catalogs.packages.models import Package
|
||||
@@ -27,7 +18,7 @@ from .common import validate_common
|
||||
|
||||
def validate_create(
|
||||
db: Session,
|
||||
line: LineItem, # LineItemCreate schema (Pydantic)
|
||||
line: LineItem,
|
||||
tenant_id: int,
|
||||
company_id: int,
|
||||
errors: ErrorCollector,
|
||||
@@ -44,7 +35,7 @@ def validate_create(
|
||||
"""
|
||||
|
||||
# Access fa_data safely
|
||||
fa_data = getattr(line, "fa_data", None)
|
||||
fa_data: FaLineItem = getattr(line, "fa_data", None)
|
||||
|
||||
# Required field validations
|
||||
if not line.class_id:
|
||||
@@ -1,9 +1,8 @@
|
||||
from decimal import Decimal
|
||||
from sqlalchemy.orm import Session
|
||||
from api.v1.modules.a76.invoices.common.common_validators import invoice_exists
|
||||
from core.exceptions import ErrorCollector
|
||||
|
||||
from ....models import LineItem
|
||||
from ...models import LineItem
|
||||
from api.v1.modules.a76.invoices.models import InvoiceHeader
|
||||
from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import (
|
||||
USTariffFraction,
|
||||
@@ -24,8 +24,10 @@ from api.v1.modules.a76.invoices.common.common_validators import (
|
||||
invoice_updated,
|
||||
)
|
||||
from core.exceptions import ErrorCollector
|
||||
from .imports.temporary.validators.create import validate_create
|
||||
from .imports.temporary.validators.update import validate_update
|
||||
from .imports.validators.create import validate_create as validate_create_import
|
||||
from .imports.validators.update import validate_update as validate_update_import
|
||||
from .exports.validators.create import validate_create as validate_create_export
|
||||
from .exports.validators.update import validate_update as validate_update_export
|
||||
|
||||
from .schemas import LineItemCreate, LineItemUpdate
|
||||
from .line_financials.models import LineFinancial
|
||||
@@ -333,14 +335,16 @@ class ItemService:
|
||||
# Validar que la factura exista y no esté actualizada (si viene invoice_id)
|
||||
if not item_data.invoice_id:
|
||||
errors.add_required_error(field="invoice_id")
|
||||
errors.raise_if_errors("Error al crear el item")
|
||||
errors.raise_if_errors("Error al crear el item - invoice_id es requerido")
|
||||
|
||||
if not invoice_exists_by_id(
|
||||
invoice = invoice_exists_by_id(
|
||||
db, item_data.invoice_id, tenant_id, company_id, errors
|
||||
):
|
||||
errors.raise_if_errors("Error al crear el item")
|
||||
)
|
||||
|
||||
if not invoice:
|
||||
errors.raise_if_errors("Error al encontra la factura para el item")
|
||||
if not invoice_updated(db, item_data.invoice_id, tenant_id, company_id, errors):
|
||||
errors.raise_if_errors("Error al crear el item")
|
||||
errors.raise_if_errors("Error al crear el item - la factura ya fue actualizada, no se pueden agregar items")
|
||||
|
||||
# Lock invoice and calculate line number
|
||||
if not ItemService._lock_invoice(
|
||||
@@ -370,14 +374,27 @@ class ItemService:
|
||||
item_data.component_part_number_id = resolved_id
|
||||
|
||||
# Validar el item
|
||||
validate_create(
|
||||
db,
|
||||
item_data, # Schema Pydantic completo
|
||||
tenant_id,
|
||||
company_id,
|
||||
errors,
|
||||
line_number,
|
||||
)
|
||||
if invoice.operation_type == "exp":
|
||||
if invoice.invoice_type == "CR" and invoice.document_type == "AFIJO":
|
||||
errors.add_error("invoice_id", "No se pueden agregar items a una factura de tipo CR con documento AFIJO", code="INVALID_INVOICE_TYPE")
|
||||
|
||||
validate_create_export(
|
||||
db,
|
||||
item_data, # Schema Pydantic completo
|
||||
tenant_id,
|
||||
company_id,
|
||||
errors,
|
||||
line_number,
|
||||
)
|
||||
else:
|
||||
validate_create_import(
|
||||
db,
|
||||
item_data, # Schema Pydantic completo
|
||||
tenant_id,
|
||||
company_id,
|
||||
errors,
|
||||
line_number,
|
||||
)
|
||||
|
||||
# Validaciones adicionales específicas del negocio
|
||||
if item_data.fa_data and item_data.fa_data.is_subitem is None:
|
||||
@@ -458,6 +475,12 @@ class ItemService:
|
||||
# Validaciones con ErrorCollector
|
||||
errors = ErrorCollector()
|
||||
|
||||
invoice = invoice_exists_by_id(
|
||||
db, item_data.invoice_id, tenant_id, company_id, errors
|
||||
)
|
||||
if not invoice:
|
||||
errors.raise_if_errors("Error al encontra la factura para el item")
|
||||
|
||||
# Lock invoice
|
||||
invoice_id_to_lock = (
|
||||
item_data.invoice_id if item_data.invoice_id else db_item.invoice_id
|
||||
@@ -465,7 +488,7 @@ class ItemService:
|
||||
if not ItemService._lock_invoice(
|
||||
db, invoice_id_to_lock, tenant_id, company_id, errors
|
||||
):
|
||||
errors.raise_if_errors("Error al actualizar el item")
|
||||
errors.raise_if_errors("Error al actualizar el item")
|
||||
|
||||
# Resolve part ID if a string is provided in part_number (alias for part_number_id)
|
||||
if hasattr(item_data, 'part_number_id') and item_data.part_number_id and not isinstance(item_data.part_number_id, int):
|
||||
@@ -485,17 +508,28 @@ class ItemService:
|
||||
if resolved_id:
|
||||
item_data.component_part_number_id = resolved_id
|
||||
|
||||
# Validar el item que se va a actualizar
|
||||
validate_update(
|
||||
db,
|
||||
item_data, # Schema de update
|
||||
db_item, # LineItem existente en DB
|
||||
tenant_id,
|
||||
company_id,
|
||||
errors,
|
||||
db_item.line_number,
|
||||
)
|
||||
|
||||
if invoice.operation_type == "exp":
|
||||
# Validar el item que se va a actualizar
|
||||
validate_update_export(
|
||||
db,
|
||||
item_data, # Schema de update
|
||||
db_item, # LineItem existente en DB
|
||||
tenant_id,
|
||||
company_id,
|
||||
errors,
|
||||
db_item.line_number,
|
||||
)
|
||||
else:
|
||||
# Validar el item que se va a actualizar
|
||||
validate_update_import(
|
||||
db,
|
||||
item_data, # Schema de update
|
||||
db_item, # LineItem existente en DB
|
||||
tenant_id,
|
||||
company_id,
|
||||
errors,
|
||||
db_item.line_number,
|
||||
)
|
||||
|
||||
# Validar tipo de partida
|
||||
if hasattr(item_data, "item_type") and item_data.item_type:
|
||||
|
||||
@@ -1,877 +0,0 @@
|
||||
"""
|
||||
SQL Query builders for invoice movement services.
|
||||
Centralizes all SQL query construction logic.
|
||||
"""
|
||||
|
||||
|
||||
class TemporaryImportQueries:
|
||||
"""SQL queries for temporary imports using PostgreSQL tables."""
|
||||
|
||||
@staticmethod
|
||||
def build_aggregated_query(db_name: str, where_str: str) -> str:
|
||||
"""Build optimized query for NORMAL mode (grouped by invoice with totals)."""
|
||||
# Note: db_name parameter kept for compatibility but not used in PostgreSQL
|
||||
return f"""
|
||||
SELECT
|
||||
ih.invoice_number AS C1,
|
||||
COALESCE(ped.pedimento_number, '') AS C2,
|
||||
TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3,
|
||||
CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C4,
|
||||
COALESCE(ped.pedimento_code, '') AS C5,
|
||||
COALESCE(ped.regime, '') AS C10,
|
||||
COALESCE(TO_CHAR(log.entry_exit_date, 'YYYYMMDD'), '') AS C11,
|
||||
COALESCE(TO_CHAR(log.delivery_date, 'YYYYMMDD'), '') AS C12,
|
||||
COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C13,
|
||||
COALESCE(cmp.remesa, 0) AS C14,
|
||||
COALESCE(fin.exchange_rate, 0) AS C15,
|
||||
COALESCE(cmp.provider_id::text, '') AS C16,
|
||||
COALESCE(cmp.sold_to_id::text, '') AS C17,
|
||||
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(cmp.edocument, '') AS C42,
|
||||
COALESCE(cmp.vucem_operation_num, '') AS C43,
|
||||
COALESCE(fin.exchange_rate, 0) AS C50,
|
||||
COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C51,
|
||||
COALESCE(ih.capture_user, '') AS C52,
|
||||
COALESCE(ih.who_updated, '') AS C53,
|
||||
COALESCE(log.carrier_id, '') AS C54,
|
||||
COALESCE(log.transport_num || ' ' || log.license_plate, '') AS C55,
|
||||
'' AS C56,
|
||||
'' AS C57,
|
||||
'' AS C58,
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0) AS total_me,
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0) AS total_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.items i ON i.invoice_id = ih.id
|
||||
LEFT JOIN a76.item_lines il ON il.item_id = i.id
|
||||
LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id
|
||||
LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
WHERE ih.operation_type = 'imp'
|
||||
AND ih.invoice_type = 'TEM'
|
||||
AND {where_str}
|
||||
GROUP BY ih.id, ih.invoice_number, ped.pedimento_number, ped.pedimento_code, ped.regime,
|
||||
log.entry_exit_date, log.delivery_date, log.payment_date, cmp.remesa, fin.exchange_rate,
|
||||
cmp.provider_id, cmp.sold_to_id, cmp.customs_broker_id, cmp.aduana, ped_r1.pedimento_number,
|
||||
cmp.edocument, cmp.vucem_operation_num, ih.emission_date, ih.capture_user, ih.who_updated,
|
||||
log.carrier_id, log.transport_num, log.license_plate
|
||||
ORDER BY ih.invoice_number
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_main_query(db_name: str, where_str: str) -> str:
|
||||
"""Build main SQL query for DETAILED mode (all partidas) from PostgreSQL."""
|
||||
# Note: db_name parameter is kept for compatibility but not used in PostgreSQL
|
||||
return f"""
|
||||
SELECT
|
||||
ih.invoice_number AS C1,
|
||||
COALESCE(ped.pedimento_number, '') AS C2,
|
||||
TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3,
|
||||
CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C4,
|
||||
COALESCE(ped.pedimento_code, '') AS C5,
|
||||
COALESCE(fin.value_me, 0) AS C6,
|
||||
COALESCE(fin.value_mn, 0) AS C7,
|
||||
COALESCE(cmp.provider_id::text, '') AS C8,
|
||||
COALESCE(cmp.sold_to_id::text, '') AS C9,
|
||||
COALESCE(ped.regime, '') AS C10,
|
||||
COALESCE(TO_CHAR(log.entry_exit_date, 'YYYYMMDD'), '') AS C11,
|
||||
COALESCE(TO_CHAR(log.delivery_date, 'YYYYMMDD'), '') AS C12,
|
||||
COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C13,
|
||||
COALESCE(cmp.remesa, 0) AS C14,
|
||||
COALESCE(fin.exchange_rate, 0) AS C15,
|
||||
COALESCE(cmp.provider_id::text, '') AS C16,
|
||||
COALESCE(cmp.sold_to_id::text, '') AS C17,
|
||||
COALESCE(cmp.customs_broker_id::text, '') AS C18,
|
||||
'' AS C19,
|
||||
COALESCE(il.class_id::text, '') AS C20,
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C21,
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_english, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C22,
|
||||
COALESCE(lq.quantity, 0) AS C23,
|
||||
COALESCE(il.unit_of_measure::text, '') AS C24,
|
||||
COALESCE(lf.value_mxn, 0) AS C25,
|
||||
COALESCE(lf.customs_value_mxn, 0) AS C26,
|
||||
COALESCE(lf.value_usd, 0) AS C27,
|
||||
COALESCE(lf.customs_value_usd, 0) AS C28,
|
||||
COALESCE(lq.net_weight, 0) AS C29,
|
||||
COALESCE(lq.gross_weight, 0) AS C30,
|
||||
COALESCE(ih.purchase_order, '') AS C31,
|
||||
COALESCE(lc.fraction, '') AS C32,
|
||||
COALESCE(lc.fraction_type, '') AS C33,
|
||||
COALESCE(lc.advalorem_numeric, 0) AS C34,
|
||||
COALESCE(lc.sector, '') AS C35,
|
||||
COALESCE(lf.igi_amount_usd, 0) AS C36,
|
||||
COALESCE(lc.origin_country, '') AS C37,
|
||||
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(il.line_number, 0) AS C44,
|
||||
'' AS C45,
|
||||
'' AS C46,
|
||||
COALESCE(cls.us_fraction, '') AS C47,
|
||||
COALESCE(prt.eccn, '') AS C48,
|
||||
COALESCE(il.part_number::text, '') AS C49,
|
||||
COALESCE(fin.exchange_rate, 0) AS C50,
|
||||
COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C51,
|
||||
COALESCE(ih.capture_user, '') AS C52,
|
||||
COALESCE(ih.who_updated, '') AS C53,
|
||||
COALESCE(log.carrier_id, '') AS C54,
|
||||
COALESCE(log.transport_num || ' ' || log.license_plate, '') AS C55,
|
||||
'' AS C56,
|
||||
'' AS C57,
|
||||
'' AS C58
|
||||
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.item_lines il ON il.item_id = (
|
||||
SELECT id FROM a76.items WHERE invoice_id = ih.id LIMIT 1
|
||||
)
|
||||
LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id
|
||||
LEFT JOIN a76.classes cls ON cls.id = il.class_id
|
||||
LEFT JOIN a76.parts prt ON prt.id = il.part_number
|
||||
LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure
|
||||
WHERE ih.operation_type = 'imp'
|
||||
AND ih.invoice_type = 'TEM'
|
||||
AND {where_str}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_totals_query(db_name: str) -> str:
|
||||
"""Build query to get totals for an invoice."""
|
||||
return f"""
|
||||
SELECT
|
||||
COALESCE(SUM(EqiPim.ValorImpoME), 0),
|
||||
COALESCE(SUM(EqiPim.ValorImpoMN), 0)
|
||||
FROM [{db_name}].dbo.QEqiMaq EqiPim
|
||||
WHERE EqiPim.Consecutivo = :consecutivo
|
||||
AND EqiPim.EsSubpartida = 'P'
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_series_query(db_name: str) -> str:
|
||||
"""Build query to get series information."""
|
||||
return f"""
|
||||
SELECT SerieImpo, ModeloImpo, ParteImpo
|
||||
FROM [{db_name}].dbo.QSeriesImpo
|
||||
WHERE Consecutivo = :consecutivo
|
||||
AND LineaImpo = :linea
|
||||
ORDER BY RenImpo
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_driver_badge_query(db_name: str) -> str:
|
||||
"""Build query to get driver badge number."""
|
||||
return f"""
|
||||
SELECT TOP 1 NUMGAFETEUNICO
|
||||
FROM [{db_name}].dbo.GConductor
|
||||
LEFT JOIN [{db_name}].dbo.QFacImp
|
||||
ON QFacImp.CONDUCTOR = GConductor.CONDUCTOR
|
||||
WHERE FacturaImpo = :factura
|
||||
"""
|
||||
|
||||
|
||||
class DefinitiveImportQueries:
|
||||
"""SQL queries for definitive imports (PostgreSQL schema)."""
|
||||
|
||||
@staticmethod
|
||||
def build_aggregated_query(db_name: str, where_clause: str) -> str:
|
||||
"""Build optimized query for NORMAL mode (grouped by invoice with totals)."""
|
||||
return f"""
|
||||
SELECT
|
||||
ih.invoice_number AS C1,
|
||||
COALESCE(ped.pedimento_number, '') AS C2,
|
||||
TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3,
|
||||
CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C4,
|
||||
COALESCE(ped.pedimento_code, '') AS C5,
|
||||
COALESCE(ped.regime, '') AS C10,
|
||||
COALESCE(TO_CHAR(log.entry_exit_date, 'YYYYMMDD'), '') AS C11,
|
||||
COALESCE(TO_CHAR(log.delivery_date, 'YYYYMMDD'), '') AS C12,
|
||||
COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C13,
|
||||
COALESCE(log.payment_receipt_num, '') AS C14,
|
||||
COALESCE(fin.exchange_rate, 0) AS C15,
|
||||
COALESCE(cmp.provider_id::text, '') AS C16,
|
||||
COALESCE(cmp.sold_to_id::text, '') AS C17,
|
||||
COALESCE(cmp.customs_broker_id::text, '') AS C18,
|
||||
COALESCE(ih.purchase_order, '') AS C31,
|
||||
COALESCE(cmp.aduana, '') AS C39,
|
||||
ih.id AS C35,
|
||||
COALESCE(ped_r1.pedimento_number, '') AS C42,
|
||||
COALESCE(cmp.edocument, '') AS C43,
|
||||
COALESCE(cmp.vucem_operation_num, '') AS C44,
|
||||
COALESCE(fin.exchange_rate, 0) AS C51,
|
||||
COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C52,
|
||||
COALESCE(ih.capture_user, '') AS C53,
|
||||
COALESCE(ih.who_updated, '') AS C54,
|
||||
COALESCE(log.transport_id || ' ' || log.transport_num, '') AS C56,
|
||||
'' AS C57,
|
||||
'' AS C58,
|
||||
'' AS C59,
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0) AS total_me,
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0) AS total_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.items i ON i.invoice_id = ih.id
|
||||
LEFT JOIN a76.item_lines il ON il.item_id = i.id
|
||||
LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id
|
||||
LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
WHERE ih.operation_type = 'imp'
|
||||
AND ih.invoice_type IN ('DEF', 'EXDEF', 'MATDE')
|
||||
AND {where_clause}
|
||||
GROUP BY ih.id, ih.invoice_number, ped.pedimento_number, ped.pedimento_code, ped.regime,
|
||||
log.entry_exit_date, log.delivery_date, log.payment_date, log.payment_receipt_num,
|
||||
fin.exchange_rate, cmp.provider_id, cmp.sold_to_id, cmp.customs_broker_id,
|
||||
ih.purchase_order, cmp.aduana, ped_r1.pedimento_number, cmp.edocument,
|
||||
cmp.vucem_operation_num, ih.emission_date, ih.capture_user, ih.who_updated,
|
||||
log.transport_id, log.transport_num
|
||||
ORDER BY ih.invoice_number
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_main_query(db_name: str, where_clause: str) -> str:
|
||||
return f"""
|
||||
SELECT
|
||||
ih.invoice_number AS C1, -- [0]
|
||||
ped.pedimento_number AS C2, -- [1]
|
||||
ih.invoice_date AS C3, -- [2]
|
||||
ped.status AS C4, -- [3]
|
||||
ped.pedimento_code AS C5, -- [4]
|
||||
'' AS C6, '' AS C7, '' AS C8, '' AS C9, -- [5-8]
|
||||
ped.regime AS C10, -- [9]
|
||||
log.entry_exit_date AS C11, -- [10]
|
||||
log.delivery_date AS C12, -- [11]
|
||||
log.payment_date AS C13, -- [12]
|
||||
log.payment_receipt_num AS C14, -- [13]
|
||||
'' AS C15, -- [14]
|
||||
cmp.provider_id AS C16, -- [15]
|
||||
cmp.sold_to_id AS C17, -- [16]
|
||||
cmp.customs_broker_id AS C18, -- [17]
|
||||
'' AS C19, -- [18]
|
||||
prt.part_number AS C20, -- [19]
|
||||
ld.description_spanish AS C21, -- [20]
|
||||
ld.description_english AS C22, -- [21]
|
||||
lq.quantity AS C23, -- [22]
|
||||
um.code AS C24, -- [23]
|
||||
lf.value_mxn AS C25, -- [24]
|
||||
'' AS C26, -- [25]
|
||||
lf.value_usd AS C27, -- [26]
|
||||
'' AS C28, -- [27]
|
||||
lq.net_weight AS C29, -- [28]
|
||||
lq.gross_weight AS C30, -- [29]
|
||||
ih.purchase_order AS C31, -- [30]
|
||||
lc.fraction AS C32, -- [31]
|
||||
'' AS C33, '' AS C34, -- [32-33]
|
||||
ih.id AS C35, -- [34]
|
||||
'' AS C36, '' AS C37, -- [35-36]
|
||||
lc.origin_country AS C38, -- [37]
|
||||
cmp.aduana AS C39, -- [38]
|
||||
il.material_type AS C40, -- [39]
|
||||
il.id AS C41, -- [40]
|
||||
'' AS C42, -- [41] rectification_id
|
||||
cmp.edocument AS C43, -- [42]
|
||||
cmp.vucem_operation_num AS C44, -- [43]
|
||||
il.line_number AS C45, -- [44]
|
||||
ld.brand AS C46, -- [45]
|
||||
ld.model AS C47, -- [46]
|
||||
prt.us_fraction AS C48, -- [47]
|
||||
prt.eccn AS C49, -- [48]
|
||||
prt.id AS C50, -- [49]
|
||||
fin.exchange_rate AS C51, -- [50]
|
||||
ih.emission_date AS C52, -- [51]
|
||||
ih.capture_user AS C53, -- [52]
|
||||
ih.who_updated AS C54, -- [53]
|
||||
'' AS C55, -- [54]
|
||||
COALESCE(log.transport_id || ' ' || log.transport_num, '') AS C56, -- [55]
|
||||
'' AS C57, -- [56] Pedimento18 (row[56])
|
||||
COALESCE(ld.lot, '') AS C58, -- [57] Lote (row[57])
|
||||
'' AS C59, -- [58] TipoPed (row[58])
|
||||
'' AS C60 -- [59] Relleno final
|
||||
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 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
|
||||
LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id
|
||||
LEFT JOIN a76.classes cls ON cls.id = il.class_id
|
||||
LEFT JOIN a76.parts prt ON prt.id = il.part_number
|
||||
LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure
|
||||
WHERE {where_clause}
|
||||
ORDER BY ih.invoice_number, il.line_number
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_totals_query(db_name: str) -> str:
|
||||
"""Build query to get totals for a definitive import invoice."""
|
||||
return f"""
|
||||
SELECT
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0),
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0)
|
||||
FROM a76.item_line_financials lf
|
||||
INNER JOIN a76.item_lines il ON il.id = lf.item_line_id
|
||||
LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id
|
||||
INNER JOIN a76.items itm ON itm.id = il.item_id
|
||||
WHERE itm.invoice_id = :consecutivo
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_series_query(db_name: str) -> str:
|
||||
"""Build query to get series information for definitive imports."""
|
||||
# TODO: QSeriesDef table not migrated to PostgreSQL yet
|
||||
return """
|
||||
SELECT '' as serie, '' as modelo, '' as parte
|
||||
WHERE 1=0
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_driver_badge_query(db_name: str) -> str:
|
||||
"""Build query to get driver badge number for definitive imports."""
|
||||
# TODO: GConductor table not migrated to PostgreSQL yet
|
||||
return """
|
||||
SELECT '' as badge
|
||||
WHERE 1=0
|
||||
"""
|
||||
|
||||
|
||||
class RepairImportQueries:
|
||||
"""SQL queries for repair imports (PostgreSQL schema)."""
|
||||
|
||||
@staticmethod
|
||||
def build_aggregated_query(db_name: str, where_str: str, discharge_clause: str = "") -> str:
|
||||
"""Build optimized query for NORMAL mode (grouped by invoice with totals)."""
|
||||
discharge_filter = "" # Temporarily disabled until schema migration
|
||||
return f"""
|
||||
SELECT
|
||||
ih.invoice_number AS C2,
|
||||
COALESCE(ped.pedimento_number, '') AS C3,
|
||||
TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C4,
|
||||
CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C5,
|
||||
COALESCE(ped.pedimento_code, '') AS C6,
|
||||
COALESCE(ped.regime, '') AS C7,
|
||||
COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C9,
|
||||
COALESCE(cmp.remesa::text, '') AS C10,
|
||||
COALESCE(fin.exchange_rate, 0) AS C11,
|
||||
COALESCE(cmp.provider_id::text, '') AS C12,
|
||||
COALESCE(cmp.sold_to_id::text, '') AS C13,
|
||||
COALESCE(cmp.customs_broker_id::text, '') AS C14,
|
||||
COALESCE(ih.purchase_order, '') AS C24,
|
||||
COALESCE(ped.customs_office, '') AS C29,
|
||||
ih.id AS C30,
|
||||
COALESCE(cmp.edocument, '') AS C33,
|
||||
COALESCE(cmp.vucem_operation_num, '') AS C34,
|
||||
COALESCE(fin.exchange_rate, 0) AS C40,
|
||||
COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C41,
|
||||
COALESCE(ih.capture_user, '') AS C42,
|
||||
COALESCE(ih.who_updated, '') AS C43,
|
||||
COALESCE(log.carrier_id, '') AS C44,
|
||||
COALESCE(log.transport_num, '') AS C45,
|
||||
COALESCE(ped.pedimento_code, '') AS C47,
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0) AS total_me,
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0) AS total_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.items i ON i.invoice_id = ih.id
|
||||
LEFT JOIN a76.item_lines il ON il.item_id = i.id
|
||||
LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id
|
||||
LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
WHERE ih.operation_type = 'imp'
|
||||
AND ih.invoice_type = 'REP'
|
||||
AND COALESCE(cmp.is_regime_change, false) = false
|
||||
{"AND " + where_str if where_str else ""}
|
||||
{discharge_filter}
|
||||
GROUP BY ih.id, ih.invoice_number, ped.pedimento_number, ped.pedimento_code, ped.regime,
|
||||
log.payment_date, cmp.remesa, fin.exchange_rate, cmp.provider_id, cmp.sold_to_id,
|
||||
cmp.customs_broker_id, ih.purchase_order, ped.customs_office, cmp.edocument,
|
||||
cmp.vucem_operation_num, ih.emission_date, ih.capture_user, ih.who_updated,
|
||||
log.carrier_id, log.transport_num
|
||||
ORDER BY ih.invoice_number
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_main_query(db_name: str, where_str: str, discharge_clause: str = "") -> str:
|
||||
"""Build main SQL query for repair import data."""
|
||||
# Note: is_discharged field not yet migrated to PostgreSQL schema
|
||||
# discharge_filter = "AND il.is_discharged = true" if "descargado" in discharge_clause.lower() else ""
|
||||
discharge_filter = "" # Temporarily disabled until schema migration
|
||||
return f"""
|
||||
SELECT
|
||||
il.line_number,
|
||||
ih.invoice_number,
|
||||
COALESCE(ped.pedimento_number, ''),
|
||||
TO_CHAR(ih.invoice_date, 'YYYYMMDD'),
|
||||
CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END,
|
||||
COALESCE(ped.pedimento_code, ''),
|
||||
COALESCE(ped.regime, ''),
|
||||
'',
|
||||
COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), ''),
|
||||
COALESCE(cmp.remesa::text, ''),
|
||||
COALESCE(fin.exchange_rate, 0),
|
||||
COALESCE(cmp.provider_id::text, ''),
|
||||
COALESCE(cmp.sold_to_id::text, ''),
|
||||
COALESCE(cmp.customs_broker_id::text, ''),
|
||||
COALESCE(il.part_number::text, ''),
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '),
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_english, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '),
|
||||
COALESCE(lq.quantity, 0),
|
||||
COALESCE(il.unit_of_measure, 0),
|
||||
COALESCE(lf.value_mxn, 0),
|
||||
COALESCE(lf.value_usd, 0),
|
||||
COALESCE(lq.net_weight, 0),
|
||||
COALESCE(lq.gross_weight, 0),
|
||||
COALESCE(ih.purchase_order, ''),
|
||||
COALESCE(lc.fraction, ''),
|
||||
'',
|
||||
COALESCE(lc.sector, ''),
|
||||
COALESCE(lc.origin_country, ''),
|
||||
COALESCE(ped.customs_office, ''),
|
||||
ih.id,
|
||||
'P',
|
||||
'',
|
||||
COALESCE(cmp.edocument, ''),
|
||||
COALESCE(cmp.vucem_operation_num, ''),
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '),
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '),
|
||||
COALESCE(lc.american_fraction, ''),
|
||||
COALESCE(prt.eccn, ''),
|
||||
COALESCE(fin.exchange_rate, 0),
|
||||
COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), ''),
|
||||
COALESCE(ih.capture_user, ''),
|
||||
COALESCE(ih.who_updated, ''),
|
||||
COALESCE(log.carrier_id, ''),
|
||||
COALESCE(log.transport_num, ''),
|
||||
'',
|
||||
COALESCE(ped.pedimento_code, '')
|
||||
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 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
|
||||
LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id
|
||||
LEFT JOIN a76.parts prt ON prt.id = il.part_number
|
||||
LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure
|
||||
WHERE ih.operation_type = 'imp'
|
||||
AND ih.invoice_type = 'REP'
|
||||
AND COALESCE(cmp.is_regime_change, false) = false
|
||||
{"AND " + where_str if where_str else ""}
|
||||
{discharge_filter}
|
||||
ORDER BY ih.invoice_number, il.line_number
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_totals_query(db_name: str, discharge_clause: str = "") -> str:
|
||||
"""Build query to get totals for a repair import invoice."""
|
||||
# Note: is_discharged field not yet migrated to PostgreSQL schema
|
||||
# discharge_filter = "AND il.is_discharged = true" if "descargado" in discharge_clause.lower() else ""
|
||||
discharge_filter = "" # Temporarily disabled until schema migration
|
||||
return f"""
|
||||
SELECT
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0),
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0)
|
||||
FROM a76.item_line_financials lf
|
||||
INNER JOIN a76.item_lines il ON il.id = lf.item_line_id
|
||||
LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id
|
||||
INNER JOIN a76.items itm ON itm.id = il.item_id
|
||||
WHERE itm.invoice_id = :consecutivo
|
||||
|
||||
{discharge_filter}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_series_query(db_name: str) -> str:
|
||||
"""Build query to get series information for repair imports."""
|
||||
# TODO: QSeriesImpoRep table not migrated to PostgreSQL yet
|
||||
return """
|
||||
SELECT '' as serie, '' as modelo, '' as parte
|
||||
WHERE 1=0
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_driver_badge_query(db_name: str) -> str:
|
||||
"""Build query to get driver badge number for repair imports."""
|
||||
# TODO: GConductor table not migrated to PostgreSQL yet
|
||||
return """
|
||||
SELECT '' as badge
|
||||
WHERE 1=0
|
||||
"""
|
||||
|
||||
|
||||
class ExportQueries:
|
||||
"""SQL queries for exports (PostgreSQL schema)."""
|
||||
|
||||
@staticmethod
|
||||
def build_aggregated_query(db_name: str, where_clause: str) -> str:
|
||||
"""
|
||||
Build optimized query for NORMAL mode (grouped by invoice with totals).
|
||||
|
||||
Args:
|
||||
db_name: Database name (not used in PostgreSQL version)
|
||||
where_clause: Additional WHERE conditions (without WHERE keyword)
|
||||
"""
|
||||
return f"""
|
||||
SELECT
|
||||
ih.invoice_number AS C1,
|
||||
COALESCE(ped.pedimento_number, '') AS C2,
|
||||
TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3,
|
||||
CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C6,
|
||||
COALESCE(ped.pedimento_code, '') AS C7,
|
||||
COALESCE(ped.regime, '') AS C8,
|
||||
COALESCE(TO_CHAR(log.entry_exit_date, 'YYYYMMDD'), '') AS C9,
|
||||
COALESCE(TO_CHAR(log.delivery_date, 'YYYYMMDD'), '') AS C10,
|
||||
COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C11,
|
||||
COALESCE(log.payment_receipt_num, '') AS C12,
|
||||
COALESCE(cmp.provider_id::text, '') AS C14,
|
||||
COALESCE(cmp.sold_to_id::text, '') AS C15,
|
||||
COALESCE(cmp.customs_broker_id::text, '') AS C16,
|
||||
COALESCE(ih.purchase_order, '') AS C27,
|
||||
COALESCE(cmp.aduana, '') AS C33,
|
||||
COALESCE(ih.invoice_type, '') AS C34,
|
||||
ih.id AS C35,
|
||||
COALESCE(cmp.edocument, '') AS C40,
|
||||
COALESCE(cmp.vucem_operation_num, '') AS C41,
|
||||
COALESCE(fin.exchange_rate, 0) AS C48,
|
||||
COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C49,
|
||||
COALESCE(ih.capture_user, '') AS C50,
|
||||
COALESCE(ih.who_updated, '') AS C51,
|
||||
COALESCE(log.transport_id || ' ' || log.transport_num, '') AS C53,
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0) AS total_me,
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0) AS total_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.items i ON i.invoice_id = ih.id
|
||||
LEFT JOIN a76.item_lines il ON il.item_id = i.id
|
||||
LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id
|
||||
LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
WHERE {where_clause}
|
||||
GROUP BY ih.id, ih.invoice_number, ih.is_updated, ped.pedimento_number, ped.pedimento_code, ped.regime,
|
||||
log.entry_exit_date, log.delivery_date, log.payment_date, log.payment_receipt_num,
|
||||
cmp.provider_id, cmp.sold_to_id, cmp.customs_broker_id, ih.purchase_order, cmp.aduana,
|
||||
ih.invoice_type, cmp.edocument, cmp.vucem_operation_num, fin.exchange_rate,
|
||||
ih.emission_date, ih.capture_user, ih.who_updated, log.transport_id, log.transport_num,
|
||||
ih.invoice_date
|
||||
ORDER BY ih.invoice_number
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_main_query(db_name: str, where_clause: str) -> str:
|
||||
return f"""
|
||||
SELECT
|
||||
ih.invoice_number AS C1, -- [0]
|
||||
ped.pedimento_number AS C2, -- [1]
|
||||
ih.invoice_date AS C3, -- [2]
|
||||
'' AS C4, -- [3]
|
||||
'' AS C5, -- [4]
|
||||
ped.status AS C6, -- [5]
|
||||
ped.pedimento_code AS C7, -- [6]
|
||||
ped.regime AS C8, -- [7]
|
||||
log.entry_exit_date AS C9, -- [8]
|
||||
log.delivery_date AS C10, -- [9]
|
||||
log.payment_date AS C11, -- [10]
|
||||
log.payment_receipt_num AS C12, -- [11]
|
||||
'' AS C13, -- [12]
|
||||
cmp.provider_id AS C14, -- [13]
|
||||
cmp.sold_to_id AS C15, -- [14]
|
||||
cmp.customs_broker_id AS C16, -- [15]
|
||||
'' AS C17, -- [16]
|
||||
prt.part_number AS C18, -- [17]
|
||||
ld.description_spanish AS C19, -- [18]
|
||||
ld.description_english AS C20, -- [19]
|
||||
lq.quantity AS C21, -- [20]
|
||||
um.code AS C22, -- [21]
|
||||
'' AS C23, -- [22]
|
||||
'' AS C24, -- [23]
|
||||
lq.net_weight AS C25, -- [24]
|
||||
lq.gross_weight AS C26, -- [25]
|
||||
ih.purchase_order AS C27, -- [26]
|
||||
lc.fraction AS C28, -- [27]
|
||||
'' AS C29, -- [28]
|
||||
'' AS C30, -- [29]
|
||||
'' AS C31, -- [30]
|
||||
'' AS C32, -- [31]
|
||||
cmp.aduana AS C33, -- [32]
|
||||
ih.invoice_type AS C34, -- [33]
|
||||
ih.id AS C35, -- [34]
|
||||
lf.value_mxn AS C36, -- [35]
|
||||
lf.value_usd AS C37, -- [36]
|
||||
il.material_type AS C38, -- [37]
|
||||
'' AS C39, -- [38] rectification_id
|
||||
cmp.edocument AS C40, -- [39]
|
||||
cmp.vucem_operation_num AS C41, -- [40]
|
||||
il.line_number AS C42, -- [41]
|
||||
ld.brand AS C43, -- [42]
|
||||
ld.model AS C44, -- [43]
|
||||
prt.us_fraction AS C45, -- [44]
|
||||
prt.eccn AS C46, -- [45]
|
||||
prt.id AS C47, -- [46]
|
||||
fin.exchange_rate AS C48, -- [47]
|
||||
ih.emission_date AS C49, -- [48]
|
||||
ih.capture_user AS C50, -- [49]
|
||||
ih.who_updated AS C51, -- [50]
|
||||
'' AS C52, -- [51]
|
||||
COALESCE(log.transport_id || ' ' || log.transport_num, '') AS C53, -- [52] NumCaja
|
||||
'' AS C54, -- [53] Pedimento18
|
||||
COALESCE(ld.lot, '') AS C55, -- [54] Lote
|
||||
'' AS C56, -- [55] TipoPedimentoTransporte
|
||||
'' AS C57, -- [56]
|
||||
'' AS C58, -- [57]
|
||||
'' AS C59, -- [58]
|
||||
'' AS C60 -- [59] Relleno final
|
||||
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 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
|
||||
LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id
|
||||
LEFT JOIN a76.classes cls ON cls.id = il.class_id
|
||||
LEFT JOIN a76.parts prt ON prt.id = il.part_number
|
||||
LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure
|
||||
WHERE {where_clause}
|
||||
ORDER BY ih.invoice_number, il.line_number
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_totals_query(db_name: str, discharge_clause: str = "") -> str:
|
||||
"""Build query to get totals for an export invoice.
|
||||
|
||||
Only sums partidas where is_subitem is false (main partidas, not sub-items).
|
||||
"""
|
||||
discharge_filter = "AND il.is_discharged = true" if "descargado" in discharge_clause.lower() else ""
|
||||
return f"""
|
||||
SELECT
|
||||
COALESCE(SUM(lf.value_usd), 0),
|
||||
COALESCE(SUM(lf.value_mxn), 0)
|
||||
FROM a76.item_line_financials lf
|
||||
INNER JOIN a76.item_lines il ON il.id = lf.item_line_id
|
||||
INNER JOIN a76.items itm ON itm.id = il.item_id
|
||||
WHERE itm.invoice_id = :consecutivo
|
||||
{discharge_filter}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_series_query(db_name: str) -> str:
|
||||
"""Build query to get series information for exports."""
|
||||
# TODO: QSeriesExpo table not migrated to PostgreSQL yet
|
||||
return """
|
||||
SELECT '' as serie, '' as modelo, '' as parte
|
||||
WHERE 1=0
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_driver_badge_query(db_name: str) -> str:
|
||||
"""Build query to get driver badge number for exports."""
|
||||
# TODO: GConductor table not migrated to PostgreSQL yet
|
||||
return """
|
||||
SELECT '' as badge
|
||||
WHERE 1=0
|
||||
"""
|
||||
|
||||
|
||||
class ExportRepairQueries:
|
||||
"""SQL queries for export repairs (PostgreSQL schema)."""
|
||||
|
||||
@staticmethod
|
||||
def build_aggregated_query(db_name: str, where_str: str, discharge_clause: str = "") -> str:
|
||||
"""Build optimized query for NORMAL mode (grouped by invoice with totals)."""
|
||||
# Note: discharge_clause temporarily disabled until is_discharged field migrated
|
||||
discharge_filter = "" # Will be: " AND il.is_discharged = true/false" when ready
|
||||
return f"""
|
||||
SELECT
|
||||
ih.invoice_number AS C1,
|
||||
COALESCE(ped.pedimento_number, '') AS C2,
|
||||
TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3,
|
||||
CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C6,
|
||||
COALESCE(ped.pedimento_code, '') AS C7,
|
||||
COALESCE(ped.regime, '') AS C8,
|
||||
COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C11,
|
||||
COALESCE(cmp.remesa::text, '') AS C12,
|
||||
COALESCE(fin.exchange_rate, 0) AS C13,
|
||||
COALESCE(cmp.provider_id::text, '') AS C14,
|
||||
COALESCE(cmp.sold_to_id::text, '') AS C15,
|
||||
COALESCE(cmp.customs_broker_id::text, '') AS C16,
|
||||
COALESCE(ih.purchase_order, '') AS C27,
|
||||
COALESCE(ped.customs_office, '') AS C33,
|
||||
COALESCE(ih.document_type, '') AS C34,
|
||||
ih.id AS C35,
|
||||
COALESCE(cmp.edocument, '') AS C40,
|
||||
COALESCE(cmp.vucem_operation_num, '') AS C41,
|
||||
COALESCE(fin.exchange_rate, 0) AS C48,
|
||||
COALESCE(TO_CHAR(ih.invoice_date, 'YYYYMMDD'), '') AS C49,
|
||||
COALESCE(ih.capture_user, '') AS C50,
|
||||
COALESCE(ih.who_updated, '') AS C51,
|
||||
COALESCE(log.carrier_id, '') AS C52,
|
||||
COALESCE(log.transport_id || ' ' || log.transport_num, '') AS C53,
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0) AS total_me,
|
||||
COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0) AS total_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.items i ON i.invoice_id = ih.id
|
||||
LEFT JOIN a76.item_lines il ON il.item_id = i.id
|
||||
LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id
|
||||
LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
WHERE ih.operation_type = 'exp'
|
||||
AND ih.invoice_type = 'REP'
|
||||
{"AND " + where_str if where_str else ""}
|
||||
GROUP BY ih.id, ih.invoice_number, ped.pedimento_number, ped.pedimento_code, ped.regime,
|
||||
log.payment_date, cmp.remesa, fin.exchange_rate, cmp.provider_id, cmp.sold_to_id,
|
||||
cmp.customs_broker_id, ih.purchase_order, ped.customs_office, ih.document_type,
|
||||
cmp.edocument, cmp.vucem_operation_num, ih.invoice_date, ih.capture_user,
|
||||
ih.who_updated, log.carrier_id, log.transport_id, log.transport_num
|
||||
ORDER BY ih.invoice_number
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_main_query(db_name: str, where_str: str) -> str:
|
||||
"""Build main SQL query for export repair data."""
|
||||
return f"""
|
||||
SELECT
|
||||
ih.invoice_number AS C1,
|
||||
COALESCE(ped.pedimento_number, '') AS C2,
|
||||
TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3,
|
||||
COALESCE(fin.value_me, 0) AS C4,
|
||||
COALESCE(fin.value_mn, 0) AS C5,
|
||||
CASE WHEN ih.is_updated THEN 'AC' ELSE 'NA' END AS C6,
|
||||
COALESCE(ped.pedimento_code, '') AS C7,
|
||||
COALESCE(ped.regime, '') AS C8,
|
||||
'' AS C9,
|
||||
'' AS C10,
|
||||
COALESCE(TO_CHAR(log.payment_date, 'YYYYMMDD'), '') AS C11,
|
||||
COALESCE(cmp.remesa::text, '') AS C12,
|
||||
COALESCE(fin.exchange_rate, 0) AS C13,
|
||||
COALESCE(cmp.provider_id::text, '') AS C14,
|
||||
COALESCE(cmp.sold_to_id::text, '') AS C15,
|
||||
COALESCE(cmp.customs_broker_id::text, '') AS C16,
|
||||
'' AS C17,
|
||||
COALESCE(cls.class_code, '') AS C18,
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C19,
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(cls.description_en, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C20,
|
||||
COALESCE(lq.quantity, 0) AS C21,
|
||||
COALESCE(il.unit_of_measure, 0) AS C22,
|
||||
COALESCE(lf.customs_value_mxn, 0) AS C23,
|
||||
COALESCE(lf.customs_value_usd, 0) AS C24,
|
||||
COALESCE(lq.net_weight, 0) AS C25,
|
||||
COALESCE(lq.gross_weight, 0) AS C26,
|
||||
COALESCE(ih.purchase_order, '') AS C27,
|
||||
COALESCE(lc.fraction, '') AS C28,
|
||||
COALESCE(lc.fraction_type, '') AS C29,
|
||||
COALESCE(lc.advalorem_numeric, 0) AS C30,
|
||||
COALESCE(lc.sector, '') AS C31,
|
||||
COALESCE(lc.origin_country, '') AS C32,
|
||||
COALESCE(ped.customs_office, '') AS C33,
|
||||
COALESCE(ih.document_type, '') AS C34,
|
||||
ih.id AS C35,
|
||||
COALESCE(lf.value_mxn, 0) AS C36,
|
||||
COALESCE(lf.value_usd, 0) AS C37,
|
||||
'P' AS C38,
|
||||
'' AS C39,
|
||||
COALESCE(cmp.edocument, '') AS C40,
|
||||
COALESCE(cmp.vucem_operation_num, '') AS C41,
|
||||
COALESCE(il.line_number, 0) AS C42,
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C43,
|
||||
REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C44,
|
||||
COALESCE(cls.us_fraction, '') AS C45,
|
||||
COALESCE(prt.eccn, '') AS C46,
|
||||
COALESCE(il.part_number::text, '') AS C47,
|
||||
COALESCE(fin.exchange_rate, 0) AS C48,
|
||||
COALESCE(TO_CHAR(ih.invoice_date, 'YYYYMMDD'), '') AS C49,
|
||||
COALESCE(ih.capture_user, '') AS C50,
|
||||
COALESCE(ih.who_updated, '') AS C51,
|
||||
COALESCE(log.carrier_id, '') AS C52,
|
||||
COALESCE(log.transport_id || ' ' || log.transport_num, '') AS C53,
|
||||
'' AS C54,
|
||||
COALESCE(ld.lot, '') AS C55,
|
||||
'' AS C56,
|
||||
'' AS C57,
|
||||
'' AS C58,
|
||||
'' AS C59
|
||||
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 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
|
||||
LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id
|
||||
LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id
|
||||
LEFT JOIN a76.classes cls ON cls.id = il.class_id
|
||||
LEFT JOIN a76.parts prt ON prt.id = il.part_number
|
||||
LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure
|
||||
WHERE UPPER(ih.operation_type) IN ('EXP', 'TRA', 'RET')
|
||||
AND UPPER(ih.invoice_type) IN ('DEF', 'REP', 'EXDEF', 'MATDE')
|
||||
AND {where_str}
|
||||
ORDER BY ih.invoice_number, il.line_number
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_totals_query(db_name: str, discharge_clause: str = "") -> str:
|
||||
"""Build query to get totals for an export repair invoice."""
|
||||
discharge_filter = "AND il.is_discharged = true" if "descargado" in discharge_clause.lower() else ""
|
||||
return f"""
|
||||
SELECT
|
||||
COALESCE(SUM(lf.value_usd), 0),
|
||||
COALESCE(SUM(lf.value_mxn), 0)
|
||||
FROM a76.item_line_financials lf
|
||||
INNER JOIN a76.item_lines il ON il.id = lf.item_line_id
|
||||
INNER JOIN a76.items itm ON itm.id = il.item_id
|
||||
WHERE itm.invoice_id = :consecutivo
|
||||
|
||||
{discharge_filter}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_series_query(db_name: str) -> str:
|
||||
"""Build query to get series information for export repairs."""
|
||||
# TODO: QSeriesExpoRep table not migrated to PostgreSQL yet
|
||||
return """
|
||||
SELECT '' as serie, '' as modelo, '' as parte
|
||||
WHERE 1=0
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build_driver_badge_query(db_name: str) -> str:
|
||||
"""Build query to get driver badge number for export repairs."""
|
||||
# TODO: GConductor table not migrated to PostgreSQL yet
|
||||
return """
|
||||
SELECT '' as badge
|
||||
WHERE 1=0
|
||||
"""
|
||||
Reference in New Issue
Block a user