ERPNext Developer Guideline for Beginners
คู่มือปูพื้นฐานสำหรับ Developer ใหม่ ก่อนพัฒนา ERPNext / Frappe / Government / GHIS
เอกสารนี้มีไว้สำหรับ Developer ใหม่, Developer ที่มาจาก Java / Go / Node.js / PHP / .NET / Vue / React รวมถึง AI Coding Agents
เป้าหมายคือทำให้เข้าใจก่อนว่า ERPNext ไม่ใช่เว็บ CRUD ทั่วไป
ถ้าใช้วิธีคิดแบบระบบทั่วไปโดยไม่เข้าใจ Frappe/ERPNext อาจทำให้หน้าจอดูเหมือนใช้งานได้ แต่ข้อมูลบัญชี, Stock, Workflow, Permission หรือ Audit เสียโดยไม่รู้ตัว
บริษัทตัวอย่างที่ใช้ทั้งเล่ม
ใน ERPNext คำว่า Company หมายถึงนิติบุคคลหรือหน่วยงานที่เป็นเจ้าของบัญชี ไม่จำเป็นต้องเป็นบริษัทจำกัด คู่มือนี้ใช้หน่วยงานสมมุติเดียวกันตลอดเล่มเพื่อให้เห็นความเชื่อมโยงของข้อมูล
| Master Data | คำอธิบายภาษาไทย | ข้อมูลตัวอย่าง |
|---|---|---|
| Company | หน่วยงานเจ้าของบัญชีและรายการธุรกิจ | โรงพยาบาลมหาวิทยาลัยตัวอย่าง (TUH) |
| Supplier | ผู้ขายหรือผู้ให้บริการที่หน่วยงานต้องจ่ายเงิน | บริษัท ไทยเมดซัพพลาย จำกัด |
| Customer | ลูกค้าหรือผู้รับบริการที่ต้องชำระเงินให้หน่วยงาน | หน่วยงานตัวอย่าง A |
| Item | สินค้า วัสดุ หรือบริการที่ซื้อขาย | MED-001 หน้ากากอนามัย |
| Warehouse | คลังที่เก็บและติดตามจำนวนสินค้า | คลังกลาง TUH, คลัง OPD TUH |
| Cost Center | หน่วยรับผิดชอบรายได้หรือค่าใช้จ่าย | ฝ่ายผู้ป่วยนอก TUH |
ตัวอย่างหลักคือ สั่งสินค้า 100 กล่อง → รับก่อน 80 กล่อง → ตั้งหนี้ 80 กล่อง → จ่ายเงินจริง → ย้าย 20 กล่องไปคลัง OPD แต่ละเหตุการณ์มีเอกสารและผลต่อ Stock/Accounting คนละจังหวะ
Master Data คือข้อมูลหลักที่ใช้ซ้ำ เช่น Company, Supplier และ Item ส่วน Transaction คือเอกสารเหตุการณ์ เช่น Purchase Receipt หรือ Payment Entry
1. ก่อนเริ่ม: ERPNext ต่างจากเว็บทั่วไปอย่างไร
Developer ทั่วไปอาจคุ้นกับโครงสร้าง:
Frontend
↓
REST API
↓
Controller
↓
Service
↓
Repository
↓
Database
และคิดว่า:
ถ้าต้องการเปลี่ยนข้อมูล
→ UPDATE DATABASE
ถ้าต้องการเพิ่ม field
→ ALTER TABLE
ถ้าต้องการ status ใหม่
→ UPDATE status
ถ้าต้องการ API
→ เขียน endpoint ใหม่
แนวคิดนี้ ใช้ตรง ๆ กับ ERPNext ไม่ได้
ERPNext มี Framework ที่ชื่อ Frappe คอยควบคุม:
- โครงสร้างข้อมูล
- Lifecycle ของเอกสาร
- Permission
- Workflow
- Audit
- Accounting
- Stock
- Background Job
- Scheduler
- API
- UI
ดังนั้นการเขียน code ที่ดูเหมือนง่าย เช่น:
UPDATE purchase_invoice
SET paid_amount = 10000
อาจทำให้หน้าจอแสดงว่าจ่ายแล้ว
แต่:
General Ledger ยังไม่ถูก
Payment Ledger ยังไม่ถูก
Outstanding ยังไม่ถูก
Financial Report ยังผิด
นี่คือสิ่งที่ Dev ใหม่ต้องเข้าใจก่อนที่สุด
2. Mental Model ที่ต้องจำ
ให้คิดเป็นชั้น dependency จากฐานขึ้นไป ชั้นด้านล่างใช้ความสามารถของชั้นก่อนหน้าและห้าม import ย้อนกลับ
Frappe Framework
↓
ERPNext Standard ERP
↓
erpnext_thailand Thai VAT / WHT / Tax Invoice
↓
erp_government_thailand กฎภาครัฐที่ใช้ร่วมกัน
↓
ghis_erp Target สำหรับ extension โรงพยาบาลที่ใช้ร่วมกัน
↓
erpnext_customize กฎและค่าของหน่วยงานเดียว
ghis_erp ยังไม่มีใน repository ปัจจุบัน และสร้างได้ต่อเมื่อ GHIS H1 ได้รับอนุมัติเท่านั้น การติดตั้งแบบ Government-only จึงข้ามชั้นนี้ได้
กฎสำคัญ:
ERPNext / Frappe = Platform ที่เรา Extend
ไม่ใช่ Source Code ที่เราแก้ตามใจ
3. คำศัพท์พื้นฐานที่ Developer ต้องรู้
3.1 Frappe คืออะไร
Frappe Framework คือ Framework ที่ ERPNext ใช้สร้างระบบ
ถ้าเปรียบเทียบกับสิ่งที่ Developer อาจรู้จัก:
Spring Boot
Laravel
Django
NestJS
ASP.NET
Frappe ก็เป็น Framework ประเภทหนึ่ง
แต่มีของสำเร็จรูปสำหรับระบบธุรกิจเยอะมาก เช่น:
- Data Model
- Form
- Permission
- Workflow
- REST API
- Background Job
- Scheduler
- Report
- Audit
กฎที่ต้องจำ
ถ้า Frappe มี mechanism รองรับอยู่แล้ว ให้ใช้ของ Frappe ก่อนสร้างเอง
3.2 ERPNext คืออะไร
ERPNext คือ Application ERP ที่สร้างบน Frappe
ตัวอย่าง module:
Accounting
Buying
Selling
Stock
Asset
HR
Projects
Manufacturing
CRM
ตัวอย่าง DocType สำคัญ:
Purchase Order
Purchase Invoice
Payment Entry
Sales Invoice
Journal Entry
Stock Entry
Purchase Receipt
Delivery Note
กฎที่ต้องจำ
อย่าสร้างของใหม่ซ้ำกับของที่ ERPNext มีอยู่แล้ว หากสามารถ Extend ได้
3.2.1 ตารางแปลศัพท์เอกสาร ERP สำหรับ Developer มือใหม่
คำแนะนำ: ก่อนเขียน feature ที่แตะเอกสารเหล่านี้ ให้เข้าใจก่อนว่าเอกสารตัวนั้น “เป็นเจ้าของเหตุการณ์อะไร” เพราะแต่ละตัวมีผลต่อ Stock และ Accounting คนละจังหวะ
| ศัพท์ ERPNext | ภาษาไทยแบบเข้าใจง่าย | ใช้เมื่อไร | ผลหลัก |
|---|---|---|---|
| Purchase Order (PO) | ใบสั่งซื้อ | องค์กรอนุมัติว่าจะซื้อสินค้าหรือบริการจากผู้ขาย | เป็น Commitment/คำสั่งซื้อ ยังไม่ใช่การรับของและยังไม่ใช่การจ่ายเงิน |
| Purchase Receipt (PR) | ใบรับสินค้า / รับของจากการซื้อ | ของมาถึงคลังและเรารับของจริง | เพิ่ม Stock สำหรับสินค้าที่เป็น Stock Item และอาจมีผลบัญชีตามการตั้งค่า |
| Purchase Invoice (PI) | ใบแจ้งหนี้ซื้อ / เอกสารตั้งหนี้เจ้าหนี้ | ผู้ขายส่ง Invoice และองค์กรรับรู้ว่า “เป็นหนี้ผู้ขายเท่าไร” | กระทบเจ้าหนี้และ Accounting เมื่อ Submit |
| Payment Entry (PE) | รายการรับเงิน / จ่ายเงิน | จ่ายเงินให้ผู้ขาย หรือรับเงินจากลูกค้า | กระทบเงินสด/ธนาคาร, Ledger และยอดค้างชำระ |
| Sales Order (SO) | ใบสั่งขาย | ลูกค้าสั่งซื้อและองค์กรรับคำสั่งขาย | ยืนยันคำสั่งขาย แต่ยังไม่ใช่การส่งของหรือรับเงิน |
| Delivery Note (DN) | ใบส่งสินค้า / ส่งมอบสินค้า | ส่งสินค้าจริงให้ลูกค้า | ตัด Stock สำหรับสินค้าที่เป็น Stock Item |
| Sales Invoice (SI) | ใบแจ้งหนี้ขาย / เอกสารตั้งหนี้ลูกหนี้ | เรียกเก็บเงินจากลูกค้า | กระทบลูกหนี้/รายได้และ Accounting เมื่อ Submit |
| Journal Entry (JE) | รายการบันทึกบัญชี / ใบสำคัญรายวัน | ปรับปรุงบัญชีที่ไม่ควรทำผ่าน Invoice/Payment/Stock transaction | Post Debit/Credit โดยตรงตามกฎบัญชี |
| Stock Entry (SE) | เอกสารเคลื่อนไหวสินค้าในคลัง | โอนคลัง, เบิกใช้, รับเข้า, ผลิต ฯลฯ | สร้าง Stock movement ตาม Purpose |
| Stock Reconciliation | เอกสารปรับยอดสต็อกตามตรวจนับจริง | ตรวจนับของจริงแล้วพบว่ายอดระบบไม่ตรง | ปรับ Stock ผ่าน transaction ที่ ERPNext เข้าใจ |
| GL Entry | รายการบัญชีแยกประเภท | ERPNext สร้างจาก transaction ทางบัญชี | เป็นผลลัพธ์ทางบัญชี ห้าม Dev แก้ตรง |
| Stock Ledger Entry | รายการเคลื่อนไหวสต็อก | ERPNext สร้างจากเอกสาร Stock | เป็นประวัติ movement ห้าม Dev แก้ตรง |
| Bin | ยอดสรุป Stock ของ Item ต่อ Warehouse | ERPNext ใช้คำนวณ actual/reserved/projected qty | เป็นข้อมูล derived ห้ามแก้เพื่อปรับยอด Stock |
จำแบบสั้นมาก
PO = ตกลงว่าจะซื้อ
PR = รับของแล้ว
PI = เป็นหนี้ผู้ขายแล้ว
PE = จ่ายเงินจริงแล้ว
SO = ลูกค้าสั่งแล้ว
DN = ส่งของแล้ว
SI = ลูกค้าเป็นหนี้เราแล้ว
PE = รับเงินจริงแล้ว
3.2.2 Flow การซื้อแบบเห็นภาพจริง
ตัวอย่าง: โรงพยาบาลซื้อ Notebook 10 เครื่อง เครื่องละ 30,000 บาท
1) Purchase Order (ใบสั่งซื้อ)
10 เครื่อง x 30,000 = 300,000
↓
"เราอนุมัติว่าจะซื้อ"
2) Purchase Receipt (ใบรับสินค้า)
ของมาถึงจริง 10 เครื่อง
↓
"เราได้รับของแล้ว"
Stock เพิ่ม
3) Purchase Invoice (ใบแจ้งหนี้ซื้อ / ตั้งหนี้เจ้าหนี้)
Supplier ส่ง Invoice 300,000
↓
"ตอนนี้เราเป็นหนี้ Supplier 300,000"
Accounting เกิด
4) Payment Entry (รายการจ่ายเงิน)
โอนเงินจากธนาคาร 300,000
↓
"เราได้จ่ายหนี้แล้ว"
Bank ลด + Outstanding Supplier ลด
สิ่งที่ Dev มือใหม่ชอบเข้าใจผิด
“ถ้าของมาถึงแล้ว ก็ UPDATE Purchase Order ให้ Paid ได้เลยไหม?”
ไม่ได้ เพราะ “รับของ”, “ตั้งหนี้” และ “จ่ายเงิน” เป็นคนละ transaction
การรับของ → Purchase Receipt
การตั้งหนี้ → Purchase Invoice
การจ่ายเงินจริง → Payment Entry
ถ้า Dev เปลี่ยนแค่ status = Paid หน้าจออาจดูถูก แต่ Ledger และ Outstanding จะไม่ถูกต้อง
หมายเหตุเรื่อง Configuration: Flow นี้เป็น baseline สำหรับการเรียนรู้ ไม่ใช่กฎตายตัวทุกบริษัท บางกรณี Purchase Invoice หรือ Sales Invoice สามารถอัปเดต Stock ได้ตามการตั้งค่า และ Delivery Note อาจเป็นขั้นตอนที่ไม่บังคับ Dev ต้องตรวจ configuration และ business process ของ Company ก่อนเสมอ
3.2.3 Flow การขายแบบเห็นภาพจริง
ตัวอย่าง: องค์กรขายอุปกรณ์ให้หน่วยงาน A จำนวน 5 ชิ้น
1) Sales Order (ใบสั่งขาย)
ลูกค้ายืนยันว่าจะซื้อ
↓
2) Delivery Note (ใบส่งสินค้า)
ส่งของออกจริง
↓
Stock ลด
3) Sales Invoice (ใบแจ้งหนี้ขาย / ตั้งหนี้ลูกหนี้)
เรียกเก็บเงินลูกค้า
↓
Accounts Receivable / รายได้เกิด
4) Payment Entry (รายการรับเงิน)
ลูกค้าโอนเงิน
↓
Bank เพิ่ม + Outstanding ลูกค้าลด
จุดสำคัญ
ส่งของ ≠ ออก Invoice ≠ รับเงิน
สามเหตุการณ์นี้อาจเกิดคนละวัน จึงห้าม Dev รวบรัดด้วยการ UPDATE status เดียว
หมายเหตุเรื่อง Configuration: บางธุรกิจออก Sales Invoice พร้อมตัด Stock หรือไม่ใช้ Delivery Note แยก ขั้นตอนจริงจึงต้องยืนยันกับผู้ใช้และตรวจการตั้งค่าของเอกสาร ห้ามข้าม transaction ด้วยการแก้ฐานข้อมูลตรง
3.2.4 อภิธานศัพท์สำหรับมือใหม่
| ศัพท์ | คำอธิบายภาษาไทย | ตัวอย่างใน TUH |
|---|---|---|
| Company | นิติบุคคลหรือหน่วยงานทางบัญชี เจ้าของสมุดบัญชีและ transaction | โรงพยาบาลมหาวิทยาลัยตัวอย่าง (TUH) |
| Master Data | ข้อมูลหลักที่ใช้ซ้ำ เช่น Supplier, Customer, Item, Warehouse และ Account | Supplier “บริษัท ไทยเมดซัพพลาย จำกัด” |
| Transaction | เอกสารที่บันทึกเหตุการณ์ธุรกิจและผ่าน lifecycle | Purchase Invoice หรือ Payment Entry |
| Ledger | บัญชีรายการเคลื่อนไหวที่ระบบสร้างจาก transaction | GL Entry และ Stock Ledger Entry |
| Posting Date | วันที่ที่รายการมีผลต่อบัญชีหรือ Stock อาจไม่ใช่วันสร้างเอกสาร | วันที่รับรู้เจ้าหนี้ของ Purchase Invoice |
| Accounts Payable (AP) | เจ้าหนี้: เงินที่องค์กรต้องจ่ายให้ Supplier | ยอดค้างชำระของไทยเมดซัพพลาย |
| Accounts Receivable (AR) | ลูกหนี้: เงินที่ Customer ต้องจ่ายให้องค์กร | ยอดค้างชำระของหน่วยงานตัวอย่าง A |
| Outstanding | ยอดหนี้ที่ยังชำระไม่ครบ | Invoice 30,000 จ่ายแล้ว 20,000 เหลือ 10,000 |
| Valuation | วิธีและมูลค่าที่ระบบใช้ตีราคาสินค้าคงคลัง | มูลค่าหน้ากาก MED-001 ในคลังกลาง |
| Permission-aware query | การอ่านข้อมูลโดยเคารพสิทธิ์ของผู้ใช้ปัจจุบัน | frappe.get_list สำหรับหน้ารายการที่ผู้ใช้เห็น |
| Idempotency | เรียกคำสั่งซ้ำแล้วไม่สร้างผลซ้ำโดยไม่ตั้งใจ | HIS ส่ง payment reference เดิมซ้ำ ต้องไม่สร้าง Payment Entry ซ้ำ |
| Migration | กระบวนการทำให้ schema และข้อมูลของ Site ตรงกับ code เวอร์ชันใหม่ | bench --site erp.localhost migrate |
| Fixture | configuration ที่ export เป็นไฟล์และติดตั้งซ้ำได้ | Custom Field หรือ Workflow ที่ project กำหนดให้ deploy ด้วย code |
3.3 App คืออะไร
App คือ source code package
ตัวอย่าง:
frappe
erpnext
erpnext_thailand
erp_government_thailand
ghis_erp (target; ยังไม่สร้าง)
erpnext_customize
Source บนเครื่อง Developer ของโปรเจกต์นี้อยู่ใน repository ย่อยใต้:
erpnext_docker/apps-src/
├── frappe READ ONLY
├── erpnext READ ONLY
├── erpnext_thailand
├── erp_government_thailand
└── erpnext_customize
ความหมาย
ถามก่อนว่า “อีกหน่วยงานภาครัฐไทยต้องใช้กฎเดียวกันหรือไม่” ถ้าใช่ให้พิจารณา erp_government_thailand; ถ้าเป็น shared hospital extension ให้รอ ghis_erp; ถ้าเป็นค่า วงเงิน workflow หรือรูปแบบเอกสารของ TUH เพียงแห่งเดียวให้ใช้ erpnext_customize
3.4 Site คืออะไร
Site คือ instance ของระบบหนึ่งชุด
แต่ละ Site มี:
- Database
- Configuration
- Installed Apps
- Files
- Users
ตัวอย่าง:
erp.localhost ← site ปัจจุบันของ environment นี้
training.localhost ← ตัวอย่างชื่อ site สำหรับ Lab แยก (ต้องสร้างและตรวจจริงก่อนใช้)
Dev ใหม่มักสับสน
App ≠ Site
App คือ code
Site คือระบบที่นำ app ไปติดตั้ง
Bench คือ environment ที่รวม apps, sites, process และคำสั่งสำหรับรัน Frappe หนึ่งชุด
4. DocType คืออะไร
คำนี้สำคัญที่สุดใน Frappe
คำอธิบายง่าย ๆ
DocType คือสิ่งที่รวมหลายอย่างเข้าด้วยกัน:
Database Table
+
Model
+
Form
+
Validation
+
Permission
+
API
+
Metadata
ตัวอย่าง:
Purchase Order
Supplier
Item
Employee
Government Budget
ถ้าเป็นระบบทั่วไป Developer อาจทำ:
CREATE TABLE
สร้าง Model
สร้าง API
สร้าง Form
สร้าง Permission
แต่ Frappe ใช้ DocType เป็นศูนย์กลาง
5. Document คืออะไร
Document คือ record หนึ่งรายการของ DocType
ตัวอย่าง:
DocType:
Purchase Order
Document:
PO-2026-00001
เปรียบเทียบง่าย ๆ:
DocType = Class / Table
Document = Object / Record
6. Child Table คืออะไร
Child Table คือข้อมูลรายการย่อยใน Document
ตัวอย่าง:
Purchase Order
Header
- Supplier
- Date
Items
- Item A
- Item B
- Item C
Items คือ Child Table
Dev ไม่ควรสร้าง table relation แบบ manual ถ้า Frappe Child Table ตอบโจทย์ได้
7. Custom Field คืออะไร
Custom Field ใช้เพิ่ม field เข้า Standard DocType
ตัวอย่าง ERPNext มี:
Purchase Order
สมมติว่า training app ต้องการเพิ่ม:
Training Reference
ไม่จำเป็นต้องสร้าง Purchase Order (ใบสั่งซื้อ) ใหม่
ให้สร้าง Custom Field custom_training_reference ใน training app และ export ให้ deploy ซ้ำได้ ชื่อจริงต้องตรวจจาก requirement และ metadata เดิมก่อน ห้ามสร้าง field นี้ใน project app หรือระบบจริง
8. เคสที่ Dev ปกติมักแก้ผิด: ต้องเพิ่มข้อมูลใน Purchase Order (ใบสั่งซื้อ)
วิธีคิดของ Dev ทั่วไป
แก้ schema ของ Purchase Order
เพิ่ม column
แก้ model
แก้ UI
หรือไปแก้ source:
erpnext_docker/apps-src/erpnext/erpnext/buying/...
ทำไมผิด
เมื่อ ERPNext upgrade:
ERPNext code ใหม่
+
code ที่เราแก้เอง
=
merge conflict / behavior conflict
วิธีที่ควรทำ
Custom Field
+
Hook
+
Service
9. Hook คืออะไร
Hook คือช่องทางที่ Frappe เปิดให้ Custom App เข้าไปเสริม behavior
เปรียบเทียบได้กับ:
Event Listener
Middleware Hook
Plugin Extension Point
ตัวอย่าง:
ตัวอย่างต่อไปนี้ใช้ชื่อ my_training_app เพื่อแสดงโครงสร้างเท่านั้น ไม่ใช่ module ที่มีอยู่ใน project:
doc_events = {
"Purchase Order": {
"validate": "my_training_app.events.purchase_order.validate"
}
}
หมายความว่า:
Purchase Order กำลัง Validate
↓
Frappe เรียก
↓
my_training_app
10. เหตุผลที่ใช้ Hook แทนการแก้ Core
ผิด:
เข้า erpnext_docker/apps-src/erpnext
แก้ purchase_order.py
ถูก:
ERPNext Purchase Order
↓
Hook
↓
custom app ที่รับผิดชอบ
กฎ
Core ถือเป็น Read Only
11. Service คืออะไร
Service คือแนวทางจัดโครงสร้าง code ของ project สำหรับเก็บ Business Logic ไม่ใช่ object type ที่ Frappe สร้างให้อัตโนมัติ
ตัวอย่าง:
budget/ledger.py
procurement/validation.py
disbursement/payment.py
แนะนำ flow:
Hook หรือ Controller
↓
Domain/Service module ของ project
↓
Document API / Frappe ORM
ไม่ควร:
Hook
↓
business logic 300 บรรทัด
12. Controller คืออะไร
Controller คือ Python class ที่ควบคุม behavior ของ DocType
ตัวอย่าง event:
validate
before_save
on_update
before_submit
on_submit
before_cancel
on_cancel
สิ่งเหล่านี้เรียกว่า lifecycle event
Controller ของ DocType สืบทอดจาก frappe.model.document.Document และ Frappe จะเรียก event ตาม lifecycle ของเอกสาร อย่าเรียก event เองเพื่อเลียนแบบการ Submit ให้ใช้ Document API เช่น doc.submit() ตามสิทธิ์และ transaction ที่ถูกต้อง
13. Document Lifecycle คืออะไร
Document ใน ERPNext ไม่ได้มีแค่:
Create
Update
Delete
แต่มีขั้นตอน เช่น:
New
↓
Validate
↓
Save
↓
Submit
↓
Cancel
↓
Amend
14. docstatus คืออะไร
ERPNext ใช้ docstatus
0 = Draft
1 = Submitted
2 = Cancelled
นี่ต่างจาก:
status = Approved
status = Waiting
status = Completed
สองอย่างนี้ไม่เหมือนกัน
15. เคส Dev ปกติแก้ไม่ได้แบบ CRUD: Invoice ที่ Submit แล้ว
สมมุติ:
Purchase Invoice
PI-0001
ยอด = 100,000
Submitted แล้ว
User บอก:
ช่วยแก้ยอดเป็น 120,000
Dev ทั่วไปอาจทำ:
UPDATE purchase_invoice
SET total = 120000
WHERE id = ...
ปัญหา
Purchase Invoice (ใบแจ้งหนี้ซื้อ / เอกสารตั้งหนี้เจ้าหนี้จากการซื้อ) อาจสร้าง:
GL Entry
Accounts Payable
Tax
Outstanding
Payment Ledger
ถ้าแก้เฉพาะ Invoice:
Invoice = 120,000
GL = 100,000
ระบบเสียทันที
วิธีที่ถูก
ต้องใช้ flow ที่ ERPNext รองรับ เช่น:
Cancel
↓
Amend
↓
แก้ยอด
↓
Submit ใหม่
หรือใช้ Accounting Adjustment ที่เหมาะสม
16. Submitted Document คืออะไร
Submitted Document คือเอกสารที่ผ่าน transaction สำคัญแล้ว
ตัวอย่าง:
Purchase Invoice
Sales Invoice
Journal Entry
Payment Entry
Stock Entry
Purchase Receipt
Delivery Note
กฎ
Submitted Document ห้ามแก้ database โดยตรง
17. ORM คืออะไร
ORM คือ API สำหรับคุยกับ database ผ่าน Frappe
ตัวอย่าง:
frappe.get_doc(...)
frappe.get_list(...)
frappe.get_all(...)
frappe.db.get_value(...)
Developer ควรใช้ก่อน Raw SQL
ข้อแตกต่างด้าน Permission:
frappe.get_listใช้ permission ของผู้ใช้ปัจจุบัน แต่frappe.get_allไม่ใช้ permission เดียวกัน จึงห้ามเปลี่ยนแทนกันเพียงเพราะ query ได้ข้อมูลไม่ครบ และfrappe.db.get_valueก็ไม่ใช่การตรวจ authorization ให้เลือก API ตาม trust boundary ของงาน
อ้างอิง: Frappe Database API
18. เคส Dev ปกติชอบแก้ตรง DB
ผิด:
UPDATE `tabPurchase Order`
SET status='Completed'
ปัญหา:
ERPNext อาจคำนวณ status จาก:
received_qty
billed_qty
docstatus
per_received
per_billed
ทำให้ UI บอก Completed แต่ transaction จริงไม่ Completed
19. Raw SQL ใช้ได้ไหม
ใช้ได้ แต่เป็น เครื่องมือระดับระวัง
เหมาะกับ:
- Report
- Aggregate
- Read-only analytics
- Performance case ที่มีเหตุผล
ไม่ควรใช้กับ:
- Accounting write
- Stock write
- Submitted Document
- Workflow state manipulation
20. Accounting คือ Danger Zone
ERPNext Accounting มีระบบ Ledger
สำคัญที่สุดคือ:
GL Entry
GL = General Ledger (บัญชีแยกประเภททั่วไป)
ภาษาไทย:
รายการบัญชีแยกประเภท
ตัวอย่าง:
Purchase Invoice (ใบแจ้งหนี้ซื้อ / เอกสารตั้งหนี้เจ้าหนี้จากการซื้อ) 10,000
ERPNext อาจ post:
Expense Dr 10,000
Payable Cr 10,000
21. GL Entry (รายการบัญชีแยกประเภท) คืออะไร
GL Entry (รายการบัญชีแยกประเภท) คือรายการบัญชีที่ถูก post จาก transaction
Developer ไม่ควรสร้างหรือแก้ GL ด้วย SQL เอง
ห้าม
UPDATE `tabGL Entry`
22. เคส Dev ปกติ: ต้องการให้ Invoice เป็น Paid
User บอก:
Invoice นี้จ่ายแล้ว ช่วยเปลี่ยนเป็น Paid
Dev ปกติอาจทำ:
paid_amount = total
status = Paid
ผิด
เพราะ ERPNext ต้องมี:
Payment Entry
↓
GL Entry
↓
Payment Ledger
↓
Outstanding Calculation
วิธีถูก
สร้าง Payment Entry (รายการรับเงิน / จ่ายเงิน) ตาม flow ของ ERPNext
23. Payment Entry (รายการรับเงิน / จ่ายเงิน) คืออะไร
Document สำหรับบันทึกการรับ/จ่ายเงิน
ตัวอย่าง:
Pay Supplier
Receive from Customer
Internal Transfer
Payment Entry (รายการรับเงิน / จ่ายเงิน) เชื่อมกับ Accounting
ดังนั้นไม่ควรจำลอง payment ด้วยการ update field
24. Stock Ledger คือ Danger Zone อีกส่วน
ERPNext stock ไม่ได้เก็บแค่:
quantity
แต่เกี่ยวกับ:
Stock Ledger Entry
Bin
Valuation
Warehouse
Serial Number
Batch
Accounting
25. Stock Ledger Entry (รายการเคลื่อนไหวสต็อก) คืออะไร
ภาษาไทย:
รายการเคลื่อนไหวสต็อก
ตัวอย่าง:
ซื้อเข้า +10
ขายออก -2
โอนคลัง -5/+5
ทุก movement มีผลต่อ Stock Ledger
26. Bin คืออะไร
Bin คือ snapshot ของข้อมูล stock ต่อ:
Item + Warehouse
เช่น:
Item A
Warehouse Phuket
actual_qty = 100
reserved_qty = 20
กฎ
ห้าม Update Bin เพื่อปรับ Stock
27. เคส Dev ปกติ: Stock ในหน้าจอผิด
User บอก:
Stock ควรเป็น 100 แต่ตอนนี้ 95 ช่วยแก้เป็น 100
Dev ทั่วไป:
UPDATE Bin
SET actual_qty = 100
ผิด
เพราะ:
Bin = 100
แต่ Stock Ledger
ยังรวมได้ 95
ภายหลัง repost แล้วค่าจะกลับ หรือ valuation ผิด
วิธีถูก
ใช้:
Stock Reconciliation
หรือ transaction ที่ ERPNext รองรับ
28. Stock Reconciliation (เอกสารปรับยอดสต็อกตามการตรวจนับจริง) คืออะไร
Document สำหรับปรับ stock จริงให้ตรงกับระบบ
ใช้เมื่อ:
ตรวจนับจริง
พบ quantity ไม่ตรง
ERPNext จะสร้าง stock movement ที่ถูกต้อง
29. Workflow คืออะไร
Workflow คือ Approval Flow
ตัวอย่าง:
Draft
↓
หัวหน้าตรวจ
↓
การเงินอนุมัติ
↓
ผู้อำนวยการอนุมัติ
30. Workflow ไม่ใช่ Business Logic
ตัวอย่าง:
Workflow บอกว่า:
Finance Approved
แต่ไม่ได้หมายความว่า:
Budget เพียงพอ
Budget validation ต้องตรวจ server-side
31. Client Script คืออะไร
Client Script คือ JavaScript ที่รันใน Browser
ใช้ทำ:
Show/Hide
Warning
Auto Fill
UX Validation
32. เคส Dev ใหม่พลาด: Validate แค่ Client
ตัวอย่าง:
if (amount > budget) {
alert("Budget ไม่พอ")
}
ดูเหมือนใช้งานได้
แต่ User สามารถเรียก REST API โดยตรง
ดังนั้น API อาจส่ง:
amount = 1000000
ผ่านเข้าระบบ
วิธีถูก
ต้อง validate server:
Browser Validation
+
Server Validation
33. Permission คืออะไร
Permission คือสิทธิ์การใช้งาน
ไม่ใช่แค่:
Admin
User
Frappe มีหลาย layer:
Role
DocType Permission
Permission Level
User Permission
Document Scope
34. Role คืออะไร
Role คือกลุ่มสิทธิ์
ตัวอย่าง:
Procurement Officer
Finance Officer
Budget Approver
Hospital Admin
35. User Permission คืออะไร
User Permission ใช้จำกัดว่าคนนี้เห็นข้อมูล subset ไหน
ตัวอย่าง:
Role:
Procurement Officer
User A:
Company = โรงพยาบาลมหาวิทยาลัยตัวอย่าง (TUH)
User B:
Company = หน่วยงานตัวอย่าง B
ทั้งสองคนมี Role เดียวกัน แต่ User Permission จำกัด Company คนละแห่ง จึงต้องทดสอบทั้งหน้าจอ List, Report และ API ว่ามองเห็นเฉพาะข้อมูลที่อนุญาต
36. เคส Dev ปกติ: ซ่อน Button แล้วคิดว่าปลอดภัย
ผิด:
if user != admin:
hide_delete_button()
User ยังสามารถยิง API ได้
Security ต้อง enforce ฝั่ง server
37. Naming Series คืออะไร
ERPNext มีระบบสร้างเลขเอกสาร
ตัวอย่าง:
PO-2026-00001
PI-2026-00001
Government อาจใช้:
GOV-BUD-2569-00001
สำคัญ: Naming Series ช่วยสร้างชื่อเอกสารที่ไม่ชนกัน แต่ไม่ได้รับประกันเลขราชการแบบห้ามมีช่องว่าง หาก requirement ต้อง “ออกเลขเมื่อ Submit, ห้ามข้ามเลข, แยกตามปี/หน่วยงาน และรองรับหลาย request พร้อมกัน” ต้องออกแบบ controlled counter พร้อม database locking, การยกเลิก และ audit trail โดยให้ Senior review
38. เคส Dev ปกติ: Generate ID ด้วย MAX + 1
ผิด:
SELECT MAX(id)+1
ถ้ามี 2 request พร้อมกัน:
Request A → 101
Request B → 101
เกิด duplicate
ใช้ Naming Series ของ Frappe สำหรับเลขทั่วไป หรือใช้ controlled counter ที่ออกแบบเรื่อง concurrency และ audit แล้วสำหรับเลขราชการที่มีข้อกำหนดพิเศษ ห้ามใช้ MAX + 1
39. Background Job คืออะไร
Background Job คือการส่งงานไปให้ Worker ทำ
เหมาะกับงาน:
Import 100,000 records
Generate PDF จำนวนมาก
Sync HIS
Large recalculation
ไม่ควรรันทั้งหมดใน HTTP request
40. Worker คืออะไร
Worker คือ process ที่ทำงาน background
Flow:
User
↓
API
↓
Queue
↓
Worker
↓
Process
41. Queue คืออะไร
Queue คือคิวงาน
Frappe มี queue สำหรับงานประเภทต่าง ๆ
แนวคิด:
short
default
long
42. Scheduler คืออะไร
Scheduler คือระบบรันงานตามเวลา
เช่น:
ทุก 1 ชั่วโมง
ทุกวัน
ทุกสัปดาห์
เหมือน cron แต่ integrated กับ Frappe
43. เคส Dev ปกติ: Sync ข้อมูลทุก 5 นาที
Dev อาจสร้าง:
Windows Task Scheduler
Linux Cron
Custom Node Job
ทั้งที่ระบบมี Frappe Scheduler
ผลคือ production มี job กระจัดกระจายและ trace ยาก
ถ้า Frappe Scheduler รองรับ ให้ใช้ Scheduler ก่อน
44. Transaction คืออะไร
Transaction คือชุดการเปลี่ยนข้อมูลที่ต้องสำเร็จพร้อมกัน
ตัวอย่าง:
Reserve Budget
↓
Create Purchase Order
↓
Update Commitment
ถ้าขั้นที่ 3 fail
ขั้น 1 และ 2 ต้องถูกพิจารณาว่าควร rollback หรือไม่
45. Commit คืออะไร
Commit คือยืนยัน transaction ลง database
Dev ใหม่ไม่ควรใช้:
frappe.db.commit()
ทุกครั้งหลัง save
เพราะ Frappe จัดการ transaction ให้อยู่แล้วในหลายกรณี
46. เคสที่ manual commit ทำให้ระบบพัง
Create Budget Reservation
COMMIT
↓
Create PO
↓
Error
เกิด:
Reservation อยู่
PO ไม่มี
ข้อมูลค้าง
47. Migration คืออะไร
Migration คือการปรับโครงสร้างระบบเมื่อ code เปลี่ยน
อาจรวม:
- Schema
- DocType
- Custom Field
- Patch
- Fixtures
จึงไม่ใช่แค่:
git pull
restart
48. bench migrate คืออะไร
คำสั่งสำคัญที่ใช้ apply การเปลี่ยนแปลงของ Frappe/ERPNext
ตัวอย่าง flow deploy:
Pull Code
↓
Install Dependencies
↓
bench migrate
↓
Build
↓
Restart
49. Fixture คืออะไร
Fixture คือ configuration ที่ export มาเก็บใน Git
เช่น:
Custom Field
Workflow
Role
Property Setter
เพื่อให้ DEV / UAT / PROD เหมือนกัน
50. เคส Dev ใหม่: ทำ Custom Field ใน DEV แล้วจบ
DEV มี:
custom_training_reference
แต่ PROD ไม่มี
เพราะ field อยู่แค่ DB ของ DEV
ต้อง export customization/fixture ให้ deploy ได้
51. Patch คืออะไร
Patch คือ code ที่รันตอน migration เพื่อแก้ data หรือเปลี่ยนโครงสร้าง
เช่น:
Migration old field → new field
Backfill values
Normalize old records
52. REST API ของ Frappe
Frappe มี API ของ Document อยู่แล้ว
ดังนั้นก่อนสร้าง endpoint ใหม่ ให้ดูว่า standard API ใช้ได้หรือไม่
53. Whitelisted Method คืออะไร
Function ที่เปิดให้เรียกผ่าน API ได้
ตัวอย่าง:
@frappe.whitelist()
def reserve_budget(...):
...
ต้องตรวจ
- Permission
- Input
- Company
- User scope
- Duplicate
- State
54. Multi-Company คืออะไร
ERPNext รองรับหลาย Company
อย่า hard-code:
company = "โรงพยาบาลมหาวิทยาลัยตัวอย่าง"
ควรใช้:
doc.company
เพราะระบบเดียวอาจใช้หลายหน่วยงาน
55. Audit Trail คืออะไร
Audit Trail คือประวัติว่า:
ใคร
ทำอะไร
เมื่อไร
เอกสารเปลี่ยนอย่างไร
Government / GHIS มีความสำคัญมาก
การ UPDATE database ตรงอาจ bypass audit mechanism
56. Integration คืออะไร
Integration คือเชื่อม ERPNext กับระบบอื่น
เช่น:
HIS
Bank
Procurement System
e-Tax
Government API
ต้องคิดเรื่อง:
- Retry
- Timeout
- Duplicate
- Logging
- Error
- Idempotency
57. Idempotency คืออะไร
ภาษาไทยง่าย ๆ:
เรียกซ้ำแล้วต้องไม่สร้างผลลัพธ์ซ้ำโดยไม่ตั้งใจ
ตัวอย่าง:
Payment API timeout
↓
Caller retry
↓
สร้าง Payment อีกใบ
กลายเป็นจ่ายสองครั้ง
ต้องใช้:
external_reference_id
idempotency_key
58. Override คืออะไร
Override คือการแทน behavior เดิมของ Framework/ERPNext
ถือเป็นเครื่องมือเสี่ยงกว่า Hook
ก่อน Override ต้องถาม:
Hook ทำไม่ได้จริงหรือ?
Custom Field ทำไม่ได้หรือ?
Service extension ทำไม่ได้หรือ?
59. Monkey Patch คืออะไร
Monkey Patch คือการเปลี่ยน function/class behavior ตอน runtime
ใน project นี้:
Forbidden by default
เพราะ trace และ upgrade ยากมาก
60. Property Setter คืออะไร
Property Setter ใช้เปลี่ยน property ของ Standard DocType โดยไม่แก้ source
เช่น:
Label
Required
Hidden
Default
ควรใช้แทนการเข้าไปแก้ JSON ของ Core โดยตรงในหลายกรณี
61. Workspace คืออะไร
Workspace คือหน้ารวมเมนู / dashboard ใน Desk
ใช้สร้าง:
Government Workspace
GHIS Workspace
Budget Workspace
ไม่ต้องสร้าง SPA ใหม่ทันที
62. Report คืออะไร
Frappe มีหลาย report เช่น:
Query Report
Script Report
Report Builder
อย่าทำ report เป็น write logic
Report ควรเน้น read-only
63. Print Format คืออะไร
ใช้สร้างเอกสารพิมพ์ เช่น:
ใบขอซื้อ
ใบสั่งซื้อ
ใบอนุมัติ
Voucher
อย่าใส่ business logic สำคัญไว้ใน Print Format
64. Safe Zone / Caution Zone / Danger Zone
Safe Zone — มือใหม่ทำได้หลังเข้าใจพื้นฐาน
Custom DocType
Custom Field
Workspace
Report
Print Format
Simple Workflow
Service module ใน training app
Caution Zone — ควรมี Senior Review
Hook บน Standard DocType
Raw SQL
Permission Hook
Scheduler
Background Job
Migration
Submitted Document
Override
API ที่เปิดให้ client/integration เรียก
Custom DocType ที่กระทบ process จริง
Danger Zone — ห้ามทำเอง
Modify frappe Core
Modify erpnext Core
Direct GL Entry update
Direct Stock Ledger update
Direct Bin update
Direct accounting adjustment โดยไม่ผ่าน transaction ที่อนุมัติ
Monkey Patch
65. ตัวอย่างสถานการณ์จริงสำหรับสอน Dev
Case A — User ขอเพิ่มเลขอ้างอิงภายในใน PO
Dev ปกติคิด
แก้ table
แก้ ERPNext PO source
ERPNext way
Custom Field
+
Fixture
ถ้าต้อง Validate:
Hook
↓
Domain validation module
Case B — User ขอให้ Invoice เป็น Paid
Dev ปกติ
UPDATE status = Paid
ERPNext way
Payment Entry
เพราะต้อง post Accounting
Case C — User ขอแก้ Stock
Dev ปกติ
UPDATE quantity
ERPNext way
Stock Reconciliation
Case D — User ขอแก้ Invoice ที่ Submit แล้ว
Dev ปกติ
UPDATE total
ERPNext way
Cancel
↓
Amend
↓
Submit
Case E — User ขอเพิ่ม Approval
Dev ปกติ
status column
+
if statement
ERPNext way
Workflow
+
Server-side validation
Case F — User ขอจำกัด Company
Dev ปกติ
WHERE company = ...
ทุก API
ERPNext way
พิจารณา:
Role
+
User Permission
+
Server Permission
Case G — User ขอ Import 100,000 แถว
Dev ปกติ
API request loop
ERPNext way
Upload
↓
Queue
↓
Background Worker
Case H — User ขอ Job ทุกคืน
Dev ปกติ
cron.sh
ERPNext way
Frappe Scheduler
65.1 Case Lab — ตัวอย่างที่ Dev เว็บทั่วไปมักแก้ผิด แต่ ERPNext ต้องคิดอีกแบบ
Case I — “ของเข้าคลังแล้ว ช่วยเพิ่ม Stock จาก 95 เป็น 100”
Requirement จาก User
ตรวจนับจริงพบ Item
MED-001มี 100 กล่อง แต่หน้า ERPNext แสดง 95 กล่อง
❌ วิธีที่ Dev CRUD อาจทำ
UPDATE `tabBin`
SET actual_qty = 100
WHERE item_code = 'MED-001'
AND warehouse = 'คลังกลาง TUH';
💥 ทำไมอันตราย
ระบบจะกลายเป็น:
Bin = 100
Stock Ledger รวมจริง = 95
เมื่อ ERPNext Repost / Recalculate ข้อมูลอาจกลับไป 95 หรือ Valuation เพี้ยน
✅ ERPNext Way
ใช้:
Stock Reconciliation (เอกสารปรับยอดสต็อกจากการตรวจนับจริง)
ระบุ:
Item = MED-001
Warehouse = คลังกลาง TUH
Actual Qty = 100
ERPNext จะสร้าง movement ที่ trace และ audit ได้
กฎที่สอนน้อง
อย่าปรับ Stock ด้วยการแก้ยอดปลายทาง ให้สร้าง Transaction ที่อธิบายว่า “ทำไม Stock เปลี่ยน”
Case J — “Invoice นี้ลูกค้าจ่ายแล้ว ช่วยเปลี่ยนให้เป็น Paid”
Requirement จาก User
Sales Invoice SI-00045 = 50,000 บาท
ลูกค้าโอนเงินแล้ว
แต่ระบบยังแสดง Outstanding 50,000
❌ วิธีที่ Dev ทั่วไปอาจทำ
status = Paid
outstanding_amount = 0
paid_amount = 50000
💥 สิ่งที่ไม่ได้เกิดขึ้น
Bank Account ไม่เพิ่ม
GL Entry ไม่เกิด
Payment Ledger ไม่เกิด
Invoice Allocation ไม่เกิด
Audit การรับเงินไม่มี
✅ ERPNext Way
สร้าง:
Payment Entry (รายการรับเงิน)
Payment Type = Receive
Party = Customer
Reference = SI-00045
Amount = 50,000
Bank Account = ...
เมื่อ Submit แล้ว ERPNext จึงตัด Outstanding อย่างถูกต้อง
กฎที่สอนน้อง
Paidเป็น “ผลลัพธ์ของ transaction การรับเงิน” ไม่ใช่ status ที่ Dev ควร set เอง
Case K — “Supplier ส่ง Invoice ผิด จาก 100,000 ต้องเป็น 120,000”
สภาพระบบ
Purchase Invoice PI-00120
docstatus = 1 (Submitted)
ยอดเดิม = 100,000
❌ วิธีที่ Dev CRUD อาจทำ
UPDATE `tabPurchase Invoice`
SET grand_total = 120000
WHERE name = 'PI-00120';
💥 ผลที่เสี่ยงเกิด
Purchase Invoice = 120,000
GL Entry = 100,000
Payable = 100,000
รายงาน = ไม่ตรงกัน
✅ ERPNext Way
ตรวจ business policy แล้วใช้ flow ที่ถูก เช่น:
Cancel PI-00120
↓
Amend
↓
แก้เป็น 120,000
↓
Submit เอกสารใหม่
หรือใช้ Adjustment/Credit-Debit mechanism ที่ project กำหนด
กฎ
Submitted accounting document ไม่ใช่ record ธรรมดาที่ UPDATE ได้
Case L — “PO ห้ามเกิน Budget”
Requirement
Government Budget คงเหลือ:
100,000 บาท
User กำลังสร้าง Purchase Order:
120,000 บาท
❌ วิธีที่ยังไม่พอ
เขียน Client Script:
if (frm.doc.grand_total > frm.doc.remaining_budget) {
frappe.throw("Budget ไม่พอ");
}
💥 ปัญหา
API / Integration สามารถเรียก server โดยไม่ผ่าน Browser
✅ ERPNext Way
เอกสารที่ project กำหนดว่าเป็นจุดผูกพันงบประมาณ
↓
erp_government_thailand Hook
↓
Budget control module ตรวจนโยบายบน Server
↓
Server ตรวจ Budget จริง
Client Script ใช้เพิ่ม UX ได้ แต่ Server Validation ต้องเป็น source of truth
ตัวอย่างที่ควรคิดต่อ
หาก User A และ User B เปิดหน้าจอพร้อมกัน:
Budget เหลือ 100,000
A ขอ 80,000
B ขอ 80,000
ทั้งคู่เห็น budget 100,000 ได้พร้อมกัน
ดังนั้น budget control ต้องพิจารณา Concurrency (การทำงานพร้อมกัน) / Reservation (การกันวงเงิน) ไม่ใช่แค่ if amount > remaining และต้องยืนยันจาก architecture ว่าเอกสารใดเป็นเจ้าของ commitment ก่อนเขียน Hook
Case M — “ส่งของแล้ว แต่ยังไม่ต้องออก Invoice วันนี้”
นี่เป็นเคสที่ Dev ใหม่มักคิดว่า Delivery Note กับ Sales Invoice ควรเป็น record เดียวกัน
ธุรกิจจริง
10 ก.ย. ส่งสินค้า
15 ก.ย. ออก Invoice
30 ก.ย. ลูกค้าชำระเงิน
ERPNext แยกเป็น:
10 ก.ย. Delivery Note (ส่งสินค้า)
15 ก.ย. Sales Invoice (ตั้งหนี้ลูกหนี้)
30 ก.ย. Payment Entry (รับเงิน)
ทำไมต้องแยก
เพราะ:
Stock Event = 10 ก.ย.
Accounting Event = 15 ก.ย.
Cash Event = 30 ก.ย.
ถ้ารวมทุกอย่างเป็น status เดียว Audit และรายงานตามวันที่จะผิด
Case N — “รับของมาแค่ 8 จาก PO 10 ชิ้น”
Purchase Order
สั่ง 10 เครื่อง
วันแรก Supplier ส่ง 8 เครื่อง
❌ Dev ใหม่อาจคิด
PO.status = Received
✅ ERPNext Way
สร้าง:
Purchase Receipt #1 = 8 เครื่อง
PO จะยังรู้ว่า:
Ordered = 10
Received = 8
Pending = 2
อีก 2 เครื่องมาทีหลัง:
Purchase Receipt #2 = 2 เครื่อง
Lesson
ERPNext ใช้ “เอกสาร transaction + quantity progress” ไม่ใช่ boolean/status อย่างเดียว
Case O — “ต้องย้ายยา 50 กล่อง จากคลังกลางไปคลัง OPD”
❌ วิธี Dev DB
Warehouse A qty -= 50
Warehouse B qty += 50
✅ ERPNext Way
ใช้:
Stock Entry (เอกสารเคลื่อนไหวสินค้าในคลัง)
Purpose = Material Transfer
ผลที่ต้องเกิดพร้อมกัน:
คลังกลาง TUH -50
คลัง OPD TUH +50
Stock Ledger มีต้นทางและปลายทาง
Audit รู้ว่าใครย้าย เมื่อไร
Case P — “ฝ่ายบัญชีขอปรับค่าใช้จ่ายจาก Account A ไป Account B”
นี่เป็นเคสที่ Dev ไม่ควรเข้าไป UPDATE GL Entry
❌ ห้าม
UPDATE `tabGL Entry`
SET account = 'Account B'
WHERE ...
✅ ต้องคุยกับ Accounting Flow
หากเป็นรายการปรับปรุงบัญชีที่เหมาะสม อาจใช้:
Journal Entry (รายการบันทึกบัญชี / ใบสำคัญรายวัน)
ตัวอย่าง:
Account B Dr 10,000
Account A Cr 10,000
เพื่อให้มีเอกสารอ้างอิง, ผู้อนุมัติ, วันที่บัญชี และ Audit Trail
Lesson
GL Entry เป็น “ผลลัพธ์ปลายทางของ Accounting Engine” ไม่ใช่หน้าจอ CRUD สำหรับแก้บัญชี
Case Q — “Import 100,000 รายการแล้ว API timeout”
❌ วิธีที่ไม่ควร
POST /import
→ loop 100,000 rows
→ รอ request เดียวจนเสร็จ
User ปิด browser หรือ proxy timeout แล้วไม่รู้ว่างานไปถึงไหน
✅ ERPNext Way
Upload File
↓
สร้าง Import Job
↓
frappe.enqueue(...)
↓
Worker ทำงาน
↓
เก็บ Progress / Error / Result
Lesson
งานที่นานควรเปลี่ยนจาก “Request/Response” เป็น “Job”
Case R — “ซ่อนปุ่ม Submit สำหรับคนที่ไม่มีสิทธิ์”
❌ ยังไม่ปลอดภัย
frm.toggle_display("submit", false)
เพราะคนที่รู้ API อาจยิง request โดยตรง
✅ วิธีคิดที่ถูก
UI hide button
+
Role / Permission
+
Server-side state/permission validation
Lesson
สิ่งที่ Browser ห้าม ไม่ได้แปลว่า Server ห้าม
66. Government / GHIS Architecture Rule
แบ่ง responsibility:
erp_government_thailand
เก็บ:
- Government Budget
- Fiscal Year
- Fund Source
- Government Procurement
- Government Rules
ghis_erp (target; ยังไม่สร้าง)
เก็บ:
- Hospital-specific logic
- HIS integration
- GHIS workflow
- Hospital mapping
- Hospital-specific reports
67. ห้ามสร้าง Dependency มั่ว
Dependency ที่อนุญาตมีทิศทางเดียว:
frappe → erpnext → erpnext_thailand → erp_government_thailand → ghis_erp → erpnext_customize
ห้าม import ย้อนขึ้น เช่น erp_government_thailand → ghis_erp เพราะทำให้ layer กลางใช้ซ้ำไม่ได้และเสี่ยง Circular Dependency ส่วน Government-only deployment สามารถไม่มี ghis_erp ได้
68. กฎสำหรับ AI Agents
Claude / Codex / Agent ต้องอ่าน erpnext_docker/AGENTS.md และเอกสาร canonical ตามลำดับก่อน coding ส่วนคู่มือนี้ใช้ปูพื้นฐาน ไม่ใช่ authority ของ phase หรือ architecture
Agent ต้อง:
1. Inspect ก่อนแก้
2. ห้ามแก้ Core
3. ใช้ Standard ก่อน Custom
4. ใช้ Hook ก่อน Override
5. ใช้ Service เก็บ Business Logic
6. ไม่แตะ GL/Stock Ledger/Bin
7. ไม่เริ่ม Phase ต่อเอง
8. รายงาน Files Changed
9. รายงาน Migration Impact
10. รายงาน Tests
69. ห้าม Agent Assume
ห้ามเดาว่า:
Field นี้มีอยู่
API นี้มีอยู่
Hook นี้ใช้ได้
DocType นี้มีอยู่
ต้อง inspect code/version ก่อน
70. กฎ 10 ข้อที่ Dev มือใหม่ต้องจำขึ้นใจ
1. ห้ามแก้ Frappe Core
2. ห้ามแก้ ERPNext Core
3. ใช้ Standard DocType ก่อนสร้างใหม่
4. ใช้ Custom Field แทนแก้ Core
5. ใช้ Hook แทนแก้ Controller Core
6. Submitted Document ห้าม Update DB ตรง
7. Accounting ห้ามแก้ GL ตรง
8. Stock ห้ามแก้ Bin / Stock Ledger ตรง
9. Client Script ไม่ใช่ Security
10. ทุก customization ต้อง deploy ซ้ำได้
71. ก่อนเขียน Code ให้ถาม 12 คำถามนี้
[ ] ERPNext มีของเดิมหรือยัง?
[ ] Extend ของเดิมได้หรือไม่?
[ ] ต้องสร้าง DocType ใหม่จริงหรือ?
[ ] เอกสารนี้ Submit ได้หรือไม่?
[ ] กระทบบัญชีหรือไม่?
[ ] กระทบ Stock หรือไม่?
[ ] กระทบ Permission หรือไม่?
[ ] กระทบ Multi-company หรือไม่?
[ ] ต้องมี Workflow หรือไม่?
[ ] ต้องเป็น Background Job หรือไม่?
[ ] ต้อง Migration หรือ Fixture หรือไม่?
[ ] ถ้า ERPNext upgrade code นี้ยังอยู่ได้หรือไม่?
72. ก่อน Merge
[ ] ไม่แก้ frappe core
[ ] ไม่แก้ erpnext core
[ ] ไม่มี direct GL update
[ ] ไม่มี direct Stock Ledger update
[ ] ไม่มี direct Bin update
[ ] Server validation มีครบ
[ ] Permission ถูกตรวจ server-side
[ ] Submitted lifecycle ถูกต้อง
[ ] Customization export แล้ว
[ ] Migration ถูกพิจารณา
[ ] Tests ผ่าน
73. สัญญาณอันตรายที่ต้องเรียก Senior
ถ้าเห็น code เหล่านี้ให้หยุด Review:
frappe.db.commit()
frappe.db.sql("UPDATE ...")
tabGL Entry
tabStock Ledger Entry
tabBin
หรือมีการแก้:
erpnext_docker/apps-src/frappe/
erpnext_docker/apps-src/erpnext/
74. วิธีคิดที่ถูกสำหรับ Dev ใหม่
อย่าถามแค่:
จะเขียน code ให้ทำงานยังไง?
ให้ถาม:
ERPNext ต้องการให้ transaction นี้เกิดผ่าน mechanism ไหน?
ตัวอย่าง:
ต้องการจ่ายเงิน
→ Payment Entry
ต้องการแก้ Stock
→ Stock transaction
ต้องการ Approval
→ Workflow
ต้องการเพิ่ม field
→ Custom Field
ต้องการ Validation
→ Server Hook / Service
ต้องการงานหนัก
→ Background Job
ต้องการงานตามเวลา
→ Scheduler
75. หลักสุดท้าย
ใน ERPNext การทำให้ “หน้าจอดูถูก” ไม่ได้แปลว่า “ระบบถูก”
ต้องตรวจ:
Data
Lifecycle
Permission
Accounting
Stock
Audit
Migration
Upgrade
ทุกครั้ง
เป้าหมายของ Developer ไม่ใช่เพียง:
Feature works
แต่ต้องเป็น:
Feature works
+
Data remains correct
+
Accounting remains correct
+
Stock remains correct
+
Audit remains correct
+
Upgrade remains possible
76. Recommended Reading Order for New Developers
ให้ Dev ใหม่อ่านตามลำดับ:
Day 1
1. Frappe / ERPNext / App / Site
2. DocType / Document / Child Table
3. Custom Field
4. Lifecycle / docstatus
Day 2
5. Hook / Service
6. Workflow
7. Permission
8. ORM
Day 3
9. Accounting basics
10. Stock basics
11. Migration / Fixture
12. Background Job / Scheduler
ก่อนแก้ระบบจริง
13. อ่าน erpnext_docker/AGENTS.md
14. อ่าน docs/PROJECT_STATUS.md
15. อ่าน docs/CODEX_HANDOFF.md
16. อ่าน Roadmap: section 0, section 3 และ current Phase เท่านั้น
17. อ่าน ERPNext_v16_Company_Codex_Final.md เฉพาะ section ที่เกี่ยวข้อง
18. อ่าน docs/DEFERRED_WORK.md section 2 และ row ที่เกี่ยวข้อง
19. อ่าน docs/ADR.md ก่อนสร้างหรือย้าย DocType
20. ถ้าเป็น GHIS task จึงค่อยอ่าน GHIS roadmap/blueprint ส่วนที่เกี่ยวข้อง
Official Reference ที่ควรเปิดคู่กัน
- Frappe Apps และ Frappe Sites
- Frappe DocTypes และ Document API
- Database API และ Background Jobs
- Purchase Invoice, Sales Invoice, Payment Entry และ Stock Reconciliation
77. New Developer Training Exercise
ก่อนให้ Dev แตะ feature จริง ให้ทำใน training site และ training app ที่แยกจาก project apps เท่านั้น ใช้ Company ตัวอย่าง TUH และข้อมูลตัวอย่างจากต้นเล่ม ห้ามทำใน UAT/Production
Exercise 1
ใช้ Developer Mode เปิดดู metadata ของ Purchase Order, Purchase Invoice และ Payment Entry แล้วอธิบายว่า DocType, fields, child table, controller และ docstatus อยู่ตรงไหน โดยยังไม่แก้อะไร
Exercise 2
สร้าง Custom DocType สำหรับการฝึกที่ไม่ชนกับ business object จริง:
Training Service Request
ให้มี Subject, Company, Request Date, Amount และ Status พร้อม permission สำหรับ Training User
ห้ามสร้าง Government Fund Source ตามคู่มือเวอร์ชันเก่า เพราะ project จริงมี DocType Government Funding Source อยู่แล้ว การสร้างชื่อใกล้กันจะทำให้ junior เข้าใจ domain ผิด
Exercise 3
เพิ่ม Custom Field custom_training_reference ให้ Training Service Request แล้ว export เป็น fixture หรือ code ตามแนวทางของ training app
Exercise 4
สร้าง server-side validation ให้ Amount มากกว่า 0 และทดสอบทั้งการ Save ผ่าน Desk และการ Insert ผ่าน Document API
Exercise 5
ย้ายกฎ Amount ไป domain/service module ของ training app โดยให้ Controller หรือ Hook เป็น thin entry point
Exercise 6
สร้าง Workflow Draft → Pending Approval → Approved/Rejected และ User Permission ให้ Training User เห็นเฉพาะ Company TUH จากนั้นทดสอบ Desk, Report และ API
Exercise 7
ลองสร้าง Purchase Invoice (ใบแจ้งหนี้ซื้อ / เอกสารตั้งหนี้เจ้าหนี้จากการซื้อ) และ Submit
จากนั้นให้เปิดดู:
GL Entry
เพื่อให้เห็นว่าการ Submit ไม่ใช่แค่เปลี่ยน status
Exercise 8
สร้าง Stock Entry (เอกสารเคลื่อนไหวสินค้าในคลัง)
แล้วดู:
Stock Ledger Entry
Bin
เพื่อเข้าใจว่า Stock ไม่ใช่ quantity field เดียว
หลักฐานที่ต้องส่งทุก Exercise
1. ชื่อ training site และ app
2. Files changed / fixtures / migration impact
3. Test case: create, save, submit (ถ้ามี), permission และ API
4. Expected result เทียบ Actual result
5. วิธีลบข้อมูลฝึกโดยไม่แตะข้อมูลจริง
78. AI / Developer Prompt Header
ใช้ข้อความนี้ก่อนสั่ง Agent:
Before coding in erpnext_docker, read AGENTS.md and follow its ordered
canonical-document list. Read only the current phase and relevant sections.
Rules:
- Treat apps-src/frappe and apps-src/erpnext as read-only.
- Prefer ERPNext Standard behavior before creating custom behavior.
- Use Custom Fields, Hooks, Services, Workflow and Permissions.
- Never directly manipulate GL Entry, Stock Ledger Entry or Bin.
- Never bypass submitted document lifecycle.
- Keep business validation server-side.
- Make all configuration reproducible through code/fixtures/migrations.
- Do only the requested phase.
- Do not start the next phase automatically.
At completion report:
- files changed
- DocTypes affected
- hooks
- fixtures
- patches
- migrations
- tests
- risks
79. Summary Cheat Sheet
| ต้องการทำอะไร | วิธีที่ Dev ปกติอาจคิด | วิธีที่ควรใช้ใน ERPNext |
|---|---|---|
| เพิ่ม field | ALTER TABLE | Custom Field |
| เปลี่ยน behavior | แก้ Core | Hook / Extension |
| เพิ่ม approval | status + if | Workflow |
| Validate | JavaScript อย่างเดียว | Server Validation |
| จ่าย Invoice | UPDATE Paid | Payment Entry (รายการรับเงิน / จ่ายเงิน) |
| แก้ Stock | UPDATE quantity | Stock Transaction |
| แก้ Submitted Invoice | UPDATE DB | Cancel / Amend |
| งานหนัก | API รอจนจบ | Background Job |
| Job ตามเวลา | cron กระจัดกระจาย | Scheduler |
| สร้างเลขเอกสาร | MAX + 1 | Naming Series หรือ controlled counter ตาม requirement |
| จำกัดข้อมูล | WHERE เองทุก API | Permission Model |
| Deploy custom field | Click PROD | Fixture / Export |
| แก้บัญชี | UPDATE GL | Standard Accounting Transaction |
80. Golden Sentence
ให้ Dev ใหม่จำประโยคนี้:
ก่อนแก้ข้อมูลใน ERPNext อย่าถามแค่ว่า Table ไหน — ให้ถามว่า Transaction หรือ Lifecycle ไหนเป็นเจ้าของข้อมูลนั้น
นี่คือความแตกต่างสำคัญระหว่างการเขียน CRUD Application กับการพัฒนา ERPNext อย่างถูกต้อง