- InlineField component + useInlineEdit composable for Odoo-style dblclick editing - Client search by name, account ID, and legacy_customer_id (or_filters) - SMS/Email notification panel on ContactCard via n8n webhooks - Ticket reply thread via Communication docs - All migration scripts (51 files) now tracked - Client portal and field tech app added to monorepo - README rewritten with full feature list, migration summary, architecture - CHANGELOG updated with all recent work - ROADMAP updated with current completion status - Removed hardcoded tokens from docs (use $ERP_SERVICE_TOKEN) - .gitignore updated (docker/, .claude/, exports/, .quasar/) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
138 lines
4.9 KiB
Python
138 lines
4.9 KiB
Python
"""
|
|
Add office_extension custom field to Employee and populate from legacy staff.ext.
|
|
|
|
Run inside erpnext-backend-1:
|
|
/home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/add_office_extension.py
|
|
"""
|
|
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)
|
|
|
|
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# PHASE 1: Add office_extension custom field
|
|
# ═══════════════════════════════════════════════════════════════
|
|
print("\n" + "="*60)
|
|
print("PHASE 1: ADD CUSTOM FIELD")
|
|
print("="*60)
|
|
|
|
existing = frappe.db.sql("""
|
|
SELECT fieldname FROM "tabCustom Field"
|
|
WHERE dt = 'Employee' AND fieldname = 'office_extension'
|
|
""")
|
|
|
|
if not existing:
|
|
# Get max idx for contact_details tab fields
|
|
max_idx = frappe.db.sql("""
|
|
SELECT COALESCE(MAX(idx), 0) FROM "tabCustom Field"
|
|
WHERE dt = 'Employee'
|
|
""")[0][0]
|
|
|
|
cf_name = "Employee-office_extension"
|
|
frappe.db.sql("""
|
|
INSERT INTO "tabCustom Field" (
|
|
name, creation, modified, modified_by, owner, docstatus, idx,
|
|
dt, fieldname, label, fieldtype, insert_after, reqd, read_only, hidden
|
|
) VALUES (
|
|
%(name)s, %(now)s, %(now)s, 'Administrator', 'Administrator', 0, %(idx)s,
|
|
'Employee', 'office_extension', 'Office Extension', 'Data',
|
|
'cell_number', 0, 0, 0
|
|
)
|
|
""", {"name": cf_name, "now": now_str, "idx": max_idx + 1})
|
|
|
|
# Add column to table
|
|
try:
|
|
frappe.db.sql("""
|
|
ALTER TABLE "tabEmployee" ADD COLUMN office_extension varchar(20)
|
|
""")
|
|
except Exception as e:
|
|
if "already exists" in str(e).lower() or "duplicate" in str(e).lower():
|
|
print("Column 'office_extension' already exists")
|
|
else:
|
|
raise
|
|
|
|
frappe.db.commit()
|
|
print("Added 'office_extension' custom field to Employee")
|
|
else:
|
|
print("'office_extension' field already exists")
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# PHASE 2: Populate from legacy staff.ext
|
|
# ═══════════════════════════════════════════════════════════════
|
|
print("\n" + "="*60)
|
|
print("PHASE 2: POPULATE FROM LEGACY")
|
|
print("="*60)
|
|
|
|
conn = pymysql.connect(
|
|
host="10.100.80.100",
|
|
user="facturation",
|
|
password="*******",
|
|
database="gestionclient",
|
|
cursorclass=pymysql.cursors.DictCursor
|
|
)
|
|
|
|
with conn.cursor() as cur:
|
|
cur.execute("""
|
|
SELECT id, ext, email FROM staff
|
|
WHERE ext IS NOT NULL AND ext != '' AND ext != '0'
|
|
""")
|
|
staff_ext = cur.fetchall()
|
|
|
|
conn.close()
|
|
print("Staff with extensions: {}".format(len(staff_ext)))
|
|
|
|
# Map legacy staff email → employee
|
|
emp_map = {}
|
|
rows = frappe.db.sql("""
|
|
SELECT name, company_email, employee_number FROM "tabEmployee"
|
|
WHERE company_email IS NOT NULL
|
|
""", as_dict=True)
|
|
for r in rows:
|
|
if r["company_email"]:
|
|
emp_map[r["company_email"].strip().lower()] = r["name"]
|
|
|
|
updated = 0
|
|
for s in staff_ext:
|
|
email = (s["email"] or "").strip().lower()
|
|
emp_name = emp_map.get(email)
|
|
if emp_name:
|
|
frappe.db.sql("""
|
|
UPDATE "tabEmployee" SET office_extension = %s WHERE name = %s
|
|
""", (str(s["ext"]).strip(), emp_name))
|
|
updated += 1
|
|
|
|
frappe.db.commit()
|
|
print("Updated {} employees with office extension".format(updated))
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# VERIFY
|
|
# ═══════════════════════════════════════════════════════════════
|
|
print("\n" + "="*60)
|
|
print("VERIFY")
|
|
print("="*60)
|
|
|
|
with_ext = frappe.db.sql("""
|
|
SELECT name, employee_name, office_extension, cell_number, company_email
|
|
FROM "tabEmployee"
|
|
WHERE office_extension IS NOT NULL AND office_extension != ''
|
|
ORDER BY name
|
|
""", as_dict=True)
|
|
|
|
print("Employees with extension: {}".format(len(with_ext)))
|
|
for e in with_ext:
|
|
print(" {} → ext={} phone={} email={}".format(
|
|
e["employee_name"], e["office_extension"],
|
|
e["cell_number"] or "-", e["company_email"] or "-"))
|
|
|
|
frappe.clear_cache()
|
|
print("\nDone — cache cleared")
|