#!/usr/bin/env python3 """One-off: import Capi Autopartes inventory from CSV into tenant_capi_autopartes. Replicates the semantics of pos/blueprints/inventory_bp.py::bulk_import_items: - creates missing categories - INSERT new items (with generated barcode) or UPDATE existing by part_number - records initial stock on the main branch via inventory_engine.record_initial - registers sku_secundario aliases Usage (as postgres): MASTER_DB_URL=postgresql://postgres@/nexus_autoparts \ TENANT_DB_URL_TEMPLATE='postgresql://postgres@/{db_name}' \ POS_JWT_SECRET=... REDIS_ENABLED=false \ python3 scripts/import_capi_inventory.py """ import csv import io import os import sys sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'pos')) import psycopg2 # noqa: E402 from services.barcode_generator import generate_barcode # noqa: E402 from services.inventory_engine import record_initial # noqa: E402 CSV_PATH = '/home/Autopartes/data/Inventario capi.csv' TENANT_DB = 'tenant_capi_autopartes' BRANCH_ID = 1 COMMIT_EVERY = 500 def parse_rows(path): raw = open(path, 'rb').read() text = None for enc in ('utf-8-sig', 'cp1252', 'latin-1'): try: text = raw.decode(enc) break except UnicodeDecodeError: continue if text is None: raise RuntimeError('Could not decode CSV') return list(csv.DictReader(io.StringIO(text))) def to_decimal(val, default=0.0): try: v = float(str(val).replace(',', '').strip()) return v except (ValueError, TypeError): return default def main(): rows = parse_rows(CSV_PATH) print(f'Filas en CSV: {len(rows)}') conn = psycopg2.connect(dbname=TENANT_DB) cur = conn.cursor() # 1. Create categories cat_names = sorted({r['categoria'].strip() for r in rows if r['categoria'].strip()}) for name in cat_names: cur.execute("INSERT INTO categories (name) VALUES (%s) ON CONFLICT DO NOTHING", (name,)) conn.commit() cur.execute("SELECT id, name FROM categories") category_map = {r[1].strip().lower(): r[0] for r in cur.fetchall()} print(f'Categorías creadas/existentes: {len(category_map)}') # 2. Import items created = updated = skipped = 0 warnings = [] for i, row in enumerate(rows, start=1): part_number = row['numero_de_parte'].strip() name = row['nombre'].strip() if not part_number or not name: skipped += 1 warnings.append(f'Fila {i}: sin numero_de_parte o nombre') continue brand = row['marca'].strip() price_1 = to_decimal(row['precio']) stock = int(to_decimal(row['cantidad'])) cost = to_decimal(row['costo']) description = (row.get('descripcion') or '').strip() category_name = row['categoria'].strip() category_id = category_map.get(category_name.lower()) if category_name else None cur.execute("SELECT id FROM inventory WHERE part_number = %s", (part_number,)) existing = cur.fetchone() if existing: item_id = existing[0] cur.execute(""" UPDATE inventory SET name = %s, brand = COALESCE(NULLIF(%s,''), brand), cost = CASE WHEN %s > 0 THEN %s ELSE cost END, price_1 = CASE WHEN %s > 0 THEN %s ELSE price_1 END, description = COALESCE(NULLIF(%s,''), description), category_id = COALESCE(%s, category_id) WHERE id = %s """, (name, brand, cost, cost, price_1, price_1, description, category_id, item_id)) updated += 1 if stock > 0: record_initial(conn, item_id, BRANCH_ID, stock, cost if cost > 0 else None) else: barcode = generate_barcode(conn, TENANT_DB) cur.execute(""" INSERT INTO inventory (part_number, barcode, name, brand, cost, price_1, location, description, category_id, unit) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id """, (part_number, barcode, name, brand, cost, price_1, '', description, category_id, 'PZA')) item_id = cur.fetchone()[0] created += 1 if stock > 0: record_initial(conn, item_id, BRANCH_ID, stock, cost if cost > 0 else None) sku_secondary = (row.get('sku_secundario') or '').strip() if sku_secondary: cur.execute(""" INSERT INTO inventory_sku_aliases (inventory_id, sku, label) VALUES (%s, %s, %s) ON CONFLICT DO NOTHING """, (item_id, sku_secondary, 'Secundario')) if i % COMMIT_EVERY == 0: conn.commit() print(f' {i}/{len(rows)} procesadas (nuevos={created}, act={updated})', flush=True) conn.commit() cur.execute("SELECT count(*) FROM inventory") total_inv = cur.fetchone()[0] cur.execute("SELECT COALESCE(SUM(stock),0) FROM inventory_stock WHERE branch_id = %s", (BRANCH_ID,)) total_stock = cur.fetchone()[0] cur.execute("SELECT count(*) FROM inventory_sku_aliases") total_alias = cur.fetchone()[0] print('\n=== RESUMEN ===') print(f'Nuevos: {created} | Actualizados: {updated} | Saltados: {skipped}') print(f'Total inventario: {total_inv} | Stock total sucursal {BRANCH_ID}: {total_stock} | Alias: {total_alias}') if warnings: print(f'Advertencias ({len(warnings)}):') for w in warnings[:20]: print(' -', w) cur.close() conn.close() if __name__ == '__main__': main()