วิธี Override Chart of Accounts ใน Frappe สำหรับภาษาไทย
เรียนรู้วิธีสร้าง Custom Chart of Accounts สำหรับบริษัทไทย พร้อมรองรับชื่อบัญชีภาษาไทย การ Import จาก CSV และการติดตั้งอัตโนมัติใน Frappe Framework

ระบบ Chart of Accounts (ผังบัญชี) ใน Frappe/ERPNext มาพร้อมกับ Template มาตรฐานหลายประเทศ แต่สำหรับบริษัทไทยที่ต้องการใช้ผังบัญชีตามมาตรฐานไทย พร้อมชื่อบัญชีภาษาไทย บทความนี้จะแนะนำวิธี Override ระบบเพื่อรองรับความต้องการเหล่านี้
ภาพรวมของระบบ
การ Override Chart of Accounts ประกอบด้วย 3 ส่วนหลัก:
- COA Template Handler - โหลด Template ผังบัญชี Custom
- Extended CSV Importer - Import ผังบัญชีจาก CSV พร้อมคอลัมน์เพิ่มเติม
- Auto-Installer - ติดตั้งผังบัญชีอัตโนมัติเมื่อสร้างบริษัท
โครงสร้างไฟล์
your_app/
├── your_app/
│ ├── accounts/
│ │ ├── chart_of_accounts/
│ │ │ ├── thai_standard.json # JSON Template
│ │ │ └── thai_standard.csv # CSV Template
│ │ └── chart_of_accounts.py # COA Handler
│ ├── overrides/
│ │ └── chart_of_accounts_importer.py # CSV Importer
│ └── setup/
│ └── coa_installer.py # Auto-Installer
└── hooks.py
1. สร้าง COA Template Handler
สร้างไฟล์ chart_of_accounts.py เพื่อ Override การโหลด Template:
import frappe
import os
import json
def get_charts_for_country(country: str, with_standard: bool = False):
"""Override เพื่อเพิ่ม Chart Templates สำหรับประเทศไทย"""
charts = []
# โหลดจาก Template Directory ของ App
template_dir = frappe.get_app_path("your_app", "accounts", "chart_of_accounts")
if os.path.exists(template_dir):
for filename in os.listdir(template_dir):
if filename.endswith('.json'):
chart_name = filename.replace('.json', '').replace('_', ' ').title()
charts.append(chart_name)
return charts
def get_chart(chart_name: str, country: str = None):
"""โหลด Chart Template ตามชื่อ"""
template_dir = frappe.get_app_path("your_app", "accounts", "chart_of_accounts")
filename = chart_name.lower().replace(' ', '_') + '.json'
filepath = os.path.join(template_dir, filename)
if os.path.exists(filepath):
with open(filepath, 'r', encoding='utf-8') as f:
return json.load(f)
return None
2. สร้าง Extended CSV Importer
CSV Importer มาตรฐานของ Frappe รองรับ 8 คอลัมน์ แต่เราต้องการเพิ่มคอลัมน์สำหรับชื่อบัญชีภาษาไทย:
import frappe
from frappe import _
import csv
class ChartOfAccountsImporter:
"""Extended COA Importer รองรับ 10 คอลัมน์"""
# คอลัมน์มาตรฐาน: 8
# คอลัมน์เพิ่มเติม: 2 (account_name_th, description_th)
EXPECTED_COLUMNS = 10
COLUMN_MAPPING = {
0: "account_name",
1: "account_name_th", # ชื่อบัญชีภาษาไทย
2: "parent_account",
3: "account_number",
4: "is_group",
5: "account_type",
6: "root_type",
7: "report_type",
8: "account_currency",
9: "description_th" # คำอธิบายภาษาไทย
}
def validate_columns(self, row):
"""ตรวจสอบจำนวนคอลัมน์"""
if len(row) < self.EXPECTED_COLUMNS:
frappe.throw(_(f"ต้องมี {self.EXPECTED_COLUMNS} คอลัมน์, พบ {len(row)}"))
def generate_data_from_csv(self, file_path):
"""Parse CSV พร้อมคอลัมน์เพิ่มเติม"""
accounts = []
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
headers = next(reader) # ข้าม Header Row
for row in reader:
if not row or not row[0].strip():
continue
self.validate_columns(row)
account = {
"account_name": row[0].strip(),
"account_name_th": row[1].strip() if len(row) > 1 else "",
"parent_account": row[2].strip() if len(row) > 2 else "",
"account_number": row[3].strip() if len(row) > 3 else "",
"is_group": int(row[4]) if len(row) > 4 and row[4] else 0,
"account_type": row[5].strip() if len(row) > 5 else "",
"root_type": row[6].strip() if len(row) > 6 else "",
"report_type": row[7].strip() if len(row) > 7 else "",
"account_currency": row[8].strip() if len(row) > 8 else "",
"description_th": row[9].strip() if len(row) > 9 else ""
}
accounts.append(account)
return accounts
def build_forest(self, accounts):
"""สร้าง Tree Structure จากข้อมูล Flat"""
by_parent = {}
for acc in accounts:
parent = acc.get("parent_account", "")
if parent not in by_parent:
by_parent[parent] = []
by_parent[parent].append(acc)
def build_tree(parent_name=""):
tree = {}
for acc in by_parent.get(parent_name, []):
name = acc["account_name"]
tree[name] = {
"account_number": acc.get("account_number"),
"account_type": acc.get("account_type"),
"root_type": acc.get("root_type"),
"report_type": acc.get("report_type"),
"is_group": acc.get("is_group"),
"account_name_th": acc.get("account_name_th"),
}
# Recursive สำหรับ Child Accounts
children = build_tree(name)
if children:
tree[name].update(children)
return tree
return build_tree()
def import_coa(self, company, chart_data):
"""Import ผังบัญชีเข้าบริษัท"""
from frappe.utils.nestedset import rebuild_tree
def create_accounts(tree, parent=None, root_type=None):
for account_name, account_data in tree.items():
# ข้าม Metadata Keys
if account_name in ["account_number", "account_type", "root_type",
"report_type", "is_group", "account_name_th"]:
continue
current_root_type = account_data.get("root_type") or root_type
account = frappe.new_doc("Account")
account.account_name = account_name
account.company = company
account.parent_account = parent
account.account_number = account_data.get("account_number")
account.account_type = account_data.get("account_type")
account.root_type = current_root_type
account.report_type = account_data.get("report_type")
account.is_group = account_data.get("is_group", 0)
# เก็บชื่อภาษาไทยใน Custom Field
if account_data.get("account_name_th"):
account.account_name_th = account_data["account_name_th"]
account.flags.ignore_permissions = True
account.insert()
# Recursive สำหรับ Child Accounts
create_accounts(account_data, account.name, current_root_type)
create_accounts(chart_data)
rebuild_tree("Account", "parent_account")
3. สร้าง Auto-Installer
สร้างไฟล์ coa_installer.py สำหรับติดตั้งอัตโนมัติ:
import frappe
import os
def install_chart_of_accounts(company_name: str, template_name: str = "thai_standard"):
"""ติดตั้งผังบัญชีอัตโนมัติ"""
# ตรวจสอบว่าบริษัทมีบัญชีอยู่แล้วหรือไม่
existing = frappe.db.count("Account", {"company": company_name})
if existing > 0:
frappe.msgprint(f"บริษัท {company_name} มี {existing} บัญชีอยู่แล้ว")
return
# โหลด Template
template_path = frappe.get_app_path(
"your_app",
"accounts",
"chart_of_accounts",
f"{template_name}.csv"
)
if not os.path.exists(template_path):
frappe.throw(f"ไม่พบ Template: {template_path}")
from your_app.overrides.chart_of_accounts_importer import ChartOfAccountsImporter
importer = ChartOfAccountsImporter()
accounts = importer.generate_data_from_csv(template_path)
chart_data = importer.build_forest(accounts)
importer.import_coa(company_name, chart_data)
frappe.db.commit()
frappe.msgprint(f"ติดตั้ง {len(accounts)} บัญชี สำหรับ {company_name} เรียบร้อย")
4. ตั้งค่า hooks.py
# Override COA Functions
override_whitelisted_methods = {
"erpnext.accounts.doctype.account.chart_of_accounts.importer.get_charts_for_country":
"your_app.accounts.chart_of_accounts.get_charts_for_country",
"erpnext.accounts.doctype.account.chart_of_accounts.importer.get_chart":
"your_app.accounts.chart_of_accounts.get_chart",
}
# ติดตั้งอัตโนมัติเมื่อสร้างบริษัท (Optional)
doc_events = {
"Company": {
"after_insert": "your_app.setup.coa_installer.install_chart_of_accounts"
}
}
5. รูปแบบ CSV Template
สร้างไฟล์ CSV 10 คอลัมน์:
account_name,account_name_th,parent_account,account_number,is_group,account_type,root_type,report_type,account_currency,description_th
Assets,สินทรัพย์,,1,1,,Asset,Balance Sheet,THB,หมวดสินทรัพย์
Current Assets,สินทรัพย์หมุนเวียน,Assets,11,1,,Asset,Balance Sheet,THB,
Cash,เงินสด,Current Assets,1101,0,Cash,Asset,Balance Sheet,THB,บัญชีเงินสด
Bank Accounts,เงินฝากธนาคาร,Current Assets,1102,1,Bank,Asset,Balance Sheet,THB,
Liabilities,หนี้สิน,,2,1,,Liability,Balance Sheet,THB,หมวดหนี้สิน
Equity,ส่วนของผู้ถือหุ้น,,3,1,,Equity,Balance Sheet,THB,หมวดทุน
Income,รายได้,,4,1,,Income,Profit and Loss,THB,หมวดรายได้
Expenses,ค่าใช้จ่าย,,5,1,,Expense,Profit and Loss,THB,หมวดค่าใช้จ่าย
สร้าง Custom Field สำหรับชื่อภาษาไทย
ใน App ของคุณ ต้องสร้าง Custom Field account_name_th บน Account DocType:
{
"doctype": "Custom Field",
"dt": "Account",
"fieldname": "account_name_th",
"fieldtype": "Data",
"label": "Account Name (Thai)",
"insert_after": "account_name"
}
การแก้ไขปัญหา
บัญชีไม่ถูก Import
- ตรวจสอบ Encoding ของไฟล์ CSV (ต้องเป็น UTF-8)
- ตรวจสอบจำนวนคอลัมน์
- Parent Account ต้องถูกสร้างก่อน Child
Tree Structure พัง
bench --site your-site.local console
from frappe.utils.nestedset import rebuild_tree
rebuild_tree("Account", "parent_account")
Custom Field ไม่ถูกบันทึก
- ตรวจสอบว่า Custom Field
account_name_thมีอยู่บน Account DocType - ตรวจสอบ Permissions
สรุป
การ Override Chart of Accounts ช่วยให้เราสามารถ:
- ใช้ผังบัญชีมาตรฐานไทย
- มีชื่อบัญชีทั้งภาษาอังกฤษและภาษาไทย
- Import จาก CSV ได้สะดวก
- ติดตั้งอัตโนมัติเมื่อสร้างบริษัท