gigafibre-fsm/scripts/migration/fix_subscription_details.py
louispaulb 607ea54b5c refactor: reduce token count, DRY code, consolidate docs
Backend services:
- targo-hub: extract deepGetValue to helpers.js, DRY disconnect reasons
  lookup map, compact CAPABILITIES, consolidate vision.js prompts/schemas,
  extract dispatch scoring weights, trim section dividers across 9 files
- modem-bridge: extract getSession() helper (6 occurrences), resetIdleTimer(),
  consolidate DM query factory, fix duplicate username fill bug, trim headers
  (server.js -36%, tplink-session.js -47%, docker-compose.yml -57%)

Frontend:
- useWifiDiagnostic: extract THRESHOLDS const, split processDiagnostic into
  6 focused helpers (processOnlineStatus, processWanIPs, processRadios,
  processMeshNodes, processClients, checkRadioIssues)
- EquipmentDetail: merge duplicate ROLE_LABELS, remove verbose comments

Documentation (17 → 13 files, -1,400 lines):
- New consolidated README.md (architecture, services, dependencies, auth)
- Merge ECOSYSTEM-OVERVIEW into ARCHITECTURE.md
- Merge MIGRATION-PLAN + ARCHITECTURE-COMPARE + FIELD-GAP + CHANGELOG → MIGRATION.md
- Merge COMPETITIVE-ANALYSIS into PLATFORM-STRATEGY.md
- Update ROADMAP.md with current phase status
- Delete CONTEXT.md (absorbed into README)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 08:39:58 -04:00

184 lines
7.5 KiB
Python

"""
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="legacy-db",
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")