เรียนรู้วิธีแก้ปัญหา Frappe Workspace ไม่ Update เมื่อรัน bench migrate ด้วย Delete+Reinsert Pattern พร้อมตัวอย่าง Workspace Manager

หนึ่งในปัญหาที่นักพัฒนา Frappe/ERPNext พบบ่อยคือ Workspace ไม่ Update เมื่อรัน bench migrate บทความนี้จะอธิบายปัญหาและวิธีแก้ไข
เมื่อเราแก้ไขไฟล์ JSON ของ Workspace และรัน bench migrate:
เหตุผลคือ Frappe ถือว่า Workspace เป็น User-customizable Data ไม่ใช่ Configuration ดังนั้น:
bench migrate ไม่ Overwrite Workspace Records ที่มีอยู่bench update ไม่ Apply JSON Changesfrappe.reload_doc() ไม่ Work สำหรับ Workspacebench migrateสร้างไฟล์ your_app/setup/workspace_manager.py:
# รัน Sync
bench --site your-site.local execute your_app.setup.workspace_manager.sync_workspaces
# จากนั้น Migrate เพื่อสร้างใหม่
bench --site your-site.local migrateใน hooks.py:
after_migrate = [
"your_app.setup.workspace_manager.sync_workspaces"
]ข้อควรระวัง: วิธีนี้จะ Sync ทุกครั้งที่ Migrate อาจไม่เหมาะสำหรับทุกกรณี
สำหรับกรณีที่ต้องการควบคุมละเอียด:
Frappe Workspace ไม่ Update อัตโนมัติเมื่อ Migrate เพราะถูกออกแบบให้ User สามารถ Customize ได้ วิธีแก้คือใช้ Delete + Reinsert Pattern:
bench migrate เพื่อสร้างใหม่จาก JSONหรือใช้ Workspace Manager Script เพื่อ Automate Process นี้
import frappe
def sync_workspaces():
"""ลบและสร้าง Workspace ใหม่เพื่อ Apply JSON Changes"""
workspaces_to_sync = [
"Your Workspace 1",
"Your Workspace 2",
]
for ws_name in workspaces_to_sync:
if frappe.db.exists("Workspace", ws_name):
frappe.delete_doc("Workspace", ws_name, force=True)
frappe.db.commit()
print(f"Deleted workspace: {ws_name}")
# Workspace จะถูกสร้างใหม่เมื่อรัน migrate
print("Run 'bench migrate' to recreate workspaces")import frappe
import json
def sync_workspace(workspace_name: str):
"""Delete และ Reinsert Workspace พร้อม Child Tables"""
# 1. ลบ Workspace และ Child Tables ที่เกี่ยวข้อง
if frappe.db.exists("Workspace", workspace_name):
frappe.db.delete("Workspace", {"name": workspace_name})
frappe.db.delete("Workspace Link", {"parent": workspace_name})
frappe.db.delete("Workspace Shortcut", {"parent": workspace_name})
frappe.db.delete("Workspace Chart", {"parent": workspace_name})
frappe.db.delete("Workspace Number Card", {"parent": workspace_name})
frappe.db.commit()
print(f"Deleted: {workspace_name}")
# 2. โหลด JSON File
workspace_json_path = frappe.get_app_path(
"your_app",
"your_module",
"workspace",
workspace_name.lower().replace(" ", "_"),
f"{workspace_name.lower().replace(' ', '_')}.json"
)
with open(workspace_json_path, 'r', encoding='utf-8') as f:
workspace_data = json.load(f)
# 3. Insert ใหม่
doc = frappe.get_doc(workspace_data)
doc.flags.ignore_permissions = True
doc.flags.ignore_links = True
doc.insert(ignore_permissions=True)
# 4. Commit และ Clear Cache
frappe.db.commit()
frappe.cache().flushall()
print(f"Recreated: {workspace_name}")
def sync_all_workspaces():
"""Sync ทุก Workspaces ของ App"""
workspaces = [
"Dashboard",
"Settings",
"Reports",
]
for ws in workspaces:
sync_workspace(ws){
"doctype": "Workspace",
"name": "Your Workspace",
"label": "Your Workspace",
"module": "Your Module",
"icon": "organization",
"public": 1,
"content": "[...]",
"links": [...],
"shortcuts": [...],
"charts": [...],
"number_cards": [...]
}