""" Import VoIP lines from legacy: pbx + phone_addr (911) + phone_provisioning (ATA). Creates VoIP Line records linked to Customer + Service Location. Run inside erpnext-backend-1: /home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/import_voip_lines.py """ import frappe import pymysql import os, sys, time sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 1) 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) legacy = pymysql.connect( host="legacy-db", user="facturation", password="VD67owoj", database="gestionclient", cursorclass=pymysql.cursors.DictCursor ) # Build mappings cust_map = {} for r in frappe.db.sql('SELECT name, legacy_account_id FROM "tabCustomer" WHERE legacy_account_id > 0', as_dict=True): cust_map[int(r['legacy_account_id'])] = r['name'] print(f" {len(cust_map)} customers mapped") loc_map = {} for r in frappe.db.sql('SELECT name, legacy_delivery_id FROM "tabService Location" WHERE legacy_delivery_id > 0', as_dict=True): loc_map[int(r['legacy_delivery_id'])] = r['name'] print(f" {len(loc_map)} locations mapped") sub_map = {} for r in frappe.db.sql('SELECT name, legacy_service_id FROM "tabService Subscription" WHERE legacy_service_id > 0', as_dict=True): sub_map[int(r['legacy_service_id'])] = r['name'] print(f" {len(sub_map)} subscriptions mapped") # ── Load all 3 legacy tables ── print("\n=== Loading legacy VoIP data ===") with legacy.cursor() as cur: # PBX lines cur.execute("SELECT * FROM pbx ORDER BY id") pbx_rows = cur.fetchall() print(f" {len(pbx_rows)} PBX lines") # 911 addresses indexed by (account_id, phone) cur.execute("SELECT * FROM phone_addr") e911_rows = cur.fetchall() e911_map = {} for r in e911_rows: key = (r['account_id'], r['phone']) e911_map[key] = r print(f" {len(e911_rows)} 911 addresses ({len(e911_map)} unique)") # ATA provisioning indexed by (account_id, phone) cur.execute("SELECT * FROM phone_provisioning") prov_rows = cur.fetchall() prov_map = {} for r in prov_rows: key = (r['account_id'], r['phone']) prov_map[key] = r print(f" {len(prov_rows)} ATA provisioning records") legacy.close() # Clear existing existing = frappe.db.sql('SELECT COUNT(*) FROM "tabVoIP Line"')[0][0] if existing: frappe.db.sql('DELETE FROM "tabVoIP Line"') frappe.db.commit() print(f" Cleared {existing} existing VoIP Lines") # ── Import PBX lines with matched 911 + ATA ── print("\n=== Importing VoIP Lines ===") T0 = time.time() created = skipped = 0 with_911 = without_911 = 0 for pbx in pbx_rows: cust = cust_map.get(pbx['account_id']) if not cust: skipped += 1 continue loc = loc_map.get(pbx['delivery_id'], '') sub = sub_map.get(pbx['service_id'], '') phone = pbx['phone'] # Match 911 address e911 = e911_map.get((pbx['account_id'], phone), {}) if e911: with_911 += 1 else: without_911 += 1 # Match ATA provisioning prov = prov_map.get((pbx['account_id'], phone), {}) # Format DID as xxx-xxx-xxxx if 10 digits did = phone if len(did) == 10: did = f"{did[:3]}-{did[3:6]}-{did[6:]}" doc = frappe.get_doc({ "doctype": "VoIP Line", "did": did, "customer": cust, "service_location": loc, "subscription": sub, "status": "Active", # SIP config "caller_id": (pbx.get('name') or '').strip(), "sip_password": pbx.get('password') or '', "extension": pbx.get('int_code') or '', "sip_context": pbx.get('user_context') or '', "max_calls": pbx.get('max_calls') or 2, "call_timeout": pbx.get('call_timeout') or 30, "language": pbx.get('language') or 'fr', "country_whitelist": pbx.get('country_whitelist') or '', # Voicemail "voicemail_enabled": 1 if pbx.get('has_vm') else 0, "vm_password": pbx.get('vm_password') or '', "vm_email": pbx.get('vm_email') or '', "vm_keep_msg": 1 if pbx.get('keep_msg') else 0, "vm_disk_quota": pbx.get('vm_disk_quota') or 180, # 911 address "e911_first_name": e911.get('first_name', ''), "e911_last_name": e911.get('last_name', ''), "e911_street_number": e911.get('street_number', ''), "e911_apt": e911.get('apt', ''), "e911_street_name": e911.get('street_name', ''), "e911_city": e911.get('city', ''), "e911_state": e911.get('state', ''), "e911_zip": e911.get('zip', ''), "e911_cauca_code": e911.get('code_cauca', ''), "e911_class": e911.get('class_service', 'RES'), "e911_enhanced": 1 if e911.get('enhanced_capable') == 'Y' else 0, "e911_synced": 1 if e911 else 0, "e911_info": e911.get('info', ''), # ATA "ata_model": prov.get('app', ''), "ata_mac": prov.get('mac', ''), "ata_password": prov.get('password', ''), "international_enabled": 1 if prov.get('internationnal') else 0, # Legacy refs "legacy_pbx_id": pbx['id'], "legacy_account_id": pbx['account_id'], "legacy_delivery_id": pbx.get('delivery_id') or 0, "legacy_service_id": pbx.get('service_id') or 0, }) try: doc.insert(ignore_if_duplicate=True) created += 1 except Exception as e: print(f" ERR PBX {pbx['id']} DID {did}: {str(e)[:80]}") frappe.db.rollback() skipped += 1 if created % 100 == 0: frappe.db.commit() print(f" [{created}/{len(pbx_rows)}] [{time.time()-T0:.0f}s]") frappe.db.commit() # ── Import orphan 911 addresses (no PBX line) ── print("\n=== Importing orphan 911 addresses ===") orphan_count = 0 pbx_phones = {(r['account_id'], r['phone']) for r in pbx_rows} for key, e911 in e911_map.items(): if key in pbx_phones: continue # already imported with PBX line cust = cust_map.get(e911['account_id']) if not cust: continue phone = e911['phone'] did = phone if len(did) == 10: did = f"{did[:3]}-{did[3:6]}-{did[6:]}" doc = frappe.get_doc({ "doctype": "VoIP Line", "did": did, "customer": cust, "status": "Inactive", "caller_id": f"{e911.get('first_name', '')} {e911.get('last_name', '')}".strip(), # 911 only "e911_first_name": e911.get('first_name', ''), "e911_last_name": e911.get('last_name', ''), "e911_street_number": e911.get('street_number', ''), "e911_apt": e911.get('apt', ''), "e911_street_name": e911.get('street_name', ''), "e911_city": e911.get('city', ''), "e911_state": e911.get('state', ''), "e911_zip": e911.get('zip', ''), "e911_cauca_code": e911.get('code_cauca', ''), "e911_class": e911.get('class_service', 'RES'), "e911_enhanced": 1 if e911.get('enhanced_capable') == 'Y' else 0, "e911_synced": 1, # was synced when line was active "e911_info": e911.get('info', ''), "legacy_account_id": e911['account_id'], }) try: doc.insert(ignore_if_duplicate=True) orphan_count += 1 except Exception as e: if "Duplicate" not in str(e): print(f" WARN: DID {did} — {str(e)[:60]}") frappe.db.commit() print(f" {orphan_count} orphan 911 lines created") # ── Verify ── print(f"\n=== Summary ===") total = frappe.db.sql('SELECT COUNT(*) FROM "tabVoIP Line"')[0][0] dist = frappe.db.sql('SELECT status, COUNT(*) FROM "tabVoIP Line" GROUP BY status') for r in dist: print(f" {r[0]:15s} {r[1]}") print(f" With 911 address: {with_911}") print(f" Without 911: {without_911}") print(f" Orphan 911 (inactive): {orphan_count}") print(f" Total: {total} VoIP Lines") print(f" Skipped (no customer): {skipped}") print(f" Time: {time.time()-T0:.0f}s") frappe.destroy() print("Done!")