""" Import employees from legacy staff table into ERPNext Employee doctype. Run inside erpnext-backend-1: /home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/import_employees.py Maps legacy group_ad → Department, status → Active/Inactive. Idempotent: deletes existing employees before reimporting. """ import frappe import pymysql import os import time from datetime import datetime 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) T_TOTAL = time.time() # ═══════════════════════════════════════════════════════════════ # CONFIG # ═══════════════════════════════════════════════════════════════ COMPANY = "TARGO" # Legacy group_ad → ERPNext Department DEPT_MAP = { "admin": "Management - T", "sysadmin": "Operations - T", "tech": "Operations - T", "support": "Customer Service - T", "comptabilite": "Accounts - T", "facturation": "Accounts - T", "": None, "none": None, } # Legacy group_ad → ERPNext Designation DESIG_MAP = { "admin": "Manager", "sysadmin": "Engineer", "tech": "Technician", "support": "Customer Service Representative", "comptabilite": "Accountant", "facturation": "Accountant", } # ═══════════════════════════════════════════════════════════════ # PHASE 0: Ensure prerequisite records exist # ═══════════════════════════════════════════════════════════════ print("\n" + "="*60) print("PHASE 0: PREREQUISITES") print("="*60) # Gender "Prefer not to say" if not frappe.db.exists("Gender", "Prefer not to say"): frappe.get_doc({"doctype": "Gender", "gender": "Prefer not to say"}).insert() frappe.db.commit() print("Created Gender: Prefer not to say") # Designation "Technician" — insert via SQL for desig_name in ["Technician"]: if not frappe.db.exists("Designation", desig_name): now = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") frappe.db.sql(""" INSERT INTO "tabDesignation" (name, creation, modified, modified_by, owner, docstatus, idx) VALUES (%(n)s, %(now)s, %(now)s, 'Administrator', 'Administrator', 0, 0) """, {"n": desig_name, "now": now}) frappe.db.commit() print("Created Designation:", desig_name) # ═══════════════════════════════════════════════════════════════ # PHASE 1: CLEANUP existing employees # ═══════════════════════════════════════════════════════════════ print("\n" + "="*60) print("PHASE 1: CLEANUP") print("="*60) existing = frappe.db.sql('SELECT COUNT(*) FROM "tabEmployee"')[0][0] if existing > 0: frappe.db.sql('DELETE FROM "tabEmployee"') frappe.db.commit() print("Deleted {} existing employees".format(existing)) else: print("No existing employees to delete") # Reset naming series counter frappe.db.sql(""" DELETE FROM "tabSeries" WHERE name = 'HR-EMP-' """) frappe.db.commit() # ═══════════════════════════════════════════════════════════════ # PHASE 2: FETCH legacy staff # ═══════════════════════════════════════════════════════════════ print("\n" + "="*60) print("PHASE 2: FETCH LEGACY STAFF") print("="*60) conn = pymysql.connect( host="legacy-db", user="facturation", password="*******", database="gestionclient", cursorclass=pymysql.cursors.DictCursor ) with conn.cursor() as cur: cur.execute(""" SELECT id, status, username, first_name, last_name, email, ext, cell, group_ad, date_embauche, fete, matricule_desjardins, ldap_id FROM staff ORDER BY id """) staff = cur.fetchall() conn.close() print("Fetched {} staff records".format(len(staff))) # ═══════════════════════════════════════════════════════════════ # PHASE 3: INSERT employees via bulk SQL # ═══════════════════════════════════════════════════════════════ print("\n" + "="*60) print("PHASE 3: INSERT EMPLOYEES") print("="*60) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") counter = 0 skipped = 0 for s in staff: # Skip system/bot accounts with no real name if not s["first_name"] or s["first_name"].strip() == "": skipped += 1 continue counter += 1 emp_name = "HR-EMP-{}".format(counter) first_name = (s["first_name"] or "").replace("'", "'").strip() last_name = (s["last_name"] or "").replace("'", "'").strip() full_name = "{} {}".format(first_name, last_name).strip() # Status: legacy 1 = Active, -1 = Inactive/Left status = "Active" if s["status"] == 1 else "Left" # Department group = (s["group_ad"] or "").strip().lower() dept = DEPT_MAP.get(group) # Designation desig = DESIG_MAP.get(group) # Date of joining from unix timestamp doj = None if s["date_embauche"]: try: ts = int(s["date_embauche"]) if ts > 0: doj = datetime.fromtimestamp(ts).strftime("%Y-%m-%d") except (ValueError, OSError): pass if not doj: doj = "2020-01-01" # placeholder # Date of birth from fete (DD|MM or MM|DD format) dob = None if s["fete"]: parts = s["fete"].split("|") if len(parts) == 2: try: day = int(parts[0]) month = int(parts[1]) # Format is DD|MM based on sample data (e.g. "06|05" = June 5th) # But "30|12" = 30th of December — day|month if day > 12: # day is definitely the day dob = "1990-{:02d}-{:02d}".format(month, day) elif month > 12: # month field is actually the day dob = "1990-{:02d}-{:02d}".format(day, month) else: # Ambiguous — use DD|MM interpretation dob = "1990-{:02d}-{:02d}".format(month, day) except (ValueError, IndexError): pass if not dob: dob = "1990-01-01" # placeholder # Email email = (s["email"] or "").strip() company_email = email if email.endswith("@targointernet.com") else None # Cell phone cell = (s["cell"] or "").strip() # Employee number = legacy staff ID emp_number = str(s["id"]) frappe.db.sql(""" INSERT INTO "tabEmployee" ( name, creation, modified, modified_by, owner, docstatus, idx, naming_series, first_name, last_name, employee_name, gender, date_of_birth, date_of_joining, status, company, department, designation, employee_number, cell_number, company_email, personal_email, prefered_contact_email, lft, rgt ) VALUES ( %(name)s, %(now)s, %(now)s, 'Administrator', 'Administrator', 0, 0, 'HR-EMP-', %(first_name)s, %(last_name)s, %(full_name)s, 'Prefer not to say', %(dob)s, %(doj)s, %(status)s, %(company)s, %(dept)s, %(desig)s, %(emp_number)s, %(cell)s, %(company_email)s, %(personal_email)s, %(pref_email)s, 0, 0 ) """, { "name": emp_name, "now": now_str, "first_name": first_name, "last_name": last_name, "full_name": full_name, "dob": dob, "doj": doj, "status": status, "company": COMPANY, "dept": dept, "desig": desig, "emp_number": emp_number, "cell": cell if cell else None, "company_email": company_email, "personal_email": email if email and not email.endswith("@targointernet.com") else None, "pref_email": "Company Email" if company_email else None, }) frappe.db.commit() print("Inserted {} employees ({} skipped - no name)".format(counter, skipped)) # Set the naming series counter frappe.db.sql(""" INSERT INTO "tabSeries" (name, current) VALUES ('HR-EMP-', %(counter)s) ON CONFLICT (name) DO UPDATE SET current = %(counter)s """, {"counter": counter}) frappe.db.commit() # Rebuild tree (Employee is a tree doctype with lft/rgt) try: frappe.rebuild_tree("Employee", "reports_to") print("Rebuilt employee tree") except Exception as e: print("Tree rebuild skipped:", e) # ═══════════════════════════════════════════════════════════════ # PHASE 4: VERIFY # ═══════════════════════════════════════════════════════════════ print("\n" + "="*60) print("PHASE 4: VERIFY") print("="*60) total = frappe.db.sql('SELECT COUNT(*) FROM "tabEmployee"')[0][0] active = frappe.db.sql('SELECT COUNT(*) FROM "tabEmployee" WHERE status = %s', ("Active",))[0][0] left = frappe.db.sql('SELECT COUNT(*) FROM "tabEmployee" WHERE status = %s', ("Left",))[0][0] print("Total employees: {}".format(total)) print(" Active: {}".format(active)) print(" Left: {}".format(left)) by_dept = frappe.db.sql(""" SELECT department, COUNT(*) as cnt FROM "tabEmployee" GROUP BY department ORDER BY cnt DESC """, as_dict=True) print("\nBy department:") for d in by_dept: print(" {}: {}".format(d["department"] or "(none)", d["cnt"])) # Sample sample = frappe.db.sql(""" SELECT name, employee_name, status, department, designation, employee_number, date_of_joining FROM "tabEmployee" ORDER BY name LIMIT 10 """, as_dict=True) print("\nSample:") for e in sample: print(" {} {} [{}] dept={} desig={} legacy_id={} joined={}".format( e["name"], e["employee_name"], e["status"], e["department"], e["designation"], e["employee_number"], e["date_of_joining"])) elapsed = time.time() - T_TOTAL print("\n" + "="*60) print("DONE in {:.1f}s".format(elapsed)) print("="*60)