- 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%
184 lines
6.2 KiB
Python
Executable File
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("========================================")
|