From 22377bb38105169b50b09db5b1e247595b5406b1 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Sat, 28 Mar 2026 15:45:51 -0400 Subject: [PATCH] feat: fix all data relationships + PPA reference numbers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix_issue_owners.py: 53K Issues linked to creator (owner) + 55K to assignee (_assign) - fix_issue_cust2.py: 47K Issues linked to Customer via legacy_account_id - fix_sub_address.py: 21K Subscriptions linked to service Address - customer_pos_id set to legacy PPA reference (15-digit bank number) on all 6,667 Customers - Subscription custom fields: service_address (Link→Address), service_location (Link→Service Location) - Fiscal Year 2025-2026 created (Jul 1 2025 → Jun 30 2026) Relationships now complete: Customer → Address (N) → Subscription (N) → Item (plan + speeds) Customer → Contact (N) → email/phone Customer → Issue (N) → parent_incident → child Issues Issue → owner (User who created) + _assign (User responsible) Subscription → service_address → specific installation address Customer.customer_pos_id = PPA bank reference number Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/migration/fix_issue_cust2.py | 62 ++++++++++++++++++ scripts/migration/fix_issue_owners.py | 89 ++++++++++++++++++++++++++ scripts/migration/fix_sub_address.py | 90 +++++++++++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 scripts/migration/fix_issue_cust2.py create mode 100644 scripts/migration/fix_issue_owners.py create mode 100644 scripts/migration/fix_sub_address.py diff --git a/scripts/migration/fix_issue_cust2.py b/scripts/migration/fix_issue_cust2.py new file mode 100644 index 0000000..11dc52d --- /dev/null +++ b/scripts/migration/fix_issue_cust2.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Fix Issue.customer via legacy ticket.account_id → Customer.legacy_account_id.""" +import pymysql +import psycopg2 + +LEGACY = {"host": "10.100.80.100", "user": "facturation", "password": "VD67owoj", + "database": "gestionclient", "connect_timeout": 30, "read_timeout": 300} +PG = {"host": "db", "port": 5432, "user": "postgres", "password": "123", + "dbname": "_eb65bdc0c4b1b2d6"} + +def log(msg): + print(msg, flush=True) + +def main(): + log("=== Fix Issue → Customer via legacy_account_id ===") + + # Get ticket → account_id mapping + mc = pymysql.connect(**LEGACY) + cur = mc.cursor(pymysql.cursors.DictCursor) + cur.execute("SELECT id, account_id FROM ticket WHERE account_id > 0") + ticket_acct = {r["id"]: r["account_id"] for r in cur.fetchall()} + mc.close() + log(" {} tickets with account_id".format(len(ticket_acct))) + + pg = psycopg2.connect(**PG) + pgc = pg.cursor() + + # Customer lookup + pgc.execute('SELECT legacy_account_id, name, customer_name FROM "tabCustomer" WHERE legacy_account_id > 0') + cust_map = {r[0]: (r[1], r[2]) for r in pgc.fetchall()} + + # Issues needing fix + pgc.execute("""SELECT name, legacy_ticket_id FROM "tabIssue" + WHERE legacy_ticket_id > 0 + AND (customer IS NULL OR customer = '' OR customer NOT LIKE 'CUST-%')""") + issues = pgc.fetchall() + log(" {} issues to fix".format(len(issues))) + + updated = 0 + for i, (issue_name, legacy_tid) in enumerate(issues): + acct_id = ticket_acct.get(legacy_tid) + if not acct_id: + continue + cust_data = cust_map.get(acct_id) + if not cust_data: + continue + + cust_name, cust_display = cust_data + pgc.execute('UPDATE "tabIssue" SET customer = %s, customer_name = %s WHERE name = %s', + (cust_name, cust_display, issue_name)) + updated += 1 + + if updated % 5000 == 0: + pg.commit() + log(" updated={}".format(updated)) + + pg.commit() + pg.close() + log("\nIssues linked to Customer: {}".format(updated)) + +if __name__ == "__main__": + main() diff --git a/scripts/migration/fix_issue_owners.py b/scripts/migration/fix_issue_owners.py new file mode 100644 index 0000000..84c1fd4 --- /dev/null +++ b/scripts/migration/fix_issue_owners.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +""" +Fix Issue owner + _assign from legacy ticket.open_by / assign_to → ERPNext User email. +Direct PG. Detached. +""" +import pymysql +import psycopg2 +import json + +LEGACY = {"host": "10.100.80.100", "user": "facturation", "password": "VD67owoj", + "database": "gestionclient", "connect_timeout": 30, "read_timeout": 300} +PG = {"host": "db", "port": 5432, "user": "postgres", "password": "123", + "dbname": "_eb65bdc0c4b1b2d6"} + +def log(msg): + print(msg, flush=True) + +def main(): + log("=== Fix Issue Owners + Assignees ===") + + # Staff ID → email + mc = pymysql.connect(**LEGACY) + cur = mc.cursor(pymysql.cursors.DictCursor) + cur.execute("SELECT id, email FROM staff WHERE email IS NOT NULL AND email != ''") + staff_email = {r["id"]: r["email"] for r in cur.fetchall()} + + # ticket → open_by, assign_to + cur.execute("SELECT id, open_by, assign_to FROM ticket ORDER BY id") + tickets = cur.fetchall() + mc.close() + log(" {} tickets, {} staff with email".format(len(tickets), len(staff_email))) + + pg = psycopg2.connect(**PG) + pgc = pg.cursor() + + # ERPNext users (verify they exist) + pgc.execute('SELECT name FROM "tabUser" WHERE enabled = 1') + valid_users = set(r[0] for r in pgc.fetchall()) + + # Issue legacy_ticket_id → name + pgc.execute('SELECT legacy_ticket_id, name FROM "tabIssue" WHERE legacy_ticket_id > 0') + issue_map = {r[0]: r[1] for r in pgc.fetchall()} + log(" {} issues mapped".format(len(issue_map))) + + updated_owner = 0 + updated_assign = 0 + + for i, t in enumerate(tickets): + issue_name = issue_map.get(t["id"]) + if not issue_name: + continue + + sets = [] + vals = [] + + # Owner (who created) + open_email = staff_email.get(t.get("open_by")) + if open_email and open_email in valid_users: + sets.append('owner = %s') + vals.append(open_email) + updated_owner += 1 + + # Assign (who's responsible) + assign_email = staff_email.get(t.get("assign_to")) + if assign_email and assign_email in valid_users: + assign_json = json.dumps([assign_email]) + sets.append('"_assign" = %s') + vals.append(assign_json) + updated_assign += 1 + + if sets: + vals.append(issue_name) + pgc.execute('UPDATE "tabIssue" SET {} WHERE name = %s'.format(", ".join(sets)), vals) + + if (i + 1) % 5000 == 0: + pg.commit() + log(" [{}/{}] owner={} assign={}".format(i+1, len(tickets), updated_owner, updated_assign)) + + pg.commit() + pg.close() + + log("") + log("=" * 50) + log("Owner updated: {}".format(updated_owner)) + log("Assign updated: {}".format(updated_assign)) + log("=" * 50) + +if __name__ == "__main__": + main() diff --git a/scripts/migration/fix_sub_address.py b/scripts/migration/fix_sub_address.py new file mode 100644 index 0000000..e3ce488 --- /dev/null +++ b/scripts/migration/fix_sub_address.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Link Subscription → Address via legacy service.delivery_id → delivery → Address.""" +import pymysql +import psycopg2 + +LEGACY = {"host": "10.100.80.100", "user": "facturation", "password": "VD67owoj", + "database": "gestionclient", "connect_timeout": 30, "read_timeout": 300} +PG = {"host": "db", "port": 5432, "user": "postgres", "password": "123", + "dbname": "_eb65bdc0c4b1b2d6"} + +def log(msg): + print(msg, flush=True) + +def main(): + log("=== Link Subscriptions → Addresses ===") + + # Legacy: service → delivery_id, delivery → account_id + address + mc = pymysql.connect(**LEGACY) + cur = mc.cursor(pymysql.cursors.DictCursor) + cur.execute(""" + SELECT s.id as service_id, s.delivery_id, d.account_id, d.address1, d.city + FROM service s + JOIN delivery d ON s.delivery_id = d.id + WHERE s.status = 1 + """) + svc_delivery = {r["service_id"]: r for r in cur.fetchall()} + mc.close() + log(" {} active services with delivery".format(len(svc_delivery))) + + pg = psycopg2.connect(**PG) + pgc = pg.cursor() + + # Subscription legacy_service_id → name + pgc.execute('SELECT legacy_service_id, name FROM "tabSubscription" WHERE legacy_service_id > 0') + sub_map = {r[0]: r[1] for r in pgc.fetchall()} + + # Address lookup: find address by customer + address_line1 match + # Build: (customer_name, address_line1) → address.name + pgc.execute(""" + SELECT a.name, a.address_line1, a.city, dl.link_name as customer + FROM "tabAddress" a + JOIN "tabDynamic Link" dl ON dl.parent = a.name AND dl.parenttype = 'Address' AND dl.link_doctype = 'Customer' + """) + # Group by customer + addr_by_cust = {} + for name, addr1, city, cust in pgc.fetchall(): + addr_by_cust.setdefault(cust, []).append((name, addr1 or "", city or "")) + + # Customer legacy_account_id → name + pgc.execute('SELECT legacy_account_id, name FROM "tabCustomer" WHERE legacy_account_id > 0') + cust_map = {r[0]: r[1] for r in pgc.fetchall()} + + updated = 0 + for svc_id, sub_name in sub_map.items(): + info = svc_delivery.get(svc_id) + if not info: + continue + + cust_name = cust_map.get(info["account_id"]) + if not cust_name: + continue + + # Find best matching address for this customer + addrs = addr_by_cust.get(cust_name, []) + if not addrs: + continue + + # Try exact match on address1 + legacy_addr = (info.get("address1") or "").strip().lower() + best = addrs[0][0] # default to first address + for aname, a1, acity in addrs: + if legacy_addr and legacy_addr in a1.lower(): + best = aname + break + + pgc.execute('UPDATE "tabSubscription" SET service_address = %s WHERE name = %s AND (service_address IS NULL OR service_address = %s)', + (best, sub_name, '')) + if pgc.rowcount > 0: + updated += 1 + + if updated % 2000 == 0 and updated > 0: + pg.commit() + log(" updated={}".format(updated)) + + pg.commit() + pg.close() + log("\nSubscriptions linked to Address: {}".format(updated)) + +if __name__ == "__main__": + main()