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>
175 lines
7.1 KiB
Python
175 lines
7.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Import terminated customers (status=4) with their terminate reason as Lead intelligence."""
|
|
import pymysql
|
|
import psycopg2
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
from html import unescape
|
|
|
|
LEGACY = {"host": "legacy-db", "user": "facturation", "password": "VD67owoj",
|
|
"database": "gestionclient", "connect_timeout": 30, "read_timeout": 300}
|
|
PG = {"host": "db", "port": 5432, "user": "postgres", "password": "123",
|
|
"dbname": "_eb65bdc0c4b1b2d6"}
|
|
|
|
ADMIN = "Administrator"
|
|
GROUP_MAP = {1: "Individual", 4: "Commercial", 5: "Individual", 6: "Individual",
|
|
7: "Individual", 8: "Commercial", 9: "Government", 10: "Non Profit"}
|
|
|
|
def uid(p=""):
|
|
return p + uuid.uuid4().hex[:10]
|
|
|
|
def now():
|
|
return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S.%f")
|
|
|
|
def clean(v):
|
|
if not v: return ""
|
|
return unescape(str(v)).strip()
|
|
|
|
def log(msg):
|
|
print(msg, flush=True)
|
|
|
|
def main():
|
|
ts = now()
|
|
log("=== Import Terminated Customers (status 3,4,5) ===")
|
|
|
|
mc = pymysql.connect(**LEGACY)
|
|
cur = mc.cursor(pymysql.cursors.DictCursor)
|
|
cur.execute("SELECT * FROM account WHERE status IN (3,4,5) ORDER BY id")
|
|
accounts = cur.fetchall()
|
|
cur.execute("SELECT * FROM delivery WHERE account_id IN (SELECT id FROM account WHERE status IN (3,4,5)) ORDER BY account_id")
|
|
deliveries = cur.fetchall()
|
|
mc.close()
|
|
log(" {} terminated accounts, {} deliveries".format(len(accounts), len(deliveries)))
|
|
|
|
del_by = {}
|
|
for d in deliveries:
|
|
del_by.setdefault(d["account_id"], []).append(d)
|
|
|
|
pg = psycopg2.connect(**PG)
|
|
pgc = pg.cursor()
|
|
|
|
pgc.execute('SELECT legacy_account_id FROM "tabCustomer" WHERE legacy_account_id > 0')
|
|
existing = set(r[0] for r in pgc.fetchall())
|
|
|
|
c_ok = c_addr = c_skip = c_err = 0
|
|
|
|
for i, a in enumerate(accounts):
|
|
aid = a["id"]
|
|
if aid in existing:
|
|
c_skip += 1
|
|
continue
|
|
|
|
first = clean(a["first_name"])
|
|
last = clean(a["last_name"])
|
|
company = clean(a["company"])
|
|
if company:
|
|
ctype, cname = "Company", company
|
|
else:
|
|
ctype, cname = "Individual", "{} {}".format(first, last).strip() or "Client-{}".format(aid)
|
|
|
|
cust_id = uid("CUST-")
|
|
group = GROUP_MAP.get(a["group_id"], "Individual")
|
|
|
|
# Build customer_details with terminate info
|
|
details = ""
|
|
if a.get("terminate_reason"):
|
|
details += "Raison départ: {}\n".format(clean(a["terminate_reason"]))
|
|
if a.get("terminate_cie"):
|
|
details += "Parti chez: {}\n".format(clean(a["terminate_cie"]))
|
|
if a.get("terminate_note"):
|
|
details += "Notes: {}\n".format(clean(a["terminate_note"])[:500])
|
|
if a.get("terminate_date"):
|
|
details += "Date: {}\n".format(clean(a["terminate_date"]))
|
|
|
|
try:
|
|
pgc.execute("""
|
|
INSERT INTO "tabCustomer" (
|
|
name, creation, modified, modified_by, owner, docstatus, idx,
|
|
naming_series, customer_name, customer_type, customer_group,
|
|
territory, default_currency, language, disabled,
|
|
legacy_account_id, legacy_customer_id, ppa_enabled, stripe_id,
|
|
customer_pos_id, customer_details
|
|
) VALUES (
|
|
%s, %s, %s, %s, %s, 0, 0,
|
|
'CUST-.YYYY.-', %s, %s, %s,
|
|
'Canada', 'CAD', 'fr', 1,
|
|
%s, %s, 0, %s,
|
|
%s, %s
|
|
)
|
|
""", (cust_id, ts, ts, ADMIN, ADMIN,
|
|
cname, ctype, group,
|
|
aid, clean(a.get("customer_id")),
|
|
clean(a.get("stripe_id")) or None,
|
|
clean(a.get("customer_id")),
|
|
details or None))
|
|
c_ok += 1
|
|
|
|
# Contact
|
|
email = clean(a.get("email"))
|
|
if first or email:
|
|
cont_id = uid("CONT-")
|
|
full = "{} {}".format(first, last).strip()
|
|
pgc.execute("""
|
|
INSERT INTO "tabContact" (
|
|
name, creation, modified, modified_by, owner, docstatus, idx,
|
|
first_name, last_name, full_name, email_id, phone, mobile_no, status
|
|
) VALUES (%s, %s, %s, %s, %s, 0, 0, %s, %s, %s, %s, %s, %s, 'Open')
|
|
""", (cont_id, ts, ts, ADMIN, ADMIN,
|
|
first or cname, last or None, full or cname,
|
|
email or None, clean(a.get("tel_home")) or None,
|
|
clean(a.get("cell")) or None))
|
|
pgc.execute("""
|
|
INSERT INTO "tabDynamic Link" (
|
|
name, creation, modified, modified_by, owner, docstatus, idx,
|
|
link_doctype, link_name, link_title, parent, parentfield, parenttype
|
|
) VALUES (%s, %s, %s, %s, %s, 0, 1, 'Customer', %s, %s, %s, 'links', 'Contact')
|
|
""", (uid("DL-"), ts, ts, ADMIN, ADMIN, cust_id, cname, cont_id))
|
|
|
|
# Addresses
|
|
for j, d in enumerate(del_by.get(aid, [])):
|
|
addr1 = clean(d.get("address1"))
|
|
city = clean(d.get("city"))
|
|
if not addr1 and not city: continue
|
|
addr_id = uid("ADDR-")
|
|
pgc.execute("""
|
|
INSERT INTO "tabAddress" (
|
|
name, creation, modified, modified_by, owner, docstatus, idx,
|
|
address_title, address_type, address_line1, city, state,
|
|
pincode, country, is_primary_address, is_shipping_address
|
|
) VALUES (%s, %s, %s, %s, %s, 0, 0,
|
|
%s, 'Shipping', %s, %s, %s, %s, 'Canada', %s, 1)
|
|
""", (addr_id, ts, ts, ADMIN, ADMIN,
|
|
clean(d.get("name")) or cname, addr1 or "N/A", city or "N/A",
|
|
clean(d.get("state")) or "QC", clean(d.get("zip")),
|
|
1 if j == 0 else 0))
|
|
pgc.execute("""
|
|
INSERT INTO "tabDynamic Link" (
|
|
name, creation, modified, modified_by, owner, docstatus, idx,
|
|
link_doctype, link_name, link_title, parent, parentfield, parenttype
|
|
) VALUES (%s, %s, %s, %s, %s, 0, %s, 'Customer', %s, %s, %s, 'links', 'Address')
|
|
""", (uid("DL-"), ts, ts, ADMIN, ADMIN, j+1, cust_id, cname, addr_id))
|
|
c_addr += 1
|
|
|
|
except Exception as e:
|
|
c_err += 1
|
|
pg.rollback()
|
|
if c_err <= 10:
|
|
log(" ERR #{} {} -> {}".format(aid, cname[:30], str(e)[:80]))
|
|
continue
|
|
|
|
if c_ok % 500 == 0:
|
|
pg.commit()
|
|
log(" [{}/{}] created={} addr={} skip={} err={}".format(i+1, len(accounts), c_ok, c_addr, c_skip, c_err))
|
|
|
|
pg.commit()
|
|
pg.close()
|
|
log("")
|
|
log("=" * 60)
|
|
log("Terminated Customers: {} created, {} skipped, {} errors".format(c_ok, c_skip, c_err))
|
|
log("Addresses: {}".format(c_addr))
|
|
log("All terminated customers have disabled=1 + terminate details in customer_details")
|
|
log("=" * 60)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|