วิธีสร้าง ERPNext Custom App ฉบับสมบูรณ์ 2026
เรียนรู้วิธีสร้าง ERPNext Custom App ตั้งแต่เริ่มต้น พร้อมตัวอย่างโค้ด Custom Doctype, Hooks และ Fixtures สำหรับ Frappe Framework

การ Customize ERPNext ให้ตรงกับความต้องการของธุรกิจเป็นสิ่งที่หลายองค์กรต้องการ และวิธีที่ดีที่สุดคือการสร้าง ERPNext Custom App ของคุณเอง บทความนี้จะสอนวิธีสร้าง Custom App ตั้งแต่เริ่มต้นจนใช้งานได้จริง พร้อมตัวอย่างโค้ดที่นำไปใช้ได้ทันที
ปัญหา (Problem)
หลายธุรกิจที่ใช้ ERPNext มักพบปัญหาเหล่านี้:
- ฟีเจอร์มาตรฐานไม่ตรงความต้องการ - ERPNext มีฟีเจอร์มากมาย แต่บางครั้งก็ไม่ครอบคลุมทุก workflow ของธุรกิจ
- การแก้ไข Core Code โดยตรงเป็นอันตราย - เมื่ออัพเดท ERPNext เวอร์ชันใหม่ การแก้ไขจะหายไป
- ต้องการเพิ่ม Custom Doctype - เช่น ระบบจัดการลูกค้าเฉพาะทาง หรือ Module พิเศษ
- ต้องการ Override พฤติกรรมของ Doctype เดิม - เช่น เพิ่ม validation หรือ automation
วิธีแก้ไขที่ถูกต้อง คือการสร้าง Custom App ที่แยกออกจาก Core ERPNext ทำให้:
- อัพเดท ERPNext ได้โดยไม่กระทบ customization
- จัดการ version control ได้ง่าย
- ย้ายไปใช้กับ site อื่นได้สะดวก
ภาพรวม Custom App Architecture
graph TB
subgraph "Frappe Bench"
FB[frappe-bench]
FB --> APPS[apps/]
FB --> SITES[sites/]
end
subgraph "Applications"
APPS --> FRAPPE[frappe]
APPS --> ERPNEXT[erpnext]
APPS --> CUSTOM[my_custom_app]
end
subgraph "Custom App Structure"
CUSTOM --> HOOKS[hooks.py]
CUSTOM --> MODULES[modules/]
CUSTOM --> FIXTURES[fixtures/]
MODULES --> DOCTYPE[doctype/]
MODULES --> EVENTS[events/]
end
subgraph "Site"
SITES --> SITE1[your-site.local]
SITE1 --> DB[(Database)]
end
HOOKS -->|doc_events| ERPNEXT
DOCTYPE -->|creates tables| DB
FIXTURES -->|exports to| CUSTOM
style CUSTOM fill:#3498db,color:#fff
style HOOKS fill:#e74c3c,color:#fff
style DOCTYPE fill:#2ecc71,color:#fff
วิธีแก้ไข (Solution)
ขั้นตอนที่ 1: สร้าง Custom App ด้วย Bench CLI
เปิด Terminal แล้วไปที่ directory ของ Frappe Bench:
cd ~/frappe-bench
สร้าง Custom App ใหม่ด้วยคำสั่ง:
bench new-app my_custom_app
ระบบจะถามข้อมูลเบื้องต้น:
App Title (default: My Custom App): My Custom App
App Description: Custom ERPNext modules for my business
App Publisher: Your Company Name
App Email: dev@yourcompany.com
App Icon (default: octicon octicon-file-directory):
App Color (default: grey): #3498db
App License (default: MIT): MIT
โครงสร้างไฟล์ที่ได้:
apps/my_custom_app/
├── my_custom_app/
│ ├── __init__.py
│ ├── hooks.py # สำคัญ! กำหนดพฤติกรรมของ App
│ ├── modules.txt # รายชื่อ modules
│ ├── patches.txt # database patches
│ ├── templates/
│ └── my_custom_app/ # module หลัก
│ ├── __init__.py
│ └── doctype/
├── setup.py
├── requirements.txt
└── README.md
ขั้นตอนที่ 2: ติดตั้ง App บน Site
bench --site your-site.local install-app my_custom_app
ตรวจสอบว่าติดตั้งสำเร็จ:
bench --site your-site.local list-apps
ผลลัพธ์ควรแสดง:
frappe
erpnext
my_custom_app
ขั้นตอนที่ 3: สร้าง Custom Doctype
Custom Doctype คือหัวใจของ Custom App สมมติเราต้องการสร้างระบบจัดการ "Project Task" แบบกำหนดเอง
3.1 สร้าง Doctype ผ่าน UI
-
ไปที่ Settings > DocType > New
-
กรอกข้อมูล:
- Name: Custom Project Task
- Module: My Custom App
- Is Submittable: ✓ (ถ้าต้องการ workflow Submit/Cancel)
-
เพิ่ม Fields:
| Label | Fieldname | Type | Options | |-------|-----------|------|---------| | Task Name | task_name | Data | - | | Description | description | Text Editor | - | | Status | status | Select | Open\nIn Progress\nCompleted\nCancelled | | Priority | priority | Select | Low\nMedium\nHigh\nCritical | | Assigned To | assigned_to | Link | User | | Due Date | due_date | Date | - | | Project | project | Link | Project |
- กด Save
3.2 สร้าง Doctype ผ่าน Code (แนะนำ)
สร้างไฟล์ custom_project_task.json ใน:
my_custom_app/my_custom_app/doctype/custom_project_task/
{
"name": "Custom Project Task",
"module": "My Custom App",
"doctype": "DocType",
"engine": "InnoDB",
"is_submittable": 1,
"naming_rule": "By fieldname",
"autoname": "field:task_name",
"fields": [
{
"fieldname": "task_name",
"fieldtype": "Data",
"label": "Task Name",
"reqd": 1,
"unique": 1
},
{
"fieldname": "description",
"fieldtype": "Text Editor",
"label": "Description"
},
{
"fieldname": "column_break_1",
"fieldtype": "Column Break"
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Open\nIn Progress\nCompleted\nCancelled",
"default": "Open"
},
{
"fieldname": "priority",
"fieldtype": "Select",
"label": "Priority",
"options": "Low\nMedium\nHigh\nCritical",
"default": "Medium"
},
{
"fieldname": "assigned_to",
"fieldtype": "Link",
"label": "Assigned To",
"options": "User"
},
{
"fieldname": "due_date",
"fieldtype": "Date",
"label": "Due Date"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
}
],
"permissions": [
{
"role": "System Manager",
"read": 1,
"write": 1,
"create": 1,
"delete": 1,
"submit": 1,
"cancel": 1
}
]
}
ขั้นตอนที่ 4: เพิ่ม Business Logic ด้วย Controller
สร้างไฟล์ custom_project_task.py:
# my_custom_app/my_custom_app/doctype/custom_project_task/custom_project_task.py
import frappe
from frappe.model.document import Document
from frappe.utils import getdate, nowdate
class CustomProjectTask(Document):
def validate(self):
"""ตรวจสอบข้อมูลก่อนบันทึก"""
self.validate_due_date()
self.set_priority_color()
def validate_due_date(self):
"""ตรวจสอบว่า Due Date ไม่ใช่วันที่ผ่านมาแล้ว"""
if self.due_date and getdate(self.due_date) < getdate(nowdate()):
frappe.throw("Due Date ต้องไม่ใช่วันที่ผ่านมาแล้ว")
def set_priority_color(self):
"""กำหนดสีตาม Priority"""
color_map = {
"Low": "green",
"Medium": "blue",
"High": "orange",
"Critical": "red"
}
self.priority_color = color_map.get(self.priority, "grey")
def on_submit(self):
"""เมื่อ Submit document"""
self.notify_assigned_user()
def notify_assigned_user(self):
"""ส่ง notification ไปยังผู้รับผิดชอบ"""
if self.assigned_to:
frappe.publish_realtime(
event="new_task_assigned",
message={
"task": self.name,
"task_name": self.task_name,
"assigned_by": frappe.session.user
},
user=self.assigned_to
)
ขั้นตอนที่ 5: ใช้ Hooks เพื่อ Override พฤติกรรม
ไฟล์ hooks.py เป็นหัวใจสำคัญของ Custom App ใช้กำหนดพฤติกรรมต่างๆ
Document Lifecycle Events
flowchart LR
subgraph "Document Events"
A[New] -->|before_insert| B[Insert]
B -->|after_insert| C[Draft]
C -->|before_save| D[Save]
D -->|validate| D
D -->|on_update| E[Saved]
E -->|before_submit| F[Submit]
F -->|on_submit| G[Submitted]
G -->|before_cancel| H[Cancel]
H -->|on_cancel| I[Cancelled]
end
style A fill:#3498db,color:#fff
style G fill:#2ecc71,color:#fff
style I fill:#e74c3c,color:#fff
คุณสามารถ hook เข้าไปที่ event ใดก็ได้เพื่อเพิ่ม business logic:
# my_custom_app/hooks.py
app_name = "my_custom_app"
app_title = "My Custom App"
app_publisher = "Your Company"
app_description = "Custom ERPNext modules"
app_version = "1.0.0"
# Document Events - Override พฤติกรรมของ Doctype อื่น
doc_events = {
"Sales Invoice": {
"on_submit": "my_custom_app.events.sales_invoice.on_submit",
"on_cancel": "my_custom_app.events.sales_invoice.on_cancel"
},
"Customer": {
"after_insert": "my_custom_app.events.customer.after_insert"
}
}
# Scheduled Tasks - งานที่รันอัตโนมัติ
scheduler_events = {
"daily": [
"my_custom_app.tasks.daily.check_overdue_tasks"
],
"hourly": [
"my_custom_app.tasks.hourly.sync_external_data"
]
}
# Override Whitelisted Methods
override_whitelisted_methods = {
"frappe.client.get_count": "my_custom_app.overrides.custom_get_count"
}
# Fixtures - ข้อมูลที่ต้อง export ไปกับ App
fixtures = [
{"dt": "Custom Field", "filters": [["module", "=", "My Custom App"]]},
{"dt": "Property Setter", "filters": [["module", "=", "My Custom App"]]},
{"dt": "Role", "filters": [["name", "in", ["Custom Task Manager"]]]},
]
# Website Route Rules
website_route_rules = [
{"from_route": "/tasks/<task>", "to_route": "task_detail"},
]
ตัวอย่าง Event Handler
สร้างไฟล์ my_custom_app/events/sales_invoice.py:
import frappe
def on_submit(doc, method):
"""ทำงานเมื่อ Sales Invoice ถูก Submit"""
# เพิ่ม loyalty points ให้ลูกค้า
add_loyalty_points(doc)
# ส่ง notification
send_invoice_notification(doc)
def add_loyalty_points(doc):
"""คำนวณและเพิ่ม loyalty points"""
points = doc.grand_total // 100 # 1 point ต่อ 100 บาท
if points > 0:
frappe.get_doc({
"doctype": "Loyalty Point Entry",
"customer": doc.customer,
"points": points,
"invoice": doc.name
}).insert(ignore_permissions=True)
def send_invoice_notification(doc):
"""ส่ง email แจ้งเตือนใบแจ้งหนี้"""
frappe.sendmail(
recipients=[doc.contact_email],
subject=f"ใบแจ้งหนี้ {doc.name}",
template="invoice_notification",
args={"doc": doc}
)
def on_cancel(doc, method):
"""ทำงานเมื่อ Sales Invoice ถูก Cancel"""
# ยกเลิก loyalty points
cancel_loyalty_points(doc)
ขั้นตอนที่ 6: สร้าง Custom Script (Client-side)
สำหรับ logic ที่ทำงานบน browser สร้างไฟล์ .js:
// my_custom_app/public/js/custom_project_task.js
frappe.ui.form.on('Custom Project Task', {
refresh: function(frm) {
// เพิ่มปุ่ม custom
if (frm.doc.status === 'Open') {
frm.add_custom_button(__('Start Task'), function() {
frm.set_value('status', 'In Progress');
frm.save();
}, __('Actions'));
}
if (frm.doc.status === 'In Progress') {
frm.add_custom_button(__('Complete Task'), function() {
frm.set_value('status', 'Completed');
frm.save();
}, __('Actions'));
}
},
priority: function(frm) {
// เปลี่ยนสี indicator ตาม priority
const colors = {
'Critical': 'red',
'High': 'orange',
'Medium': 'blue',
'Low': 'green'
};
frm.set_indicator_formatter('priority', (doc) => colors[doc.priority]);
},
due_date: function(frm) {
// แจ้งเตือนถ้า due date ใกล้จะถึง
if (frm.doc.due_date) {
const due = frappe.datetime.str_to_obj(frm.doc.due_date);
const today = frappe.datetime.str_to_obj(frappe.datetime.nowdate());
const diff = frappe.datetime.get_diff(due, today);
if (diff <= 3 && diff >= 0) {
frappe.show_alert({
message: __('Due date is approaching! Only {0} days left.', [diff]),
indicator: 'orange'
});
}
}
}
});
ลงทะเบียน script ใน hooks.py:
# เพิ่มใน hooks.py
doctype_js = {
"Custom Project Task": "public/js/custom_project_task.js"
}
ขั้นตอนที่ 7: ใช้ Fixtures เพื่อ Export ข้อมูล
Fixtures ช่วยให้คุณ export Custom Fields, Property Setters และข้อมูลอื่นๆ ไปกับ App:
# hooks.py
fixtures = [
# Export Custom Fields ทั้งหมดของ Module นี้
{
"dt": "Custom Field",
"filters": [["module", "=", "My Custom App"]]
},
# Export Property Setters (การแก้ไข properties ของ field เดิม)
{
"dt": "Property Setter",
"filters": [["module", "=", "My Custom App"]]
},
# Export Roles ที่สร้างขึ้น
{
"dt": "Role",
"filters": [["name", "in", ["Custom Task Manager", "Task User"]]]
},
# Export Workflow
{
"dt": "Workflow",
"filters": [["document_type", "=", "Custom Project Task"]]
},
# Export Print Format
{
"dt": "Print Format",
"filters": [["doc_type", "=", "Custom Project Task"]]
}
]
Export fixtures:
bench --site your-site.local export-fixtures --app my_custom_app
ไฟล์จะถูกสร้างที่ my_custom_app/my_custom_app/fixtures/
ขั้นตอนที่ 8: Deploy บน Production
Deployment Flow
flowchart LR
subgraph "Development"
DEV[Local Dev] -->|bench new-app| APP[Custom App]
APP -->|git push| GIT[GitHub]
end
subgraph "Production"
GIT -->|bench get-app| PROD[Production Server]
PROD -->|install-app| SITE[Site]
SITE -->|migrate| DB[(Database)]
end
subgraph "Docker"
GIT -->|apps.json| DOCKER[Docker Build]
DOCKER -->|deploy| CONTAINER[Container]
end
subgraph "Frappe Cloud"
GIT -->|Add App| CLOUD[Frappe Cloud]
CLOUD -->|install| CLOUDSITE[Cloud Site]
end
style APP fill:#3498db,color:#fff
style GIT fill:#333,color:#fff
style SITE fill:#2ecc71,color:#fff
8.1 Push to Git
cd apps/my_custom_app
git init
git add .
git commit -m "Initial commit: My Custom App"
git remote add origin git@github.com:yourcompany/my_custom_app.git
git push -u origin main
8.2 ติดตั้งบน Production Server
# SSH เข้า production server
cd ~/frappe-bench
# ดึง app จาก git
bench get-app https://github.com/yourcompany/my_custom_app.git
# ติดตั้งบน site
bench --site production-site.com install-app my_custom_app
# Migrate database
bench --site production-site.com migrate
# Build assets
bench build --app my_custom_app
# Restart
sudo supervisorctl restart all
8.3 ติดตั้งบน Docker ERPNext
เพิ่มใน apps.json:
[
{
"url": "https://github.com/yourcompany/my_custom_app.git",
"branch": "main"
}
]
Build Docker image ใหม่:
docker build --build-arg=APPS_JSON_BASE64=$(base64 -w 0 apps.json) \
-t my-erpnext:latest .
คำถามที่พบบ่อย (FAQ)
Custom App กับ Customization ต่างกันอย่างไร?
| ประเด็น | Custom App | Customization (UI) | |--------|------------|-------------------| | ความซับซ้อน | สูง - ต้องเขียนโค้ด | ต่ำ - ใช้ UI | | ความยืดหยุ่น | สูงมาก | จำกัด | | การย้าย site | ง่าย - export เป็น app | ยาก - ต้อง export ทีละอย่าง | | Version Control | ได้ - ใช้ Git | ไม่ได้ | | เหมาะกับ | งาน complex, ใช้หลาย site | งานง่าย, site เดียว |
จะเพิ่ม Custom Field ให้ Doctype ของ ERPNext ยังไง?
มี 2 วิธี:
วิธีที่ 1: ผ่าน UI
- ไปที่ Customize Form
- เลือก Doctype ที่ต้องการ
- เพิ่ม Custom Field
- Export เป็น Fixture
วิธีที่ 2: ผ่าน Code (แนะนำ)
# my_custom_app/install.py
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def after_install():
custom_fields = {
"Customer": [
{
"fieldname": "loyalty_tier",
"fieldtype": "Select",
"label": "Loyalty Tier",
"options": "Bronze\nSilver\nGold\nPlatinum",
"insert_after": "customer_group"
}
]
}
create_custom_fields(custom_fields)
ติดตั้ง Custom App บน Frappe Cloud ยังไง?
- Push app ไป GitHub (public หรือ private)
- ไปที่ Frappe Cloud > Benches > Apps
- กด "Add App" แล้วใส่ URL ของ Git repository
- รอ build เสร็จแล้ว install บน site
จะ debug Custom App ยังไง?
# ใช้ frappe.log_error() บันทึก error
frappe.log_error(message=str(data), title="Debug Info")
# ใช้ frappe.throw() แสดง error message
frappe.throw("Something went wrong!")
# ใช้ print() แล้วดูที่ bench console
print(f"Debug: {variable}")
ดู log:
# Error logs
tail -f ~/frappe-bench/logs/frappe.log
# Scheduler logs
tail -f ~/frappe-bench/logs/scheduler.log
สรุป (Summary)
- ERPNext Custom App คือวิธีที่ถูกต้องในการ customize ERPNext โดยไม่แก้ไข Core Code
- ใช้คำสั่ง
bench new-appเพื่อสร้าง App ใหม่ - Custom Doctype ใช้สร้างตารางข้อมูลใหม่ตามความต้องการ
- Hooks ใช้ override พฤติกรรมของ Doctype อื่นและกำหนด scheduled tasks
- Fixtures ช่วย export Custom Fields และ configurations ไปกับ App
- ควรใช้ Git ในการจัดการ version และ deploy บน production
การสร้าง Custom App อาจดูซับซ้อนในตอนแรก แต่เมื่อเข้าใจโครงสร้างแล้ว จะช่วยให้คุณ customize ERPNext ได้อย่างมีประสิทธิภาพและยั่งยืน
อ้างอิง (References)
- Frappe Framework Documentation - Create an App
- ERPNext Customization Guide
- Frappe Hooks Documentation
- Installing Custom Apps Guide
ต้องการความช่วยเหลือในการสร้าง Custom App หรือ Customize ERPNext? ติดต่อทีม Bunchee เราให้บริการ ERPNext Customization Services สำหรับธุรกิจไทย