""" Update ERPNext Item descriptions from legacy product data. Uses hijack_desc from services and product_translate for French names. Run inside erpnext-backend-1: /home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/update_item_descriptions.py """ import frappe import pymysql import os import html os.chdir("/home/frappe/frappe-bench/sites") frappe.init(site="erp.gigafibre.ca", sites_path=".") frappe.connect() frappe.local.flags.ignore_permissions = True print("Connected:", frappe.local.site) conn = pymysql.connect( host="10.100.80.100", user="facturation", password="*******", database="gestionclient", cursorclass=pymysql.cursors.DictCursor ) # ═══════════════════════════════════════════════════════════════ # STEP 1: Build a description map from legacy data # ═══════════════════════════════════════════════════════════════ print("\n" + "=" * 60) print("STEP 1: BUILD DESCRIPTION MAP") print("=" * 60) with conn.cursor() as cur: # Get all products with French translations cur.execute(""" SELECT p.id, p.sku, p.price, p.category, pt.name as fr_name, pt.description_short as fr_desc FROM product p LEFT JOIN product_translate pt ON pt.product_id = p.id AND pt.language_id = 'fr' WHERE p.active = 1 """) products = cur.fetchall() # Get category names cur.execute("SELECT id, name FROM product_cat") cats = {r["id"]: html.unescape(r["name"]) for r in cur.fetchall()} # Get most common hijack_desc per product (the custom description used on services) cur.execute(""" SELECT product_id, hijack_desc, COUNT(*) as cnt FROM service WHERE hijack = 1 AND hijack_desc IS NOT NULL AND hijack_desc != '' GROUP BY product_id, hijack_desc ORDER BY product_id, cnt DESC """) hijack_descs = {} for r in cur.fetchall(): pid = r["product_id"] if pid not in hijack_descs: hijack_descs[pid] = r["hijack_desc"] conn.close() # Build the best description for each SKU desc_map = {} for p in products: sku = p["sku"] if not sku: continue # Priority: French translation > hijack_desc > category name desc = p["fr_name"] or hijack_descs.get(p["id"]) or "" cat = cats.get(p["category"], "") desc_map[sku] = { "description": desc.strip() if desc else "", "item_group": cat, "price": float(p["price"] or 0), } print("Products mapped: {}".format(len(desc_map))) # Show samples for sku in ["FTTB1000I", "TELEPMENS", "RAB24M", "HVIPFIXE", "FTT_HFAR", "CSERV", "RAB2X", "FTTH_LOCMOD"]: d = desc_map.get(sku, {}) print(" {} -> desc='{}' group='{}' price={}".format(sku, d.get("description", ""), d.get("item_group", ""), d.get("price", ""))) # ═══════════════════════════════════════════════════════════════ # STEP 2: Update ERPNext Items # ═══════════════════════════════════════════════════════════════ print("\n" + "=" * 60) print("STEP 2: UPDATE ERPNEXT ITEMS") print("=" * 60) # Get all items that currently have "Legacy product ID" as description items = frappe.db.sql(""" SELECT name, item_name, description, item_group FROM "tabItem" WHERE description LIKE 'Legacy product ID%%' """, as_dict=True) print("Items with legacy placeholder description: {}".format(len(items))) updated = 0 for item in items: sku = item["name"] legacy = desc_map.get(sku) if not legacy: continue new_desc = legacy["description"] new_name = new_desc if new_desc else sku if new_desc: frappe.db.sql(""" UPDATE "tabItem" SET description = %s, item_name = %s WHERE name = %s """, (new_desc, new_name, sku)) updated += 1 frappe.db.commit() print("Updated: {} items".format(updated)) # Also update Subscription Plan descriptions (plan_name) plans = frappe.db.sql(""" SELECT name, plan_name, item, cost FROM "tabSubscription Plan" """, as_dict=True) plan_updated = 0 for plan in plans: item_sku = plan["item"] legacy = desc_map.get(item_sku) if not legacy or not legacy["description"]: continue new_plan_name = "PLAN-{}".format(item_sku) frappe.db.sql(""" UPDATE "tabSubscription Plan" SET plan_name = %s WHERE name = %s """, (new_plan_name, plan["name"])) plan_updated += 1 frappe.db.commit() print("Updated: {} subscription plans".format(plan_updated)) # ═══════════════════════════════════════════════════════════════ # STEP 3: VERIFY # ═══════════════════════════════════════════════════════════════ print("\n" + "=" * 60) print("STEP 3: VERIFY") print("=" * 60) # Sample items sample = frappe.db.sql(""" SELECT name, item_name, description, item_group FROM "tabItem" WHERE name IN ('FTTB1000I', 'TELEPMENS', 'RAB24M', 'HVIPFIXE', 'FTT_HFAR', 'CSERV', 'RAB2X') ORDER BY name """, as_dict=True) for s in sample: print(" {} | name={} | group={} | desc={}".format( s["name"], s["item_name"], s["item_group"], (s["description"] or "")[:80])) # Count remaining legacy descriptions remaining = frappe.db.sql(""" SELECT COUNT(*) FROM "tabItem" WHERE description LIKE 'Legacy product ID%%' """)[0][0] print("\nRemaining with legacy placeholder: {}".format(remaining)) frappe.clear_cache() print("Done — cache cleared")