"""Bulk catalog import service. Imports products into inventory with optional vehicle compatibilities and SKU aliases. Can auto-generate vehicle fitment via QWEN AI if compatibilities are not provided. """ import logging from typing import Optional logger = logging.getLogger(__name__) def import_products( tenant_conn, products: list[dict], branch_id: int, auto_generate_compat: bool = False, employee_id: Optional[int] = None, ): """Import a list of products into inventory. Each product dict may contain: - sku (str) *required - name (str) *required - brand (str) - description (str) - cost (float) - price (float) - stock (int) - location (str) - sku_aliases (list[dict]) [{"sku": str, "label": str}] - vehicles (list[dict]) [{"make", "model", "year", "engine", "engine_code"}] Returns {"imported": N, "failed": [{"sku": ..., "error": ...}], "compat_generated": M} """ cur = tenant_conn.cursor() imported = 0 failed = [] compat_generated = 0 for idx, p in enumerate(products): sku = (p.get("sku") or "").strip() name = (p.get("name") or "").strip() if not sku or not name: failed.append({"index": idx, "sku": sku, "error": "sku and name are required"}) continue brand = (p.get("brand") or "").strip() or None description = (p.get("description") or "").strip() or None cost = float(p.get("cost") or 0) price = float(p.get("price") or 0) stock = int(p.get("stock") or 0) location = (p.get("location") or "").strip() or None barcode = (p.get("barcode") or "").strip() or None try: # Check for duplicate SKU in same branch cur.execute( "SELECT id FROM inventory WHERE part_number = %s AND branch_id = %s AND is_active = true", (sku, branch_id), ) if cur.fetchone(): # Update existing item instead of creating new cur.execute( """ UPDATE inventory SET name = %s, brand = %s, description = %s, cost = %s, price_1 = %s, location = %s, barcode = COALESCE(%s, barcode), updated_at = NOW() WHERE part_number = %s AND branch_id = %s AND is_active = true RETURNING id """, (name, brand, description, cost, price, location, barcode, sku, branch_id), ) row = cur.fetchone() item_id = row[0] else: # Insert new item cur.execute( """ INSERT INTO inventory (branch_id, part_number, barcode, name, description, brand, unit, cost, price_1, price_2, price_3, tax_rate, min_stock, max_stock, location, is_active) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, true) RETURNING id """, ( branch_id, sku, barcode, name, description, brand, "PZA", cost, price, price, price, 0.16, 0, 0, location, ), ) row = cur.fetchone() item_id = row[0] # Record initial stock if provided if stock > 0: from services.inventory_engine import record_initial record_initial(tenant_conn, item_id, branch_id, stock, cost) # Insert SKU aliases aliases = p.get("sku_aliases") or [] for alias in aliases: alias_sku = (alias.get("sku") or "").strip() label = (alias.get("label") or "").strip() or None if alias_sku: cur.execute( """ INSERT INTO inventory_sku_aliases (inventory_id, sku, label) VALUES (%s, %s, %s) ON CONFLICT (inventory_id, sku) DO UPDATE SET is_active = true, label = EXCLUDED.label """, (item_id, alias_sku, label), ) # Insert manual vehicle compatibilities vehicles = p.get("vehicles") or [] for v in vehicles: make = (v.get("make") or "").strip() model = (v.get("model") or "").strip() year = v.get("year") engine = (v.get("engine") or "").strip() or None engine_code = (v.get("engine_code") or "").strip() or None if make and model and year: cur.execute( """ INSERT INTO inventory_vehicle_compat (inventory_id, make, model, year, engine, engine_code, source, model_year_engine_id) VALUES (%s, %s, %s, %s, %s, %s, 'manual', NULL) ON CONFLICT DO NOTHING """, (item_id, make, model, year, engine, engine_code), ) tenant_conn.commit() imported += 1 # Auto-generate compat via QWEN if requested and no vehicles provided if auto_generate_compat and not vehicles: try: from services.qwen_fitment import get_vehicle_fitment from services.inventory_vehicle_compat import save_qwen_fitment fitment = get_vehicle_fitment(sku, name, brand or "") inserted = save_qwen_fitment(tenant_conn, item_id, fitment) compat_generated += inserted except Exception as qe: logger.warning("QWEN auto-match failed for %s: %s", sku, qe) except Exception as e: tenant_conn.rollback() logger.warning("Import failed for sku=%s: %s", sku, e) failed.append({"index": idx, "sku": sku, "error": str(e)}) cur.close() return {"imported": imported, "failed": failed, "compat_generated": compat_generated}