""" Fix subscription details: add actual_price, custom_description from legacy hijack data. Also populate item_code and item_group for display. Run inside erpnext-backend-1: /home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/fix_subscription_details.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) # ═══════════════════════════════════════════════════════════════ # STEP 1: Add custom fields if they don't exist # ═══════════════════════════════════════════════════════════════ print("\n" + "=" * 60) print("STEP 1: ADD CUSTOM FIELDS") print("=" * 60) fields_to_add = [ ("actual_price", "Decimal", "Actual Price"), ("custom_description", "Small Text", "Custom Description"), ("item_code", "Data", "Item Code"), ("item_group", "Data", "Item Group"), ("billing_frequency", "Data", "Billing Frequency"), ] for fieldname, fieldtype, label in fields_to_add: existing = frappe.db.sql(""" SELECT column_name FROM information_schema.columns WHERE table_name = 'tabSubscription' AND column_name = %s """, (fieldname,)) if not existing: # Add column directly if fieldtype == "Decimal": frappe.db.sql('ALTER TABLE "tabSubscription" ADD COLUMN {} DECIMAL(18,6) DEFAULT 0'.format(fieldname)) else: frappe.db.sql('ALTER TABLE "tabSubscription" ADD COLUMN {} VARCHAR(512)'.format(fieldname)) print(" Added column: {}".format(fieldname)) else: print(" Column exists: {}".format(fieldname)) frappe.db.commit() # ═══════════════════════════════════════════════════════════════ # STEP 2: Load legacy service data (hijack prices + descriptions) # ═══════════════════════════════════════════════════════════════ print("\n" + "=" * 60) print("STEP 2: LOAD LEGACY SERVICE DATA") print("=" * 60) conn = pymysql.connect( host="10.100.80.100", user="facturation", password="*******", database="gestionclient", cursorclass=pymysql.cursors.DictCursor ) with conn.cursor() as cur: cur.execute(""" SELECT s.id, s.hijack, s.hijack_price, s.hijack_desc, p.sku, p.price as base_price, p.category, pc.name as cat_name FROM service s JOIN product p ON p.id = s.product_id LEFT JOIN product_cat pc ON pc.id = p.category WHERE s.id > 0 """) legacy_services = {} for r in cur.fetchall(): actual_price = float(r["hijack_price"]) if r["hijack"] else float(r["base_price"] or 0) desc = r["hijack_desc"] if r["hijack"] and r["hijack_desc"] else "" cat = html.unescape(r["cat_name"]) if r["cat_name"] else "" legacy_services[r["id"]] = { "actual_price": actual_price, "description": desc.strip(), "sku": r["sku"], "category": cat, } conn.close() print("Legacy services loaded: {}".format(len(legacy_services))) # ═══════════════════════════════════════════════════════════════ # STEP 3: Load ERPNext Item info # ═══════════════════════════════════════════════════════════════ items = frappe.db.sql(""" SELECT name, item_name, item_group FROM "tabItem" """, as_dict=True) item_map = {i["name"]: i for i in items} print("Items loaded: {}".format(len(item_map))) # ═══════════════════════════════════════════════════════════════ # STEP 4: Update subscriptions # ═══════════════════════════════════════════════════════════════ print("\n" + "=" * 60) print("STEP 3: UPDATE SUBSCRIPTIONS") print("=" * 60) # Get all subscriptions with their plan details subs = frappe.db.sql(""" SELECT s.name, s.legacy_service_id, spd.plan, sp.item, sp.cost, sp.billing_interval FROM "tabSubscription" s LEFT JOIN "tabSubscription Plan Detail" spd ON spd.parent = s.name LEFT JOIN "tabSubscription Plan" sp ON sp.plan_name = spd.plan ORDER BY s.name """, as_dict=True) print("Total subscription rows: {}".format(len(subs))) updated = 0 batch_size = 2000 for i, sub in enumerate(subs): legacy_id = sub.get("legacy_service_id") item_code = sub.get("item") or "" plan_cost = float(sub.get("cost") or 0) # Get actual price from legacy leg = legacy_services.get(legacy_id) if legacy_id else None if leg: actual_price = leg["actual_price"] custom_desc = leg["description"] item_code = leg["sku"] or item_code item_group = leg["category"] else: actual_price = plan_cost custom_desc = "" item_group = item_map.get(item_code, {}).get("item_group", "") if item_code else "" # Billing frequency billing_freq = sub.get("billing_interval") or "Month" freq_label = "M" if billing_freq == "Month" else "A" if billing_freq == "Year" else billing_freq[:1] frappe.db.sql(""" UPDATE "tabSubscription" SET actual_price = %s, custom_description = %s, item_code = %s, item_group = %s, billing_frequency = %s WHERE name = %s """, (actual_price, custom_desc, item_code, item_group, freq_label, sub["name"])) updated += 1 if updated % batch_size == 0: frappe.db.commit() print(" Updated {}/{}...".format(updated, len(subs))) frappe.db.commit() print("Updated: {} subscriptions".format(updated)) # ═══════════════════════════════════════════════════════════════ # STEP 4: VERIFY with Expro # ═══════════════════════════════════════════════════════════════ print("\n" + "=" * 60) print("STEP 4: VERIFY (Expro Transit)") print("=" * 60) expro = frappe.db.sql(""" SELECT name, item_code, actual_price, custom_description, item_group, billing_frequency, service_location, radius_user, status FROM "tabSubscription" WHERE party = 'CUST-cbf03814b9' ORDER BY service_location, actual_price DESC """, as_dict=True) for s in expro: is_rebate = float(s["actual_price"] or 0) < 0 indent = " " if is_rebate else " " desc = s["custom_description"] or "" print("{}{} {:>8.2f} {} {} {}".format( indent, (s["item_code"] or "")[:14].ljust(14), float(s["actual_price"] or 0), (s["billing_frequency"] or "M"), s["status"], desc[:50])) frappe.clear_cache() print("\nDone — cache cleared")