119 lines
5.3 KiB
Python
119 lines
5.3 KiB
Python
"""
|
|
Configuración de plantillas CSV: columnas que trae cada plantilla y cómo se mapean.
|
|
La plantilla se respeta tal cual: solo se leen columnas definidas aquí; el resto se ignora.
|
|
Solo se escribe en BD lo que los modelos de facturas aceptan (respetando models).
|
|
"""
|
|
|
|
from typing import Dict, List, Any, Optional
|
|
|
|
# Cada plantilla define sus columnas canónicas y alias (otros nombres que aceptamos en el CSV).
|
|
# canonical = nombre estándar con el que trabajamos internamente; debe coincidir con lo que
|
|
# espera la lógica de validación e insert (tasks.py).
|
|
# aliases = cabeceras alternativas que la plantilla .xls puede traer (ej. "Num Factura" → NUM FACTURA).
|
|
|
|
TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = {
|
|
# --- Encabezado factura: Impo Temp (EstructuraEncFacImpoTemp.xls) ---
|
|
"imp_temp_header": [
|
|
{"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA", "ID"]},
|
|
{"canonical": "FECHA FACTURA", "aliases": ["FECHA"]},
|
|
{"canonical": "FECHA EMISION"},
|
|
{"canonical": "CLAVE PROVEEDOR"},
|
|
{"canonical": "CLAVE VENDIDO A"},
|
|
{"canonical": "CLAVE ENVIADO A"},
|
|
{"canonical": "REGIMEN", "aliases": ["CLAVEDOCUMENTO"]},
|
|
{"canonical": "ADUANA DE CRUCE"},
|
|
{"canonical": "CLAVE MONEDA"},
|
|
{"canonical": "CLAVE INCOTERM"},
|
|
{"canonical": "TIPO MONEDA"},
|
|
{"canonical": "TIPO DE CAMBIO"},
|
|
{"canonical": "TIPO PESO"},
|
|
{"canonical": "TIPO TRANSPORTE"},
|
|
{"canonical": "REMESA"},
|
|
{"canonical": "AGENTE ADUANAL"},
|
|
{"canonical": "FLETES"},
|
|
{"canonical": "VALOR SEGUROS"},
|
|
{"canonical": "SEGUROS"},
|
|
{"canonical": "EMBALAJES"},
|
|
{"canonical": "OTROS INCREMENTABLES"},
|
|
{"canonical": "NUM PROYECTO", "aliases": ["NUMPROYECTO"]},
|
|
{"canonical": "ORDEN COMPRA", "aliases": ["ORDENCOMPRA"]},
|
|
{"canonical": "FACTURA ALTERNA"},
|
|
{"canonical": "FACTURA EXPO REF", "aliases": ["FACTURAEXPOREF"]},
|
|
{"canonical": "OBSERVACIONES E"},
|
|
{"canonical": "OBSERVACIONES I"},
|
|
{"canonical": "E DOCUMENT"},
|
|
{"canonical": "NUM OPERACION"},
|
|
{"canonical": "CLAVE TRANSPORTISTA"},
|
|
{"canonical": "NOMBRE CONDUCTOR"},
|
|
{"canonical": "NUMERO TRANSPORTE"},
|
|
{"canonical": "PRECINTO"},
|
|
],
|
|
# --- Encabezado factura: Impo Def (EstructuraEncFacImpoDef.xls) - misma estructura ---
|
|
"imp_def_header": None, # se resuelve igual que imp_temp_header
|
|
# --- Encabezado factura: Expo (EstructuraEncFacExpoCamReg.xls) - misma estructura ---
|
|
"exp_def_header": None,
|
|
# --- Partidas factura: Impo Temp (EstructuraParFacImpoTempAF.xls) ---
|
|
"imp_temp_details": [
|
|
{"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA"]},
|
|
{"canonical": "LINEA", "aliases": ["RENGLON", "PARTIDA"]},
|
|
{"canonical": "NUMPARTE", "aliases": ["NUMERO PARTE"]},
|
|
{"canonical": "PRECIO UNITARIO", "aliases": ["PRECIOUNITARIO"]},
|
|
{"canonical": "VALOR COMERCIAL", "aliases": ["VALORCOMERCIAL"]},
|
|
{"canonical": "CANTIDAD"},
|
|
{"canonical": "CANTIDAD BULTOS", "aliases": ["CANTIDADBULTOS"]},
|
|
{"canonical": "DESCRIPCION"},
|
|
{"canonical": "PAIS ORIGEN", "aliases": ["PAISORIGEN"]},
|
|
{"canonical": "FRACCION"},
|
|
{"canonical": "ORDEN DE COMPRA", "aliases": ["ORDENCOMPRA"]},
|
|
],
|
|
# --- Partidas: Impo Def y Expo - misma estructura ---
|
|
"imp_def_details": None,
|
|
"exp_def_details": None,
|
|
}
|
|
|
|
|
|
def _resolve_template_columns(template_id: str) -> Optional[List[Dict[str, Any]]]:
|
|
cols = TEMPLATE_COLUMNS.get(template_id)
|
|
if cols is not None:
|
|
return cols
|
|
if template_id in ("imp_def_header", "exp_def_header"):
|
|
return TEMPLATE_COLUMNS.get("imp_temp_header")
|
|
if template_id in ("imp_def_details", "exp_def_details"):
|
|
return TEMPLATE_COLUMNS.get("imp_temp_details")
|
|
return None
|
|
|
|
|
|
def build_normalized_lookup(template_id: str, normalize_header_fn) -> Dict[str, str]:
|
|
"""
|
|
Construye un diccionario: normalized_header -> canonical_name.
|
|
normalize_header_fn(str) -> str debe ser la función que normaliza cabeceras (ej. mayúsculas, sin acentos).
|
|
"""
|
|
cols = _resolve_template_columns(template_id)
|
|
if not cols:
|
|
return {}
|
|
lookup: Dict[str, str] = {}
|
|
for item in cols:
|
|
canonical = item["canonical"]
|
|
lookup[normalize_header_fn(canonical)] = canonical
|
|
for alias in item.get("aliases") or []:
|
|
lookup[normalize_header_fn(alias)] = canonical
|
|
return lookup
|
|
|
|
|
|
def row_from_template(row: Dict[str, Any], template_id: str, normalize_header_fn) -> Dict[str, Any]:
|
|
"""
|
|
A partir de una fila CSV (dict header->value) y un template_id, devuelve un dict
|
|
solo con las columnas de la plantilla, usando nombres canónicos.
|
|
Así la plantilla se respeta: solo entran columnas definidas en la plantilla.
|
|
"""
|
|
lookup = build_normalized_lookup(template_id, normalize_header_fn)
|
|
if not lookup:
|
|
# Sin template definido: comportamiento legacy (normalizar todo)
|
|
return {normalize_header_fn(k): v for k, v in row.items()}
|
|
out: Dict[str, Any] = {}
|
|
for csv_header, value in row.items():
|
|
key_norm = normalize_header_fn(csv_header)
|
|
if key_norm in lookup:
|
|
out[lookup[key_norm]] = value
|
|
return out
|