ERPNext Docs
พร้อมอ่าน

ERPNext Developer Guideline for Beginners

คู่มือภาษาไทยสำหรับ Developer ใหม่ ก่อนพัฒนา ERPNext / Frappe / Government / GHIS

หลักคิด: ERPNext ไม่ใช่ CRUD App ทั่วไป — ก่อนแก้ข้อมูลให้ถามว่า Transaction หรือ Lifecycle ไหนเป็นเจ้าของข้อมูลนั้น

ขอบเขต: ใช้เป็นบทเรียนเริ่มต้นและคู่มืออ้างอิงสำหรับ ERPNext v16 ในโครงการนี้ ก่อนแก้ระบบจริงต้องอ่านเอกสาร canonical ตาม AGENTS.md

POPurchase Order — ใบสั่งซื้อ
PRPurchase Receipt — ใบรับสินค้า
PIPurchase Invoice — ตั้งหนี้เจ้าหนี้
PEPayment Entry — รับ/จ่ายเงิน
SOSales Order — ใบสั่งขาย
DNDelivery Note — ใบส่งสินค้า
SISales Invoice — ตั้งหนี้ลูกหนี้
JEJournal Entry — บันทึกบัญชี
ใช้เอกสารนี้สอน Dev ใหม่: แนะนำให้อ่านหัวข้อ “ศัพท์ ERP” และ “Flow ซื้อ/ขาย” ก่อน จากนั้นค่อยไป Lifecycle → Accounting → Stock → Permission → Case Lab
ข้อควรระวัง: แบบฝึกหัดต้องทำใน training site เท่านั้น ห้ามทำใน UAT หรือ Production และห้ามสร้าง DocType ที่มีอยู่แล้ว
ไม่พบหัวข้อที่ตรงกับคำค้น ลองใช้คำสั้นลง เช่น invoice, stock, hook, budget

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 และ AccountSupplier “บริษัท ไทยเมดซัพพลาย จำกัด”
Transactionเอกสารที่บันทึกเหตุการณ์ธุรกิจและผ่าน lifecyclePurchase Invoice หรือ Payment Entry
Ledgerบัญชีรายการเคลื่อนไหวที่ระบบสร้างจาก transactionGL 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
Fixtureconfiguration ที่ 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 ที่ควรเปิดคู่กัน


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 อย่างถูกต้อง