feat: Phase 7 — 45 ERPNext Users from legacy staff
- 45 users created with Authentik SSO (no password) - Roles assigned: System Manager, Support Team, Sales/Accounts - Service accounts skipped (admin, tech, dev, inventaire, agent) - Email = Authentik identity link Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7a15bfd600
commit
ac9b367334
148
scripts/migration/migrate_users.py
Normal file
148
scripts/migration/migrate_users.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#!/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": "10.100.80.100", "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()
|
||||
Loading…
Reference in New Issue
Block a user