#!/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("========================================")