Files
Autoparts-DB/scripts/import_rached_excel.py
consultoria-as ea29cc31c0 feat(catalog): supplier catalog cleanup, fuzzy matching, and navigation fixes
- Cleaned 137+ fake engine-displacement models from supplier imports
  (v3/v4 scripts: Chevrolet, Ford, Chrysler, Dodge, Jeep, Nissan, etc.)
- Removed 1,251+ corrupted models (INT. prefixes, year-suffix, torque specs,
  empty names, trailing-year variants)
- Migrated supplier tables to master DB (supplier_catalog,
  supplier_catalog_compat, supplier_catalog_interchange)
- Fixed _get_mye_ids_with_parts() to query supplier_catalog_compat from
  master DB so supplier-only vehicles appear for all tenants
- Added fuzzy model matcher with parenthesis stripping, noise suffix removal,
  compact matching, prefix/substring fallback, model aliases, and ±3 year
  proximity
- Matched compat rows: KEEP GREEN +14,152, KNADIAN +3,021, VAZLO +127,500,
  LUK +477, RAYBESTOS +1,743
- Added KNADIAN catalog importer with year-range expansion and future-year
  filtering
- Added VAZLO catalog importer with position parsing and SKU-in-model cleanup
- Added Keep Green, LUK, Yokomitsu, Raybestos catalog importers
- Cache clearing after cleanups (_classify_cache_*, nexus:mye_ids:*,
  nexus:brand_mye_counts:*)

Final match rates:
- KEEP GREEN: 90.3%
- VAZLO: 93.6%
- YOKOMITSU: 100.0%
- KNADIAN: 57.4%
- LUK: 51.0%
- RAYBESTOS: 55.9%
2026-06-09 07:47:42 +00:00

184 lines
6.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Importar inventario de refaccionaria_rached desde Excel.
Archivo fuente: /home/Autopartes/data/PRODUCTOS_RACHED_2026.xlsx
Hoja: Hoja1
Columnas:
A: Codigo -> part_number
B: CB -> barcode (ignored, mostly empty)
C: Cve -> sku_alias (inventory_sku_aliases)
D: Descripcion -> name
E: Precio Costo -> cost
F: Precio Venta -> price_1
No hay columnas de stock, marca, ni vehiculo. Stock se deja en 0.
"""
import os
import sys
import re
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'pos'))
import psycopg2
from services.barcode_generator import generate_barcodes_batch
# ─── Config ──────────────────────────────────────────
DB_NAME = "tenant_refaccionaria_rached"
BRANCH_ID = 1
EXCEL_PATH = "/home/Autopartes/data/PRODUCTOS_RACHED_2026.xlsx"
BATCH_SIZE = 500
# Connect as local postgres user (peer auth)
conn = psycopg2.connect(f"dbname={DB_NAME} user=postgres")
conn.autocommit = False
cur = conn.cursor()
# ─── Read Excel ──────────────────────────────────────
import openpyxl
wb = openpyxl.load_workbook(EXCEL_PATH, data_only=True)
ws = wb["Hoja1"]
rows = list(ws.iter_rows(min_row=2, values_only=True))
print(f"Filas leidas del Excel: {len(rows)}")
# ─── Pre-fetch existing part_numbers ─────────────────
existing_map = {}
cur.execute("SELECT id, part_number FROM inventory WHERE branch_id = %s", (BRANCH_ID,))
for item_id, pn in cur.fetchall():
existing_map[pn.strip().upper()] = item_id
cur.close()
conn.commit()
# ─── Prepare lists ───────────────────────────────────
to_insert = [] # (part_number, name, cost, price_1)
to_alias = [] # (part_number, alias_sku)
skipped = 0
for row in rows:
codigo = str(row[0]).strip() if row[0] is not None else ""
cve = str(row[2]).strip() if row[2] is not None else ""
descripcion = str(row[3]).strip() if row[3] is not None else ""
precio_costo = float(row[4]) if row[4] is not None else 0.0
precio_venta = float(row[5]) if row[5] is not None else 0.0
if not codigo or not descripcion:
skipped += 1
continue
# Clean description (remove weird chars)
descripcion = descripcion.replace("\x81", "").replace("\x80", "").strip()
to_insert.append((codigo, descripcion, precio_costo, precio_venta))
if cve:
to_alias.append((codigo, cve))
print(f"Filas validas para importar: {len(to_insert)}")
print(f"Filas con SKU alternativo (Cve): {len(to_alias)}")
print(f"Filas saltadas (sin codigo/descripcion): {skipped}")
# ─── Batch insert / update inventory ─────────────────
cur = conn.cursor()
inserted_count = 0
updated_count = 0
# Split into new vs existing
new_items = []
update_items = []
for codigo, descripcion, cost, price in to_insert:
key = codigo.upper()
if key in existing_map:
update_items.append((descripcion, cost, price, existing_map[key]))
else:
new_items.append((codigo, descripcion, cost, price))
print(f"Nuevos: {len(new_items)} | Existentes a actualizar: {len(update_items)}")
# Generate barcodes for new items in batch
barcodes = []
if new_items:
barcodes = generate_barcodes_batch(conn, DB_NAME, len(new_items))
# Insert new items
for i, (codigo, descripcion, cost, price) in enumerate(new_items):
barcode = barcodes[i]
cur.execute(
"""
INSERT INTO inventory
(branch_id, part_number, barcode, name, cost, price_1, unit, is_active)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (branch_id, part_number) DO UPDATE SET
name = EXCLUDED.name,
cost = CASE WHEN EXCLUDED.cost > 0 THEN EXCLUDED.cost ELSE inventory.cost END,
price_1 = CASE WHEN EXCLUDED.price_1 > 0 THEN EXCLUDED.price_1 ELSE inventory.price_1 END
RETURNING id, (xmax = 0) AS inserted
""",
(BRANCH_ID, codigo, barcode, descripcion, cost, price, "PZA", True)
)
item_id, was_inserted = cur.fetchone()
if was_inserted:
inserted_count += 1
else:
updated_count += 1
# Add to map for alias linking
existing_map[codigo.upper()] = item_id
if (i + 1) % BATCH_SIZE == 0:
conn.commit()
print(f" Procesados {i + 1}/{len(new_items)} nuevos...")
# Update existing items (that weren't caught by ON CONFLICT above, if any)
for descripcion, cost, price, item_id in update_items:
cur.execute(
"""
UPDATE inventory SET
name = %s,
cost = CASE WHEN %s > 0 THEN %s ELSE cost END,
price_1 = CASE WHEN %s > 0 THEN %s ELSE price_1 END
WHERE id = %s
""",
(descripcion, cost, cost, price, price, item_id)
)
updated_count += 1
conn.commit()
print(f"Insertados: {inserted_count} | Actualizados: {updated_count}")
# ─── Insert SKU aliases ──────────────────────────────
alias_inserted = 0
alias_skipped = 0
for codigo, cve in to_alias:
item_id = existing_map.get(codigo.upper())
if not item_id:
alias_skipped += 1
continue
try:
cur.execute(
"""
INSERT INTO inventory_sku_aliases (inventory_id, sku, label)
VALUES (%s, %s, %s)
ON CONFLICT (inventory_id, sku) DO NOTHING
""",
(item_id, cve, "Cve")
)
if cur.rowcount > 0:
alias_inserted += 1
except Exception as e:
print(f" Alias error for {codigo}/{cve}: {e}")
alias_skipped += 1
conn.commit()
cur.close()
conn.close()
print("\n========================================")
print("IMPORTACION RACHED COMPLETADA")
print("========================================")
print(f"Filas procesadas: {len(to_insert)}")
print(f"Nuevos insertados: {inserted_count}")
print(f"Exist. actualizados:{updated_count}")
print(f"SKU aliases creados:{alias_inserted}")
print(f"Aliases fallidos: {alias_skipped}")
print(f"Filas saltadas: {skipped}")
print("========================================")