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

149 lines
4.7 KiB
Python

#!/usr/bin/env python3
"""
Create ERPNext Users from legacy staff + link to Authentik via email.
Authentik forwardAuth sends X-authentik-email header → ERPNext matches by email.
Direct PG. Detached.
"""
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": 120}
PG = {"host": "db", "port": 5432, "user": "postgres", "password": "123",
"dbname": "_eb65bdc0c4b1b2d6"}
ADMIN = "Administrator"
# Roles for ISP staff
ROLES_ALL = ["System Manager", "Support Team", "Sales User", "Accounts User"]
ROLES_TECH = ["Support Team"]
ROLES_ADMIN = ["System Manager", "Support Team", "Sales User", "Accounts User", "Sales Manager", "Accounts Manager"]
# Staff IDs that are admin/sysadmin
ADMIN_IDS = {2, 4, 11, 12, 3293, 4661, 4664, 4671, 4749}
# Staff IDs that are service accounts (skip)
SKIP_IDS = {1, 3301, 3393, 4663, 4682}
def uid(prefix=""):
return prefix + uuid.uuid4().hex[:10]
def now():
return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S.%f")
def clean(val):
if not val: return ""
return unescape(str(val)).strip()
def log(msg):
print(msg, flush=True)
def main():
ts = now()
log("=== User Migration ===")
mc = pymysql.connect(**LEGACY)
cur = mc.cursor(pymysql.cursors.DictCursor)
cur.execute("SELECT * FROM staff WHERE status = 1 ORDER BY id")
staff = cur.fetchall()
mc.close()
log("{} active staff loaded".format(len(staff)))
pg = psycopg2.connect(**PG)
pgc = pg.cursor()
# Existing users
pgc.execute('SELECT name FROM "tabUser"')
existing = set(r[0] for r in pgc.fetchall())
created = skipped = errors = 0
for s in staff:
sid = s["id"]
if sid in SKIP_IDS:
skipped += 1
continue
email = clean(s.get("email"))
if not email or "@" not in email:
skipped += 1
continue
if email in existing or email.lower() in {e.lower() for e in existing}:
skipped += 1
continue
first = clean(s["first_name"])
last = clean(s["last_name"])
full = "{} {}".format(first, last).strip()
username = clean(s.get("username"))
cell = clean(s.get("cell"))
# Determine roles
if sid in ADMIN_IDS:
roles = ROLES_ADMIN
else:
roles = ROLES_ALL
try:
# Create User
pgc.execute("""
INSERT INTO "tabUser" (
name, creation, modified, modified_by, owner, docstatus, idx,
email, first_name, last_name, full_name, username,
mobile_no, language, time_zone, enabled,
user_type, role_profile_name,
new_password
) VALUES (
%s, %s, %s, %s, %s, 0, 0,
%s, %s, %s, %s, %s,
%s, 'fr', 'America/Toronto', 1,
'System User', NULL,
''
)
""", (email, ts, ts, ADMIN, ADMIN,
email, first, last or None, full, username,
cell or None))
# Create Role assignments
for i, role in enumerate(roles):
pgc.execute("""
INSERT INTO "tabHas Role" (
name, creation, modified, modified_by, owner, docstatus, idx,
role, parent, parentfield, parenttype
) VALUES (%s, %s, %s, %s, %s, 0, %s, %s, %s, 'roles', 'User')
""", (uid("HR-"), ts, ts, ADMIN, ADMIN, i+1, role, email))
# Create UserRole for "All"
pgc.execute("""
INSERT INTO "tabHas Role" (
name, creation, modified, modified_by, owner, docstatus, idx,
role, parent, parentfield, parenttype
) VALUES (%s, %s, %s, %s, %s, 0, %s, 'All', %s, 'roles', 'User')
""", (uid("HR-"), ts, ts, ADMIN, ADMIN, len(roles)+1, email))
created += 1
log(" OK {} ({})".format(full, email))
except Exception as e:
errors += 1
pg.rollback()
log(" ERR {} -> {}".format(email, str(e)[:80]))
continue
pg.commit()
pg.close()
log("")
log("=" * 50)
log("Users: {} created, {} skipped, {} errors".format(created, skipped, errors))
log("=" * 50)
log("")
log("Users are created with NO password (Authentik SSO handles login).")
log("Next: bench --site erp.gigafibre.ca clear-cache")
if __name__ == "__main__":
main()