gigafibre-fsm/scripts/migration/import_customer_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

203 lines
9.0 KiB
Python

"""
Import additional customer details from legacy DB into ERPNext.
Adds custom fields and populates:
- invoice_delivery_method: Email/Paper/Both
- is_commercial: Commercial account flag
- is_bad_payer: Mauvais payeur flag
- tax_category_legacy: Tax group
- contact_name_legacy: Contact person
- tel_home/tel_office/cell: Phone numbers
- mandataire: Authorized representative
- exclude_fees: Frais exclusion flag
- notes_internal: Internal notes (misc)
- date_created_legacy: Account creation date
Run inside erpnext-backend-1:
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/import_customer_details.py
"""
import frappe
import pymysql
import os
from datetime import datetime, timezone
os.chdir("/home/frappe/frappe-bench/sites")
frappe.init(site="erp.gigafibre.ca", sites_path=".")
frappe.connect()
print("Connected:", frappe.local.site)
# ═══════════════════════════════════════════════════════════════
# STEP 1: Create custom fields on Customer
# ═══════════════════════════════════════════════════════════════
print("Creating custom fields on Customer...")
CUSTOM_FIELDS = [
{"fieldname": "billing_section", "label": "Facturation", "fieldtype": "Section Break", "insert_after": "legacy_section"},
{"fieldname": "invoice_delivery_method", "label": "Envoi facture", "fieldtype": "Select", "options": "\nEmail\nPapier\nEmail + Papier", "insert_after": "billing_section"},
{"fieldname": "is_commercial", "label": "Compte commercial", "fieldtype": "Check", "insert_after": "invoice_delivery_method"},
{"fieldname": "is_bad_payer", "label": "Mauvais payeur", "fieldtype": "Check", "insert_after": "is_commercial"},
{"fieldname": "exclude_fees", "label": "Exclure frais", "fieldtype": "Check", "insert_after": "is_bad_payer"},
{"fieldname": "billing_col_break", "label": "", "fieldtype": "Column Break", "insert_after": "exclude_fees"},
{"fieldname": "tax_category_legacy", "label": "Groupe taxe", "fieldtype": "Select", "options": "\nFederal + Provincial (9.5%)\nFederal seulement\nExempté", "insert_after": "billing_col_break"},
{"fieldname": "contact_section", "label": "Contact détaillé", "fieldtype": "Section Break", "insert_after": "tax_category_legacy"},
{"fieldname": "contact_name_legacy", "label": "Contact", "fieldtype": "Data", "insert_after": "contact_section"},
{"fieldname": "mandataire", "label": "Mandataire", "fieldtype": "Data", "insert_after": "contact_name_legacy"},
{"fieldname": "tel_home", "label": "Téléphone maison", "fieldtype": "Data", "insert_after": "mandataire"},
{"fieldname": "contact_col_break", "label": "", "fieldtype": "Column Break", "insert_after": "tel_home"},
{"fieldname": "tel_office", "label": "Téléphone bureau", "fieldtype": "Data", "insert_after": "contact_col_break"},
{"fieldname": "cell_phone", "label": "Cellulaire", "fieldtype": "Data", "insert_after": "tel_office"},
{"fieldname": "fax", "label": "Fax", "fieldtype": "Data", "insert_after": "cell_phone"},
{"fieldname": "notes_section", "label": "Notes", "fieldtype": "Section Break", "insert_after": "fax"},
{"fieldname": "notes_internal", "label": "Notes internes", "fieldtype": "Small Text", "insert_after": "notes_section"},
{"fieldname": "email_billing", "label": "Email facturation", "fieldtype": "Data", "insert_after": "notes_internal"},
{"fieldname": "email_publipostage", "label": "Email publipostage", "fieldtype": "Data", "insert_after": "email_billing"},
{"fieldname": "date_created_legacy", "label": "Date création (legacy)", "fieldtype": "Date", "insert_after": "email_publipostage"},
]
for cf in CUSTOM_FIELDS:
existing = frappe.db.exists("Custom Field", {"dt": "Customer", "fieldname": cf["fieldname"]})
if existing:
print(" {} — already exists".format(cf["fieldname"]))
continue
try:
doc = frappe.get_doc({
"doctype": "Custom Field",
"dt": "Customer",
**cf,
})
doc.insert(ignore_permissions=True)
print(" {} — created".format(cf["fieldname"]))
except Exception as e:
print(" {} — ERR: {}".format(cf["fieldname"], str(e)[:80]))
frappe.db.commit()
print("Custom fields done.")
# ═══════════════════════════════════════════════════════════════
# STEP 2: Load legacy data
# ═══════════════════════════════════════════════════════════════
print("\nLoading legacy data...")
conn = pymysql.connect(
host="legacy-db", user="facturation", password="VD67owoj",
database="gestionclient", cursorclass=pymysql.cursors.DictCursor
)
with conn.cursor() as cur:
cur.execute("""
SELECT id, customer_id, invoice_delivery, commercial, mauvais_payeur,
tax_group, contact, mandataire, tel_home, tel_office, cell, fax,
misc, email, email_autre, date_orig, frais, ppa, notes_client,
address1, address2, city, state, zip
FROM account
WHERE status = 1
""")
accounts = cur.fetchall()
conn.close()
print("Active legacy accounts: {}".format(len(accounts)))
# Customer mapping
cust_map = {}
custs = frappe.db.sql('SELECT name, legacy_account_id FROM "tabCustomer" WHERE legacy_account_id IS NOT NULL AND legacy_account_id > 0', as_dict=True)
for c in custs:
cust_map[c["legacy_account_id"]] = c["name"]
print("Customer mapping: {}".format(len(cust_map)))
# ═══════════════════════════════════════════════════════════════
# STEP 3: Update customers
# ═══════════════════════════════════════════════════════════════
INVOICE_DELIVERY = {1: "Email", 2: "Papier", 3: "Email + Papier"}
TAX_GROUP = {1: "Federal + Provincial (9.5%)", 2: "Federal seulement", 3: "Exempté"}
def ts_to_date(ts):
if not ts or ts <= 0:
return None
try:
return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d")
except (ValueError, OSError):
return None
print("\nUpdating customers...")
updated = 0
skipped = 0
errors = 0
for a in accounts:
acct_id = a["id"]
cust_name = cust_map.get(acct_id)
if not cust_name:
skipped += 1
continue
updates = {}
if a["invoice_delivery"]:
updates["invoice_delivery_method"] = INVOICE_DELIVERY.get(a["invoice_delivery"], "")
if a["commercial"]:
updates["is_commercial"] = 1
if a["mauvais_payeur"]:
updates["is_bad_payer"] = 1
if a["frais"]:
updates["exclude_fees"] = 1
if a["tax_group"]:
updates["tax_category_legacy"] = TAX_GROUP.get(a["tax_group"], "")
if a["contact"]:
updates["contact_name_legacy"] = a["contact"]
if a["mandataire"]:
updates["mandataire"] = a["mandataire"]
if a["tel_home"]:
updates["tel_home"] = a["tel_home"]
if a["tel_office"]:
updates["tel_office"] = a["tel_office"]
if a["cell"]:
updates["cell_phone"] = a["cell"]
if a["fax"]:
updates["fax"] = a["fax"]
if a["misc"]:
updates["notes_internal"] = a["misc"]
if a["email"]:
updates["email_billing"] = a["email"]
if a["email_autre"]:
updates["email_publipostage"] = a["email_autre"]
created = ts_to_date(a["date_orig"])
if created:
updates["date_created_legacy"] = created
if not updates:
continue
# Truncate long values for Data fields (varchar 140)
for field in ["contact_name_legacy", "mandataire", "tel_home", "tel_office",
"cell_phone", "fax", "email_billing", "email_publipostage"]:
if field in updates and updates[field] and len(str(updates[field])) > 140:
updates[field] = str(updates[field])[:140]
# Build SET clause
set_parts = []
values = []
for field, val in updates.items():
set_parts.append('"{}" = %s'.format(field))
values.append(val)
values.append(cust_name)
try:
frappe.db.sql(
'UPDATE "tabCustomer" SET {} WHERE name = %s'.format(", ".join(set_parts)),
values
)
updated += 1
except Exception as e:
errors += 1
if errors <= 5:
print(" ERR {}: {}".format(cust_name, str(e)[:100]))
frappe.db.rollback()
if updated % 1000 == 0:
frappe.db.commit()
print(" Progress: {}/{}".format(updated, len(accounts)))
frappe.db.commit()
print("\nUpdated: {} customers".format(updated))
print("Skipped (no mapping): {}".format(skipped))
print("\n" + "=" * 70)
print("DONE")
print("=" * 70)