Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4785878025 | ||
|
|
2a44aed574 | ||
|
|
6a969841e5 | ||
|
|
d074657511 | ||
|
|
04735ca813 | ||
|
|
381aab2c83 |
@@ -0,0 +1,91 @@
|
||||
import frappe
|
||||
from frappe.utils.nestedset import rebuild_tree
|
||||
|
||||
from default_thai_company.tax_withholding import company_ready, get_or_create_account, thai_companies
|
||||
|
||||
# Asset Category -> fixed asset account. Names follow ERPNext's Standard chart so
|
||||
# setup-wizard companies reuse their accounts; the rest are created under the
|
||||
# same "Fixed Assets" group. Depreciation accounts are left to the Company
|
||||
# defaults (Asset falls back to them), so one change there covers every category.
|
||||
FIXED_ASSET_ACCOUNTS = {
|
||||
"Land": "Land",
|
||||
"Buildings": "Buildings",
|
||||
"Plant and Machinery": "Plants and Machineries",
|
||||
"Vehicles": "Vehicles",
|
||||
"Furniture and Fixtures": "Furnitures and Fixtures",
|
||||
"Office Equipment": "Office Equipments",
|
||||
"Computers and Electronics": "Electronic Equipments",
|
||||
"Software": "Softwares",
|
||||
"Intangible Assets": "Intangible Assets",
|
||||
}
|
||||
|
||||
FIXED_ASSET_GROUPS = ("Fixed Assets",)
|
||||
|
||||
|
||||
def fixed_asset_account(company, category):
|
||||
return get_or_create_account(
|
||||
company, FIXED_ASSET_ACCOUNTS[category], "Asset", "Fixed Asset", FIXED_ASSET_GROUPS
|
||||
)
|
||||
|
||||
|
||||
def link_company(category, company):
|
||||
"""Append the company's fixed asset account row to the category if missing."""
|
||||
if any(row.company_name == company for row in category.accounts):
|
||||
return False
|
||||
category.append(
|
||||
"accounts",
|
||||
{"company_name": company, "fixed_asset_account": fixed_asset_account(company, category.name)},
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def prepare_fixture_accounts(doc, method=None):
|
||||
"""Asset Category fixtures ship without `accounts` (company-specific).
|
||||
|
||||
Same contract as the Tax Withholding Category fixtures: fixture import
|
||||
re-inserts the doc on every migrate, so carry over the rows already on this
|
||||
site, then link every Thai company. Mandatory is relaxed for sites without a
|
||||
Thai company yet; `setup_company` links them later.
|
||||
"""
|
||||
if doc.name not in FIXED_ASSET_ACCOUNTS:
|
||||
return
|
||||
|
||||
doc.flags.ignore_mandatory = True
|
||||
|
||||
if not doc.accounts and frappe.db.exists(doc.doctype, doc.name):
|
||||
for row in frappe.get_all(
|
||||
"Asset Category Account",
|
||||
filters={"parent": doc.name, "parenttype": doc.doctype},
|
||||
fields=[
|
||||
"company_name",
|
||||
"fixed_asset_account",
|
||||
"accumulated_depreciation_account",
|
||||
"depreciation_expense_account",
|
||||
"capital_work_in_progress_account",
|
||||
],
|
||||
order_by="idx",
|
||||
):
|
||||
doc.append("accounts", row)
|
||||
|
||||
for company in thai_companies():
|
||||
link_company(doc, company)
|
||||
|
||||
|
||||
def setup_company(doc, method=None):
|
||||
"""Company.on_update: link a Thai company on every default Asset Category."""
|
||||
if not company_ready(doc):
|
||||
return
|
||||
|
||||
for name in FIXED_ASSET_ACCOUNTS:
|
||||
if not frappe.db.exists("Asset Category", name):
|
||||
continue
|
||||
category = frappe.get_doc("Asset Category", name)
|
||||
if link_company(category, doc.name):
|
||||
category.save(ignore_permissions=True)
|
||||
|
||||
|
||||
def rebuild_locations():
|
||||
"""after_migrate: fixture import re-inserts the Location tree nodes (fresh
|
||||
lft/rgt), which strands locations users added beneath them; rebuild."""
|
||||
if frappe.db.exists("Location", "All Locations"):
|
||||
rebuild_tree("Location")
|
||||
@@ -0,0 +1,237 @@
|
||||
[
|
||||
{
|
||||
"accounts": [],
|
||||
"asset_category_name": "Land",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Category",
|
||||
"enable_cwip_accounting": 0,
|
||||
"finance_books": [],
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Land",
|
||||
"non_depreciable_category": 1
|
||||
},
|
||||
{
|
||||
"accounts": [],
|
||||
"asset_category_name": "Buildings",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Category",
|
||||
"enable_cwip_accounting": 0,
|
||||
"finance_books": [
|
||||
{
|
||||
"daily_prorata_based": 1,
|
||||
"depreciation_method": "Straight Line",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Finance Book",
|
||||
"finance_book": null,
|
||||
"frequency_of_depreciation": 1,
|
||||
"idx": 1,
|
||||
"parent": "Buildings",
|
||||
"parentfield": "finance_books",
|
||||
"parenttype": "Asset Category",
|
||||
"rate_of_depreciation": 0.0,
|
||||
"salvage_value_percentage": 0.0,
|
||||
"shift_based": 0,
|
||||
"total_number_of_depreciations": 240
|
||||
}
|
||||
],
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Buildings",
|
||||
"non_depreciable_category": 0
|
||||
},
|
||||
{
|
||||
"accounts": [],
|
||||
"asset_category_name": "Plant and Machinery",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Category",
|
||||
"enable_cwip_accounting": 0,
|
||||
"finance_books": [
|
||||
{
|
||||
"daily_prorata_based": 1,
|
||||
"depreciation_method": "Straight Line",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Finance Book",
|
||||
"finance_book": null,
|
||||
"frequency_of_depreciation": 1,
|
||||
"idx": 1,
|
||||
"parent": "Plant and Machinery",
|
||||
"parentfield": "finance_books",
|
||||
"parenttype": "Asset Category",
|
||||
"rate_of_depreciation": 0.0,
|
||||
"salvage_value_percentage": 0.0,
|
||||
"shift_based": 0,
|
||||
"total_number_of_depreciations": 60
|
||||
}
|
||||
],
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Plant and Machinery",
|
||||
"non_depreciable_category": 0
|
||||
},
|
||||
{
|
||||
"accounts": [],
|
||||
"asset_category_name": "Vehicles",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Category",
|
||||
"enable_cwip_accounting": 0,
|
||||
"finance_books": [
|
||||
{
|
||||
"daily_prorata_based": 1,
|
||||
"depreciation_method": "Straight Line",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Finance Book",
|
||||
"finance_book": null,
|
||||
"frequency_of_depreciation": 1,
|
||||
"idx": 1,
|
||||
"parent": "Vehicles",
|
||||
"parentfield": "finance_books",
|
||||
"parenttype": "Asset Category",
|
||||
"rate_of_depreciation": 0.0,
|
||||
"salvage_value_percentage": 0.0,
|
||||
"shift_based": 0,
|
||||
"total_number_of_depreciations": 60
|
||||
}
|
||||
],
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Vehicles",
|
||||
"non_depreciable_category": 0
|
||||
},
|
||||
{
|
||||
"accounts": [],
|
||||
"asset_category_name": "Furniture and Fixtures",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Category",
|
||||
"enable_cwip_accounting": 0,
|
||||
"finance_books": [
|
||||
{
|
||||
"daily_prorata_based": 1,
|
||||
"depreciation_method": "Straight Line",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Finance Book",
|
||||
"finance_book": null,
|
||||
"frequency_of_depreciation": 1,
|
||||
"idx": 1,
|
||||
"parent": "Furniture and Fixtures",
|
||||
"parentfield": "finance_books",
|
||||
"parenttype": "Asset Category",
|
||||
"rate_of_depreciation": 0.0,
|
||||
"salvage_value_percentage": 0.0,
|
||||
"shift_based": 0,
|
||||
"total_number_of_depreciations": 60
|
||||
}
|
||||
],
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Furniture and Fixtures",
|
||||
"non_depreciable_category": 0
|
||||
},
|
||||
{
|
||||
"accounts": [],
|
||||
"asset_category_name": "Office Equipment",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Category",
|
||||
"enable_cwip_accounting": 0,
|
||||
"finance_books": [
|
||||
{
|
||||
"daily_prorata_based": 1,
|
||||
"depreciation_method": "Straight Line",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Finance Book",
|
||||
"finance_book": null,
|
||||
"frequency_of_depreciation": 1,
|
||||
"idx": 1,
|
||||
"parent": "Office Equipment",
|
||||
"parentfield": "finance_books",
|
||||
"parenttype": "Asset Category",
|
||||
"rate_of_depreciation": 0.0,
|
||||
"salvage_value_percentage": 0.0,
|
||||
"shift_based": 0,
|
||||
"total_number_of_depreciations": 60
|
||||
}
|
||||
],
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Office Equipment",
|
||||
"non_depreciable_category": 0
|
||||
},
|
||||
{
|
||||
"accounts": [],
|
||||
"asset_category_name": "Computers and Electronics",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Category",
|
||||
"enable_cwip_accounting": 0,
|
||||
"finance_books": [
|
||||
{
|
||||
"daily_prorata_based": 1,
|
||||
"depreciation_method": "Straight Line",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Finance Book",
|
||||
"finance_book": null,
|
||||
"frequency_of_depreciation": 1,
|
||||
"idx": 1,
|
||||
"parent": "Computers and Electronics",
|
||||
"parentfield": "finance_books",
|
||||
"parenttype": "Asset Category",
|
||||
"rate_of_depreciation": 0.0,
|
||||
"salvage_value_percentage": 0.0,
|
||||
"shift_based": 0,
|
||||
"total_number_of_depreciations": 60
|
||||
}
|
||||
],
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Computers and Electronics",
|
||||
"non_depreciable_category": 0
|
||||
},
|
||||
{
|
||||
"accounts": [],
|
||||
"asset_category_name": "Software",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Category",
|
||||
"enable_cwip_accounting": 0,
|
||||
"finance_books": [
|
||||
{
|
||||
"daily_prorata_based": 1,
|
||||
"depreciation_method": "Straight Line",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Finance Book",
|
||||
"finance_book": null,
|
||||
"frequency_of_depreciation": 1,
|
||||
"idx": 1,
|
||||
"parent": "Software",
|
||||
"parentfield": "finance_books",
|
||||
"parenttype": "Asset Category",
|
||||
"rate_of_depreciation": 0.0,
|
||||
"salvage_value_percentage": 0.0,
|
||||
"shift_based": 0,
|
||||
"total_number_of_depreciations": 60
|
||||
}
|
||||
],
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Software",
|
||||
"non_depreciable_category": 0
|
||||
},
|
||||
{
|
||||
"accounts": [],
|
||||
"asset_category_name": "Intangible Assets",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Category",
|
||||
"enable_cwip_accounting": 0,
|
||||
"finance_books": [
|
||||
{
|
||||
"daily_prorata_based": 1,
|
||||
"depreciation_method": "Straight Line",
|
||||
"docstatus": 0,
|
||||
"doctype": "Asset Finance Book",
|
||||
"finance_book": null,
|
||||
"frequency_of_depreciation": 1,
|
||||
"idx": 1,
|
||||
"parent": "Intangible Assets",
|
||||
"parentfield": "finance_books",
|
||||
"parenttype": "Asset Category",
|
||||
"rate_of_depreciation": 0.0,
|
||||
"salvage_value_percentage": 0.0,
|
||||
"shift_based": 0,
|
||||
"total_number_of_depreciations": 120
|
||||
}
|
||||
],
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Intangible Assets",
|
||||
"non_depreciable_category": 0
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,106 @@
|
||||
[
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 270000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Personal and Family Allowances"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 0.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Child and Dependant Allowances"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 100000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Life and Health Insurance"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 25000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Family Insurance"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 500000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Retirement Savings and Investments"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 300000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Thai ESG Fund"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 600000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Thai ESGX Fund"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 10500.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Social Security Fund"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 100000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Housing"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 0.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Donations"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 10000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Political Party Donation"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 190000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Income Exemptions"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Category",
|
||||
"is_active": 1,
|
||||
"max_amount": 250000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Government Stimulus Measures"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,299 @@
|
||||
[
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Personal and Family Allowances",
|
||||
"is_active": 1,
|
||||
"max_amount": 60000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Spouse allowance (spouse without income)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Personal and Family Allowances",
|
||||
"is_active": 1,
|
||||
"max_amount": 90000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Adopted child allowance (30,000 per child, max 3)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Personal and Family Allowances",
|
||||
"is_active": 1,
|
||||
"max_amount": 60000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Parent allowance (30,000 per parent, max 2)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Personal and Family Allowances",
|
||||
"is_active": 1,
|
||||
"max_amount": 60000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Spouse's parent allowance (30,000 per parent, max 2)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Child and Dependant Allowances",
|
||||
"is_active": 1,
|
||||
"max_amount": 0.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Child allowance (30,000 per child)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Child and Dependant Allowances",
|
||||
"is_active": 1,
|
||||
"max_amount": 0.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Child allowance - 2nd child onward born 2018 or later (60,000 per child)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Child and Dependant Allowances",
|
||||
"is_active": 1,
|
||||
"max_amount": 0.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Disabled or incapacitated dependant care (60,000 per person)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Child and Dependant Allowances",
|
||||
"is_active": 1,
|
||||
"max_amount": 0.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Prenatal care and childbirth expenses (60,000 per pregnancy)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Life and Health Insurance",
|
||||
"is_active": 1,
|
||||
"max_amount": 100000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Life insurance premium (policy term 10 years or more)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Life and Health Insurance",
|
||||
"is_active": 1,
|
||||
"max_amount": 25000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Health insurance premium (self)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Family Insurance",
|
||||
"is_active": 1,
|
||||
"max_amount": 10000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Spouse life insurance premium (spouse without income)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Family Insurance",
|
||||
"is_active": 1,
|
||||
"max_amount": 15000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Parents' health insurance premium"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Retirement Savings and Investments",
|
||||
"is_active": 1,
|
||||
"max_amount": 500000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Provident fund contribution (15% of wages)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Retirement Savings and Investments",
|
||||
"is_active": 1,
|
||||
"max_amount": 500000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Government Pension Fund contribution (30% of income)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Retirement Savings and Investments",
|
||||
"is_active": 1,
|
||||
"max_amount": 500000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Private Teacher Aid Fund contribution (15% of income)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Retirement Savings and Investments",
|
||||
"is_active": 1,
|
||||
"max_amount": 500000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Retirement Mutual Fund - RMF (30% of income)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Retirement Savings and Investments",
|
||||
"is_active": 1,
|
||||
"max_amount": 200000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Pension life insurance premium (15% of income)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Retirement Savings and Investments",
|
||||
"is_active": 1,
|
||||
"max_amount": 30000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "National Savings Fund contribution"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Retirement Savings and Investments",
|
||||
"is_active": 0,
|
||||
"max_amount": 200000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Super Savings Fund - SSF (30% of income, tax years 2020-2024)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Thai ESG Fund",
|
||||
"is_active": 1,
|
||||
"max_amount": 300000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Thai ESG fund (30% of income)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Thai ESGX Fund",
|
||||
"is_active": 1,
|
||||
"max_amount": 300000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Thai ESGX fund - new units (30% of income)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Thai ESGX Fund",
|
||||
"is_active": 1,
|
||||
"max_amount": 300000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Thai ESGX fund - LTF switch (500,000 spread over tax years 2025-2029)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Social Security Fund",
|
||||
"is_active": 1,
|
||||
"max_amount": 10500.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Social Security contribution - Sec. 33 employee"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Social Security Fund",
|
||||
"is_active": 1,
|
||||
"max_amount": 5184.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Social Security contribution - Sec. 39 voluntary"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Social Security Fund",
|
||||
"is_active": 1,
|
||||
"max_amount": 3600.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Social Security contribution - Sec. 40 informal worker"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Housing",
|
||||
"is_active": 1,
|
||||
"max_amount": 100000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Home loan interest"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Donations",
|
||||
"is_active": 1,
|
||||
"max_amount": 0.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "General donations (10% of income after allowances)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Donations",
|
||||
"is_active": 1,
|
||||
"max_amount": 0.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Education, sports, hospital and social development donations (2x, 10% of income after allowances)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Political Party Donation",
|
||||
"is_active": 1,
|
||||
"max_amount": 10000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Political party donation"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Income Exemptions",
|
||||
"is_active": 1,
|
||||
"max_amount": 190000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Taxpayer aged 65 or over - income exemption"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Income Exemptions",
|
||||
"is_active": 1,
|
||||
"max_amount": 190000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Disabled taxpayer under 65 - income exemption"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Government Stimulus Measures",
|
||||
"is_active": 1,
|
||||
"max_amount": 50000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Easy E-Receipt (e-Tax Invoice / e-Receipt purchases)"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Employee Tax Exemption Sub Category",
|
||||
"exemption_category": "Government Stimulus Measures",
|
||||
"is_active": 1,
|
||||
"max_amount": 200000.0,
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Solar rooftop installation (tax years 2025-2027)"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,113 @@
|
||||
[
|
||||
{
|
||||
"allow_tax_exemption": 1,
|
||||
"currency": "THB",
|
||||
"disabled": 0,
|
||||
"docstatus": 1,
|
||||
"doctype": "Income Tax Slab",
|
||||
"effective_from": "2017-01-01",
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Thailand Personal Income Tax",
|
||||
"other_taxes_and_charges": [],
|
||||
"slabs": [
|
||||
{
|
||||
"condition": "",
|
||||
"docstatus": 1,
|
||||
"doctype": "Taxable Salary Slab",
|
||||
"from_amount": 0.0,
|
||||
"idx": 1,
|
||||
"parent": "Thailand Personal Income Tax",
|
||||
"parentfield": "slabs",
|
||||
"parenttype": "Income Tax Slab",
|
||||
"percent_deduction": 0.0,
|
||||
"to_amount": 150000.0
|
||||
},
|
||||
{
|
||||
"condition": "",
|
||||
"docstatus": 1,
|
||||
"doctype": "Taxable Salary Slab",
|
||||
"from_amount": 150001.0,
|
||||
"idx": 2,
|
||||
"parent": "Thailand Personal Income Tax",
|
||||
"parentfield": "slabs",
|
||||
"parenttype": "Income Tax Slab",
|
||||
"percent_deduction": 5.0,
|
||||
"to_amount": 300000.0
|
||||
},
|
||||
{
|
||||
"condition": "",
|
||||
"docstatus": 1,
|
||||
"doctype": "Taxable Salary Slab",
|
||||
"from_amount": 300001.0,
|
||||
"idx": 3,
|
||||
"parent": "Thailand Personal Income Tax",
|
||||
"parentfield": "slabs",
|
||||
"parenttype": "Income Tax Slab",
|
||||
"percent_deduction": 10.0,
|
||||
"to_amount": 500000.0
|
||||
},
|
||||
{
|
||||
"condition": "",
|
||||
"docstatus": 1,
|
||||
"doctype": "Taxable Salary Slab",
|
||||
"from_amount": 500001.0,
|
||||
"idx": 4,
|
||||
"parent": "Thailand Personal Income Tax",
|
||||
"parentfield": "slabs",
|
||||
"parenttype": "Income Tax Slab",
|
||||
"percent_deduction": 15.0,
|
||||
"to_amount": 750000.0
|
||||
},
|
||||
{
|
||||
"condition": "",
|
||||
"docstatus": 1,
|
||||
"doctype": "Taxable Salary Slab",
|
||||
"from_amount": 750001.0,
|
||||
"idx": 5,
|
||||
"parent": "Thailand Personal Income Tax",
|
||||
"parentfield": "slabs",
|
||||
"parenttype": "Income Tax Slab",
|
||||
"percent_deduction": 20.0,
|
||||
"to_amount": 1000000.0
|
||||
},
|
||||
{
|
||||
"condition": "",
|
||||
"docstatus": 1,
|
||||
"doctype": "Taxable Salary Slab",
|
||||
"from_amount": 1000001.0,
|
||||
"idx": 6,
|
||||
"parent": "Thailand Personal Income Tax",
|
||||
"parentfield": "slabs",
|
||||
"parenttype": "Income Tax Slab",
|
||||
"percent_deduction": 25.0,
|
||||
"to_amount": 2000000.0
|
||||
},
|
||||
{
|
||||
"condition": "",
|
||||
"docstatus": 1,
|
||||
"doctype": "Taxable Salary Slab",
|
||||
"from_amount": 2000001.0,
|
||||
"idx": 7,
|
||||
"parent": "Thailand Personal Income Tax",
|
||||
"parentfield": "slabs",
|
||||
"parenttype": "Income Tax Slab",
|
||||
"percent_deduction": 30.0,
|
||||
"to_amount": 5000000.0
|
||||
},
|
||||
{
|
||||
"condition": "",
|
||||
"docstatus": 1,
|
||||
"doctype": "Taxable Salary Slab",
|
||||
"from_amount": 5000001.0,
|
||||
"idx": 8,
|
||||
"parent": "Thailand Personal Income Tax",
|
||||
"parentfield": "slabs",
|
||||
"parenttype": "Income Tax Slab",
|
||||
"percent_deduction": 35.0,
|
||||
"to_amount": 0.0
|
||||
}
|
||||
],
|
||||
"standard_tax_exemption_amount": 160000.0,
|
||||
"tax_relief_limit": 0.0
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Location",
|
||||
"is_container": 0,
|
||||
"is_group": 1,
|
||||
"location_name": "All Locations",
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "All Locations",
|
||||
"parent_location": null
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Location",
|
||||
"is_container": 0,
|
||||
"is_group": 0,
|
||||
"location_name": "Head Office",
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Head Office",
|
||||
"parent_location": "All Locations"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Location",
|
||||
"is_container": 0,
|
||||
"is_group": 0,
|
||||
"location_name": "Branch Office",
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Branch Office",
|
||||
"parent_location": "All Locations"
|
||||
},
|
||||
{
|
||||
"docstatus": 0,
|
||||
"doctype": "Location",
|
||||
"is_container": 0,
|
||||
"is_group": 0,
|
||||
"location_name": "Warehouse",
|
||||
"modified": "2026-09-16 09:00:00.000000",
|
||||
"name": "Warehouse",
|
||||
"parent_location": "All Locations"
|
||||
}
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
@@ -93,17 +93,56 @@ fixtures = [
|
||||
{
|
||||
"doctype": "Print Format",
|
||||
"filters": [
|
||||
["name", "in", ["Default Standard Sales Invoice", "Default Standard Tax Invoice/Receipt"]]
|
||||
[
|
||||
"name",
|
||||
"in",
|
||||
[
|
||||
"Default Standard Sales Invoice",
|
||||
"Default Standard Tax Invoice/Receipt",
|
||||
"Default Standard Quotation",
|
||||
],
|
||||
]
|
||||
],
|
||||
},
|
||||
{"doctype": "Tax Withholding Category", "filters": [["name", "like", "WHT %"]]},
|
||||
{
|
||||
"doctype": "Location",
|
||||
"filters": [["name", "in", ["All Locations", "Head Office", "Branch Office", "Warehouse"]]],
|
||||
},
|
||||
{
|
||||
"doctype": "Asset Category",
|
||||
"filters": [
|
||||
[
|
||||
"name",
|
||||
"in",
|
||||
[
|
||||
"Land",
|
||||
"Buildings",
|
||||
"Plant and Machinery",
|
||||
"Vehicles",
|
||||
"Furniture and Fixtures",
|
||||
"Office Equipment",
|
||||
"Computers and Electronics",
|
||||
"Software",
|
||||
"Intangible Assets",
|
||||
],
|
||||
]
|
||||
],
|
||||
},
|
||||
# HRMS doctypes; skipped by sync_fixtures on sites without hrms
|
||||
{"doctype": "Employee Tax Exemption Category"},
|
||||
{"doctype": "Employee Tax Exemption Sub Category"},
|
||||
{"doctype": "Income Tax Slab", "filters": [["name", "=", "Thailand Personal Income Tax"]]},
|
||||
]
|
||||
|
||||
# Installation
|
||||
# ------------
|
||||
|
||||
# before_install = "default_thai_company.install.before_install"
|
||||
# after_install = "default_thai_company.install.after_install"
|
||||
# Fixtures are synced before this runs; VAT schemes for companies that already exist.
|
||||
after_install = "default_thai_company.vat.setup_companies"
|
||||
# Fixture import re-inserts the Location tree; restore lft/rgt for user-added nodes.
|
||||
after_migrate = "default_thai_company.assets.rebuild_locations"
|
||||
|
||||
# Uninstallation
|
||||
# ------------
|
||||
@@ -166,8 +205,15 @@ doc_events = {
|
||||
"Tax Withholding Category": {
|
||||
"before_import": "default_thai_company.tax_withholding.prepare_fixture_accounts",
|
||||
},
|
||||
"Asset Category": {
|
||||
"before_import": "default_thai_company.assets.prepare_fixture_accounts",
|
||||
},
|
||||
"Company": {
|
||||
"on_update": "default_thai_company.tax_withholding.setup_company",
|
||||
"on_update": [
|
||||
"default_thai_company.tax_withholding.setup_company",
|
||||
"default_thai_company.vat.setup_company",
|
||||
"default_thai_company.assets.setup_company",
|
||||
],
|
||||
},
|
||||
"Payment Entry": {
|
||||
"validate": "default_thai_company.tax_withholding.set_customer_withholding",
|
||||
|
||||
@@ -3,4 +3,6 @@
|
||||
# Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations
|
||||
|
||||
[post_model_sync]
|
||||
# Patches added in this section will be executed after doctypes are migrated
|
||||
# Patches added in this section will be executed after doctypes are migrated
|
||||
default_thai_company.patches.create_vat_accounts
|
||||
default_thai_company.patches.create_vat_templates
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from default_thai_company.tax_withholding import ensure_company_accounts, thai_companies
|
||||
|
||||
|
||||
def execute():
|
||||
"""Output VAT / Input VAT were added to COMPANY_ACCOUNTS; Company.on_update
|
||||
only runs on save, so give existing Thai companies the pair."""
|
||||
for company in thai_companies():
|
||||
ensure_company_accounts(company)
|
||||
@@ -0,0 +1,5 @@
|
||||
from default_thai_company.vat import setup_companies
|
||||
|
||||
|
||||
def execute():
|
||||
setup_companies()
|
||||
@@ -12,10 +12,23 @@ from frappe.utils import cint, flt
|
||||
|
||||
from default_thai_company.utils import money_in_words
|
||||
|
||||
# Liability: tax we withhold from suppliers and remit on P.N.D.3/53 (linked on every category).
|
||||
# Asset: tax customers withhold from us, creditable against corporate income tax.
|
||||
# Tax accounts every Thai company gets (Company.on_update, fixture import, and the
|
||||
# create_vat_accounts patch for companies that predate the VAT pair).
|
||||
# Withholding: liability = tax we withhold from suppliers, remitted on P.N.D.3/53
|
||||
# (linked on every category); asset = tax customers withhold from us, creditable
|
||||
# against corporate income tax.
|
||||
# VAT: output (ภาษีขาย) collected on sales is payable, input (ภาษีซื้อ) paid on
|
||||
# purchases is recoverable; the P.P.30 return nets the two, so they are kept apart.
|
||||
LIABILITY_ACCOUNT = "Withholding Tax Payable"
|
||||
ASSET_ACCOUNT = "Withholding Tax Receivable"
|
||||
OUTPUT_VAT_ACCOUNT = "Output VAT"
|
||||
INPUT_VAT_ACCOUNT = "Input VAT"
|
||||
COMPANY_ACCOUNTS = (
|
||||
(LIABILITY_ACCOUNT, "Liability"),
|
||||
(ASSET_ACCOUNT, "Asset"),
|
||||
(OUTPUT_VAT_ACCOUNT, "Liability"),
|
||||
(INPUT_VAT_ACCOUNT, "Asset"),
|
||||
)
|
||||
|
||||
# Preferred parent groups by root type; first match wins, else the root account.
|
||||
PARENT_GROUPS = {
|
||||
@@ -30,7 +43,9 @@ def thai_companies():
|
||||
return frappe.get_all("Company", filters={"country": "Thailand"}, pluck="name")
|
||||
|
||||
|
||||
def get_or_create_account(company, account_name, root_type):
|
||||
def get_or_create_account(company, account_name, root_type, account_type="Tax", parent_groups=None):
|
||||
"""Leaf account `account_name` for `company`, created under the first existing
|
||||
group in `parent_groups` (default: PARENT_GROUPS[root_type]), else the root."""
|
||||
name = frappe.db.get_value(
|
||||
"Account", {"company": company, "account_name": account_name, "is_group": 0, "root_type": root_type}
|
||||
)
|
||||
@@ -42,17 +57,19 @@ def get_or_create_account(company, account_name, root_type):
|
||||
"doctype": "Account",
|
||||
"company": company,
|
||||
"account_name": account_name,
|
||||
"parent_account": find_parent_group(company, root_type),
|
||||
"parent_account": find_parent_group(
|
||||
company, root_type, parent_groups or PARENT_GROUPS[root_type]
|
||||
),
|
||||
"root_type": root_type,
|
||||
"account_type": "Tax",
|
||||
"account_type": account_type,
|
||||
"is_group": 0,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
return account.name
|
||||
|
||||
|
||||
def find_parent_group(company, root_type):
|
||||
for account_name in PARENT_GROUPS[root_type]:
|
||||
def find_parent_group(company, root_type, group_names):
|
||||
for account_name in group_names:
|
||||
parent = frappe.db.get_value(
|
||||
"Account",
|
||||
{"company": company, "account_name": account_name, "is_group": 1, "root_type": root_type},
|
||||
@@ -67,9 +84,10 @@ def find_parent_group(company, root_type):
|
||||
|
||||
|
||||
def ensure_company_accounts(company):
|
||||
"""Create both withholding accounts for `company`; return the liability account name."""
|
||||
get_or_create_account(company, ASSET_ACCOUNT, "Asset")
|
||||
return get_or_create_account(company, LIABILITY_ACCOUNT, "Liability")
|
||||
"""Create every account in COMPANY_ACCOUNTS for `company`; return the
|
||||
withholding liability account name (the one Tax Withholding Categories link)."""
|
||||
accounts = {name: get_or_create_account(company, name, root_type) for name, root_type in COMPANY_ACCOUNTS}
|
||||
return accounts[LIABILITY_ACCOUNT]
|
||||
|
||||
|
||||
def link_company(category, company, account):
|
||||
@@ -104,9 +122,14 @@ def prepare_fixture_accounts(doc, method=None):
|
||||
link_company(doc, company, ensure_company_accounts(company))
|
||||
|
||||
|
||||
def company_ready(doc):
|
||||
"""A Thai company whose chart of accounts exists."""
|
||||
return doc.country == "Thailand" and frappe.db.exists("Account", {"company": doc.name})
|
||||
|
||||
|
||||
def setup_company(doc, method=None):
|
||||
"""Company.on_update: create withholding accounts and link every WHT category."""
|
||||
if doc.country != "Thailand" or not frappe.db.exists("Account", {"company": doc.name}):
|
||||
"""Company.on_update: create the tax accounts and link every WHT category."""
|
||||
if not company_ready(doc):
|
||||
return
|
||||
|
||||
account = ensure_company_accounts(doc.name)
|
||||
|
||||
@@ -8,7 +8,9 @@ from frappe.utils import nowdate
|
||||
|
||||
from default_thai_company.tax_withholding import (
|
||||
ASSET_ACCOUNT,
|
||||
INPUT_VAT_ACCOUNT,
|
||||
LIABILITY_ACCOUNT,
|
||||
OUTPUT_VAT_ACCOUNT,
|
||||
get_payment_entry,
|
||||
thai_companies,
|
||||
)
|
||||
@@ -44,16 +46,7 @@ class TestTaxWithholding(FrappeTestCase):
|
||||
).insert()
|
||||
cls.payable = f"{LIABILITY_ACCOUNT} - {ABBR}"
|
||||
cls.receivable = f"{ASSET_ACCOUNT} - {ABBR}"
|
||||
|
||||
cls.vat = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"company": COMPANY,
|
||||
"account_name": "Output VAT",
|
||||
"parent_account": f"Duties and Taxes - {ABBR}",
|
||||
"account_type": "Tax",
|
||||
}
|
||||
).insert()
|
||||
cls.vat = frappe.get_doc("Account", f"{OUTPUT_VAT_ACCOUNT} - {ABBR}")
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
@@ -137,14 +130,17 @@ class TestTaxWithholding(FrappeTestCase):
|
||||
|
||||
def test_company_creation_adds_accounts_and_links_categories(self):
|
||||
self.assertIn(COMPANY, thai_companies())
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Account", self.payable, ["root_type", "parent_account"]),
|
||||
("Liability", f"Duties and Taxes - {ABBR}"),
|
||||
)
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Account", self.receivable, ["root_type", "parent_account"]),
|
||||
("Asset", f"Tax Assets - {ABBR}"),
|
||||
)
|
||||
for account, expected in (
|
||||
(self.payable, ("Liability", f"Duties and Taxes - {ABBR}", "Tax")),
|
||||
(self.receivable, ("Asset", f"Tax Assets - {ABBR}", "Tax")),
|
||||
(f"{OUTPUT_VAT_ACCOUNT} - {ABBR}", ("Liability", f"Duties and Taxes - {ABBR}", "Tax")),
|
||||
(f"{INPUT_VAT_ACCOUNT} - {ABBR}", ("Asset", f"Tax Assets - {ABBR}", "Tax")),
|
||||
):
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Account", account, ["root_type", "parent_account", "account_type"]),
|
||||
expected,
|
||||
account,
|
||||
)
|
||||
|
||||
categories = frappe.get_all(
|
||||
"Tax Withholding Category", filters={"name": ("like", "WHT %")}, pluck="name"
|
||||
@@ -153,6 +149,34 @@ class TestTaxWithholding(FrappeTestCase):
|
||||
for name in categories:
|
||||
self.assertEqual(self.category_account(name), self.payable, name)
|
||||
|
||||
def test_company_creation_adds_vat_schemes(self):
|
||||
for doctype, account in (
|
||||
("Sales Taxes and Charges Template", f"{OUTPUT_VAT_ACCOUNT} - {ABBR}"),
|
||||
("Purchase Taxes and Charges Template", f"{INPUT_VAT_ACCOUNT} - {ABBR}"),
|
||||
):
|
||||
schemes = {}
|
||||
for name in frappe.get_all(
|
||||
doctype, filters={"company": COMPANY, "title": ("like", "Thailand VAT%")}, pluck="name"
|
||||
):
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
(row,) = doc.taxes
|
||||
schemes[doc.title] = (
|
||||
doc.is_default,
|
||||
row.account_head,
|
||||
row.charge_type,
|
||||
row.rate,
|
||||
row.included_in_print_rate,
|
||||
)
|
||||
self.assertEqual(
|
||||
schemes,
|
||||
{
|
||||
"Thailand VAT 7%": (1, account, "On Net Total", 7.0, 0),
|
||||
"Thailand VAT 7% (Included)": (0, account, "On Net Total", 7.0, 1),
|
||||
"Thailand VAT 0%": (0, account, "On Net Total", 0.0, 0),
|
||||
},
|
||||
doctype,
|
||||
)
|
||||
|
||||
def test_fixture_reimport_keeps_site_account_and_relinks(self):
|
||||
alt = frappe.get_doc(
|
||||
{
|
||||
|
||||
@@ -11,6 +11,8 @@ Signatures,ลายเซ็น
|
||||
On behalf of,ในนาม
|
||||
Receiver,ผู้รับ
|
||||
Issuer,ผู้ออก
|
||||
Accepted By,ผู้อนุมัติสั่งซื้อ
|
||||
Quoted By,ผู้เสนอราคา
|
||||
Tax Invoice,ใบกำกับภาษี
|
||||
Tax Invoice/Receipt,ใบกำกับภาษี/ใบเสร็จรับเงิน
|
||||
"Commission, brokerage, agency fees - Sec. 40(2), juristic payee (P.N.D.53)",ค่านายหน้า ค่าตัวแทน - มาตรา 40(2) ผู้รับเป็นนิติบุคคล (ภ.ง.ด.53)
|
||||
@@ -53,3 +55,7 @@ Grand Total,ยอดรวมทั้งสิ้น
|
||||
Rounded Total,ยอดรวมปัดเศษ
|
||||
Terms and Conditions Details,รายละเอียดข้อตกลงและเงื่อนไข
|
||||
Page {0} of {1},หน้า {0} จาก {1}
|
||||
Withholding Tax Payable,ภาษีหัก ณ ที่จ่ายค้างจ่าย
|
||||
Withholding Tax Receivable,ภาษีถูกหัก ณ ที่จ่าย
|
||||
Output VAT,ภาษีขาย
|
||||
Input VAT,ภาษีซื้อ
|
||||
|
||||
|
@@ -0,0 +1,78 @@
|
||||
import erpnext
|
||||
import frappe
|
||||
|
||||
from default_thai_company.tax_withholding import (
|
||||
INPUT_VAT_ACCOUNT,
|
||||
OUTPUT_VAT_ACCOUNT,
|
||||
company_ready,
|
||||
get_or_create_account,
|
||||
thai_companies,
|
||||
)
|
||||
|
||||
RATE = 7.0
|
||||
|
||||
# title, rate, included_in_print_rate. Exempt supplies take no template.
|
||||
SCHEMES = (
|
||||
("Thailand VAT 7%", RATE, 0),
|
||||
("Thailand VAT 7% (Included)", RATE, 1), # VAT-inclusive prices, the retail norm
|
||||
("Thailand VAT 0%", 0.0, 0), # zero-rated: exports, international transport
|
||||
)
|
||||
DEFAULT_SCHEME = "Thailand VAT 7%"
|
||||
|
||||
# doctype, account, root type, extra row fields. Input VAT is recoverable, so on
|
||||
# purchases it is "Total" (added to the bill, kept out of item valuation).
|
||||
TEMPLATES = (
|
||||
("Sales Taxes and Charges Template", OUTPUT_VAT_ACCOUNT, "Liability", {}),
|
||||
(
|
||||
"Purchase Taxes and Charges Template",
|
||||
INPUT_VAT_ACCOUNT,
|
||||
"Asset",
|
||||
{"category": "Total", "add_deduct_tax": "Add"},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def ensure_vat_templates(company):
|
||||
"""Create the VAT schemes for `company`, skipping titles that exist. The 7%
|
||||
scheme becomes the default when the company has no default template."""
|
||||
cost_center = erpnext.get_default_cost_center(company)
|
||||
for doctype, account_name, root_type, extra in TEMPLATES:
|
||||
account = get_or_create_account(company, account_name, root_type)
|
||||
has_default = frappe.db.exists(doctype, {"company": company, "is_default": 1})
|
||||
for title, rate, included in SCHEMES:
|
||||
if frappe.db.exists(doctype, {"company": company, "title": title}):
|
||||
continue
|
||||
is_default = title == DEFAULT_SCHEME and not has_default
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": doctype,
|
||||
"title": title,
|
||||
"company": company,
|
||||
"is_default": int(is_default),
|
||||
"taxes": [
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": account,
|
||||
"rate": rate,
|
||||
"description": f"VAT {rate:g}%",
|
||||
"included_in_print_rate": included,
|
||||
"cost_center": cost_center,
|
||||
**extra,
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
has_default = has_default or is_default
|
||||
|
||||
|
||||
def setup_company(doc, method=None):
|
||||
"""Company.on_update: VAT schemes for a Thai company."""
|
||||
if company_ready(doc):
|
||||
ensure_vat_templates(doc.name)
|
||||
|
||||
|
||||
def setup_companies():
|
||||
"""after_install and the create_vat_templates patch: companies that exist
|
||||
before this code did never pass through `setup_company`."""
|
||||
for company in thai_companies():
|
||||
ensure_vat_templates(company)
|
||||
Reference in New Issue
Block a user