Compare commits
18
Commits
2d93fe45c2
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68f0817125 | ||
|
|
4a19c60ba2 | ||
|
|
71eda0bec9 | ||
|
|
8cba58ba02 | ||
|
|
4785878025 | ||
|
|
2a44aed574 | ||
|
|
6a969841e5 | ||
|
|
d074657511 | ||
|
|
04735ca813 | ||
|
|
381aab2c83 | ||
|
|
caad6e77da | ||
|
|
8aeecfca2f | ||
|
|
e85ec3a585 | ||
|
|
015ce48639 | ||
|
|
9a8602f74e | ||
|
|
33310c51ae | ||
|
|
f0961de744 | ||
|
|
dc8509d6d7 |
@@ -12,6 +12,14 @@ bench get-app $URL_OF_THIS_REPO --branch develop
|
|||||||
bench install-app default_thai_company
|
bench install-app default_thai_company
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Thai language
|
||||||
|
|
||||||
|
Every string the app adds (custom field labels, messages, print format and letter head
|
||||||
|
text, Tax Withholding Category names) is translated in `translations/th.csv`; the same
|
||||||
|
file overrides the upstream Thai for the standard labels the print formats show. The
|
||||||
|
print formats embed Sarabun (`public/fonts`, OFL) so PDFs render Thai on servers
|
||||||
|
without a Thai system font. Switch a user or the print language to `th` to use it.
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
This app uses `pre-commit` for code formatting and linting. Please [install pre-commit](https://pre-commit.com/#installation) and enable it for this repository:
|
This app uses `pre-commit` for code formatting and linting. Please [install pre-commit](https://pre-commit.com/#installation) and enable it for this repository:
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -6,6 +6,6 @@
|
|||||||
"is_default": 1,
|
"is_default": 1,
|
||||||
"modified": "2026-09-09 17:23:33.559202",
|
"modified": "2026-09-09 17:23:33.559202",
|
||||||
"name": "Thailand",
|
"name": "Thailand",
|
||||||
"template": "{{ address_line1 }}{% if address_line2 %} {{ address_line2 }}{% endif %}<br>\n{% if county %}{{ county }} {% endif -%}{{ city }}{% if state %} {{ state }}{% endif -%}{% if pincode %} {{ pincode }}{% endif -%}{% if country != \"Thailand\" %} {{ country }}{% endif %}<br>\n{% if phone or fax or email_id %}<br>{% endif -%}\n{% if phone %}{{ _(\"Phone\") }}: {{ phone }}<br>{% endif -%}\n{% if fax %}{{ _(\"Fax\") }}: {{ fax }}<br>{% endif -%}\n{% if email_id %}{{ _(\"Email\") }}: {{ email_id }}<br>{% endif -%}\n"
|
"template": "{{ address_line1 }}{% if address_line2 %} {{ address_line2 }}{% endif %}<br>\n{% if county %}{{ county }} {% endif -%}{{ city }}{% if state %} {{ state }}{% endif -%}{% if pincode %} {{ pincode }}{% endif -%}{% if country != \"Thailand\" %} {{ country }}{% endif %}<br>\n{% if phone %}{{ _(\"Phone\") }}: {{ phone }}<br>{% endif -%}\n{% if fax %}{{ _(\"Fax\") }}: {{ fax }}<br>{% endif -%}\n{% if email_id %}{{ _(\"Email\") }}: {{ email_id }}<br>{% endif -%}\n"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"align": "Left",
|
"align": "Left",
|
||||||
"content": "<style>\n .bpt-title {\n display: -webkit-box;\n display: flex;\n -webkit-box-align: center;\n align-items: center;\n width: 100%;\n }\n .bpt-title img {\n margin-right: 1em;\n }\n .bpt-title .bpt-company {\n -webkit-box-flex: 1;\n flex: 1;\n text-align: right;\n }\n</style>\n{% set company = get_letter_head_company(doc) %}\n{% if company %}\n<div class=\"bpt-title\">\n {% if company.company_logo %}\n <img alt=\"{{ company.company_name }}\" height=\"125\" src=\"{{ company.company_logo }}\">\n {% endif %}\n <div class=\"bpt-company\">\n <h1>{{ company.company_name }}</h1>\n <small>\n {% if company.tax_id %}<div>Tax ID: {{ company.tax_id }}</div>{% endif %}\n {% if company.address %}<div>{{ company.address }}</div>{% endif %}\n {% if company.phone_no %}<div>Phone: {{ company.phone_no }}</div>{% endif %}\n {% if company.email %}<div>Email: {{ company.email }}</div>{% endif %}\n </small>\n </div>\n</div>\n{% endif %}\n",
|
"content": "<style>\n .bpt-title {\n display: -webkit-box;\n display: flex;\n -webkit-box-align: center;\n align-items: center;\n width: 100%;\n }\n .bpt-title img {\n margin-right: 1em;\n }\n .bpt-title .bpt-company {\n -webkit-box-flex: 1;\n flex: 1;\n text-align: right;\n }\n</style>\n{% set company = get_letter_head_company(doc) %}\n{% if company %}\n<div class=\"bpt-title\">\n {% if company.company_logo %}\n <img alt=\"{{ company.company_name }}\" height=\"125\" src=\"{{ company.company_logo }}\">\n {% endif %}\n <div class=\"bpt-company\">\n <h1>{{ company.company_name }}</h1>\n <small>\n {% if company.tax_id %}<div>{{ _(\"Tax ID\") }}: {{ company.tax_id }}</div>{% endif %}\n {% if company.address %}<div>{{ company.address }}</div>{% endif %}\n {% if company.phone_no %}<div>{{ _(\"Phone\") }}: {{ company.phone_no }}</div>{% endif %}\n {% if company.email %}<div>{{ _(\"Email\") }}: {{ company.email }}</div>{% endif %}\n </small>\n </div>\n</div>\n{% endif %}\n",
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Letter Head",
|
"doctype": "Letter Head",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"image_width": 0.0,
|
"image_width": 0.0,
|
||||||
"is_default": 1,
|
"is_default": 1,
|
||||||
"letter_head_name": "Default Letter Head",
|
"letter_head_name": "Default Letter Head",
|
||||||
"modified": "2026-09-10 09:51:23.933752",
|
"modified": "2026-09-15 06:44:37.653888",
|
||||||
"name": "Default Letter Head",
|
"name": "Default Letter Head",
|
||||||
"source": "HTML"
|
"source": "HTML"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -78,6 +78,7 @@ jinja = {
|
|||||||
"methods": [
|
"methods": [
|
||||||
"default_thai_company.utils.get_letter_head_company",
|
"default_thai_company.utils.get_letter_head_company",
|
||||||
"default_thai_company.utils.get_company_bank_account",
|
"default_thai_company.utils.get_company_bank_account",
|
||||||
|
"default_thai_company.utils.get_in_words",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,15 +90,63 @@ jinja = {
|
|||||||
fixtures = [
|
fixtures = [
|
||||||
{"doctype": "Letter Head", "filters": [["name", "in", ["Default Letter Head"]]]},
|
{"doctype": "Letter Head", "filters": [["name", "in", ["Default Letter Head"]]]},
|
||||||
{"doctype": "Address Template", "filters": [["name", "in", ["Thailand"]]]},
|
{"doctype": "Address Template", "filters": [["name", "in", ["Thailand"]]]},
|
||||||
{"doctype": "Print Format", "filters": [["name", "in", ["Default Standard Sales Invoice"]]]},
|
{
|
||||||
|
"doctype": "Print Format",
|
||||||
|
"filters": [
|
||||||
|
[
|
||||||
|
"name",
|
||||||
|
"in",
|
||||||
|
[
|
||||||
|
"Default Standard Sales Invoice",
|
||||||
|
"Default Standard Tax Invoice/Receipt",
|
||||||
|
"Default Standard Quotation",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
},
|
||||||
{"doctype": "Tax Withholding Category", "filters": [["name", "like", "WHT %"]]},
|
{"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
|
# Installation
|
||||||
# ------------
|
# ------------
|
||||||
|
|
||||||
# before_install = "default_thai_company.install.before_install"
|
# before_install = "default_thai_company.install.before_install"
|
||||||
# after_install = "default_thai_company.install.after_install"
|
# Fixtures are synced before this runs; VAT schemes and Shipping Rules for companies
|
||||||
|
# that already exist.
|
||||||
|
after_install = [
|
||||||
|
"default_thai_company.vat.setup_companies",
|
||||||
|
"default_thai_company.shipping.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
|
# Uninstallation
|
||||||
# ------------
|
# ------------
|
||||||
@@ -146,6 +195,7 @@ fixtures = [
|
|||||||
override_doctype_class = {
|
override_doctype_class = {
|
||||||
"Sales Invoice": "default_thai_company.tax_withholding.ThaiSalesInvoice",
|
"Sales Invoice": "default_thai_company.tax_withholding.ThaiSalesInvoice",
|
||||||
"Payment Entry": "default_thai_company.tax_withholding.ThaiPaymentEntry",
|
"Payment Entry": "default_thai_company.tax_withholding.ThaiPaymentEntry",
|
||||||
|
"Shipping Rule": "default_thai_company.shipping.ThaiShippingRule",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Document Events
|
# Document Events
|
||||||
@@ -160,8 +210,16 @@ doc_events = {
|
|||||||
"Tax Withholding Category": {
|
"Tax Withholding Category": {
|
||||||
"before_import": "default_thai_company.tax_withholding.prepare_fixture_accounts",
|
"before_import": "default_thai_company.tax_withholding.prepare_fixture_accounts",
|
||||||
},
|
},
|
||||||
|
"Asset Category": {
|
||||||
|
"before_import": "default_thai_company.assets.prepare_fixture_accounts",
|
||||||
|
},
|
||||||
"Company": {
|
"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",
|
||||||
|
"default_thai_company.shipping.setup_company",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
"Payment Entry": {
|
"Payment Entry": {
|
||||||
"validate": "default_thai_company.tax_withholding.set_customer_withholding",
|
"validate": "default_thai_company.tax_withholding.set_customer_withholding",
|
||||||
|
|||||||
@@ -4,3 +4,6 @@
|
|||||||
|
|
||||||
[post_model_sync]
|
[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
|
||||||
|
default_thai_company.patches.create_shipping_rules
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from default_thai_company.shipping import setup_companies
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
setup_companies()
|
||||||
@@ -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()
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2018 The Sarabun Project Authors (https://github.com/cadsondemak/Sarabun)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,66 @@
|
|||||||
|
import erpnext
|
||||||
|
import frappe
|
||||||
|
from erpnext.accounts.doctype.shipping_rule.shipping_rule import ShippingRule
|
||||||
|
|
||||||
|
from default_thai_company.tax_withholding import company_ready, get_or_create_account, thai_companies
|
||||||
|
|
||||||
|
# Shipping Rule is named by its label (site-wide unique) but bound to one company,
|
||||||
|
# so each Thai company gets "<LABEL> - <abbr>", the way ERPNext names accounts and
|
||||||
|
# tax templates. The label doubles as the charge description on the transaction.
|
||||||
|
LABEL = "Shipping Charges"
|
||||||
|
|
||||||
|
# Shipping billed to customers is revenue; the courier's bill stays an expense.
|
||||||
|
SHIPPING_ACCOUNT = "Shipping Charges"
|
||||||
|
SHIPPING_ACCOUNT_GROUPS = ("Direct Income", "Income")
|
||||||
|
|
||||||
|
|
||||||
|
class ThaiShippingRule(ShippingRule):
|
||||||
|
def add_shipping_rule_to_tax_table(self, doc, shipping_amount):
|
||||||
|
"""A Fixed rule without an amount only seeds the charge row; the amount is
|
||||||
|
entered on the transaction. ERPNext re-applies the rule on every
|
||||||
|
recalculation, which would otherwise reset the row to 0."""
|
||||||
|
manual = self.calculate_based_on == "Fixed" and not self.shipping_amount
|
||||||
|
entered = [(row, row.tax_amount) for row in doc.get("taxes")] if manual else ()
|
||||||
|
super().add_shipping_rule_to_tax_table(doc, shipping_amount)
|
||||||
|
for row, amount in entered:
|
||||||
|
row.tax_amount = amount
|
||||||
|
|
||||||
|
|
||||||
|
def shipping_rule_name(company):
|
||||||
|
abbr = frappe.get_cached_value("Company", company, "abbr")
|
||||||
|
return f"{LABEL} - {abbr}"
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_shipping_rule(company):
|
||||||
|
"""Selling Shipping Rule for `company` with the amount entered per
|
||||||
|
transaction (see ThaiShippingRule). No-op when the rule exists."""
|
||||||
|
name = shipping_rule_name(company)
|
||||||
|
if frappe.db.exists("Shipping Rule", name):
|
||||||
|
return
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Shipping Rule",
|
||||||
|
"label": name,
|
||||||
|
"company": company,
|
||||||
|
"shipping_rule_type": "Selling",
|
||||||
|
"calculate_based_on": "Fixed",
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"account": get_or_create_account(
|
||||||
|
company, SHIPPING_ACCOUNT, "Income", "Income Account", SHIPPING_ACCOUNT_GROUPS
|
||||||
|
),
|
||||||
|
"cost_center": erpnext.get_default_cost_center(company),
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_company(doc, method=None):
|
||||||
|
"""Company.on_update: default Shipping Rule for a Thai company."""
|
||||||
|
if company_ready(doc):
|
||||||
|
ensure_shipping_rule(doc.name)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_companies():
|
||||||
|
"""after_install and the create_shipping_rules patch: companies that exist
|
||||||
|
before this code did never pass through `setup_company`."""
|
||||||
|
for company in thai_companies():
|
||||||
|
ensure_shipping_rule(company)
|
||||||
@@ -10,10 +10,25 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt
|
from frappe.utils import cint, flt
|
||||||
|
|
||||||
# Liability: tax we withhold from suppliers and remit on P.N.D.3/53 (linked on every category).
|
from default_thai_company.utils import money_in_words
|
||||||
# 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"
|
LIABILITY_ACCOUNT = "Withholding Tax Payable"
|
||||||
ASSET_ACCOUNT = "Withholding Tax Receivable"
|
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.
|
# Preferred parent groups by root type; first match wins, else the root account.
|
||||||
PARENT_GROUPS = {
|
PARENT_GROUPS = {
|
||||||
@@ -28,7 +43,9 @@ def thai_companies():
|
|||||||
return frappe.get_all("Company", filters={"country": "Thailand"}, pluck="name")
|
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(
|
name = frappe.db.get_value(
|
||||||
"Account", {"company": company, "account_name": account_name, "is_group": 0, "root_type": root_type}
|
"Account", {"company": company, "account_name": account_name, "is_group": 0, "root_type": root_type}
|
||||||
)
|
)
|
||||||
@@ -40,17 +57,19 @@ def get_or_create_account(company, account_name, root_type):
|
|||||||
"doctype": "Account",
|
"doctype": "Account",
|
||||||
"company": company,
|
"company": company,
|
||||||
"account_name": account_name,
|
"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,
|
"root_type": root_type,
|
||||||
"account_type": "Tax",
|
"account_type": account_type,
|
||||||
"is_group": 0,
|
"is_group": 0,
|
||||||
}
|
}
|
||||||
).insert(ignore_permissions=True)
|
).insert(ignore_permissions=True)
|
||||||
return account.name
|
return account.name
|
||||||
|
|
||||||
|
|
||||||
def find_parent_group(company, root_type):
|
def find_parent_group(company, root_type, group_names):
|
||||||
for account_name in PARENT_GROUPS[root_type]:
|
for account_name in group_names:
|
||||||
parent = frappe.db.get_value(
|
parent = frappe.db.get_value(
|
||||||
"Account",
|
"Account",
|
||||||
{"company": company, "account_name": account_name, "is_group": 1, "root_type": root_type},
|
{"company": company, "account_name": account_name, "is_group": 1, "root_type": root_type},
|
||||||
@@ -65,9 +84,10 @@ def find_parent_group(company, root_type):
|
|||||||
|
|
||||||
|
|
||||||
def ensure_company_accounts(company):
|
def ensure_company_accounts(company):
|
||||||
"""Create both withholding accounts for `company`; return the liability account name."""
|
"""Create every account in COMPANY_ACCOUNTS for `company`; return the
|
||||||
get_or_create_account(company, ASSET_ACCOUNT, "Asset")
|
withholding liability account name (the one Tax Withholding Categories link)."""
|
||||||
return get_or_create_account(company, LIABILITY_ACCOUNT, "Liability")
|
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):
|
def link_company(category, company, account):
|
||||||
@@ -102,9 +122,14 @@ def prepare_fixture_accounts(doc, method=None):
|
|||||||
link_company(doc, company, ensure_company_accounts(company))
|
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):
|
def setup_company(doc, method=None):
|
||||||
"""Company.on_update: create withholding accounts and link every WHT category."""
|
"""Company.on_update: create the tax accounts and link every WHT category."""
|
||||||
if doc.country != "Thailand" or not frappe.db.exists("Account", {"company": doc.name}):
|
if not company_ready(doc):
|
||||||
return
|
return
|
||||||
|
|
||||||
account = ensure_company_accounts(doc.name)
|
account = ensure_company_accounts(doc.name)
|
||||||
@@ -158,6 +183,17 @@ class ThaiSalesInvoice(SalesInvoice):
|
|||||||
self.precision("amount_after_withholding"),
|
self.precision("amount_after_withholding"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_total_in_words(self):
|
||||||
|
"""SellingController's version, with Thai wording when the saving user's
|
||||||
|
language is Thai. Print formats re-derive the text per print language
|
||||||
|
(`get_in_words`)."""
|
||||||
|
base_amount = abs(
|
||||||
|
self.base_grand_total if self.is_rounded_total_disabled() else self.base_rounded_total
|
||||||
|
)
|
||||||
|
self.base_in_words = money_in_words(base_amount, self.company_currency)
|
||||||
|
amount = abs(self.grand_total if self.is_rounded_total_disabled() else self.rounded_total)
|
||||||
|
self.in_words = money_in_words(amount, self.currency)
|
||||||
|
|
||||||
|
|
||||||
class ThaiPaymentEntry(PaymentEntry):
|
class ThaiPaymentEntry(PaymentEntry):
|
||||||
def build_gl_map(self):
|
def build_gl_map(self):
|
||||||
@@ -333,7 +369,8 @@ def get_customer_withholding(doc):
|
|||||||
total += withholding_on(details_by_category[category], base_taxable, base_taxable, precision)
|
total += withholding_on(details_by_category[category], base_taxable, base_taxable, precision)
|
||||||
|
|
||||||
if len(details_by_category) == 1:
|
if len(details_by_category) == 1:
|
||||||
description = next(iter(details_by_category.values())).description
|
# category_name; translations/th.csv carries the fixture categories
|
||||||
|
description = _(next(iter(details_by_category.values())).description)
|
||||||
else:
|
else:
|
||||||
description = _("Withholding tax deducted by customer")
|
description = _("Withholding tax deducted by customer")
|
||||||
return flt(total, precision), description
|
return flt(total, precision), description
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
{#- ERPNext's templates/print_formats/includes/taxes.html, printing the amounts that are posted.
|
||||||
|
|
||||||
|
ERPNext takes an Additional Discount off the items' net amounts (item.distributed_discount_amount;
|
||||||
|
a discount on Grand Total is split between net and taxes) and books each charge at
|
||||||
|
tax_amount_after_discount_amount, while upstream prints the discount as entered and the
|
||||||
|
pre-discount tax_amount, so the lines do not add up to Grand Total.
|
||||||
|
|
||||||
|
Inclusive tax with a discount on Grand Total: the Total line is the tax-inclusive item total
|
||||||
|
and the discount is printed as entered; Total After Discount is then broken down into
|
||||||
|
Net Total and the charges. Otherwise the Total line is the net before the discount, so the
|
||||||
|
discount is its net share, followed by Net Total and the charges. A discount that is not
|
||||||
|
distributed (cash / non-trade) comes off the grand total only and stays after the charges,
|
||||||
|
as upstream. -#}
|
||||||
|
{%- macro amount_row(label, value) -%}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
|
||||||
|
<label>{{ label }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-7 text-right">
|
||||||
|
{{ value }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{%- endmacro -%}
|
||||||
|
{%- macro currency(value) -%}
|
||||||
|
{{ frappe.format_value(value, {"fieldtype": "Currency", "options": "currency"}, doc) }}
|
||||||
|
{%- endmacro -%}
|
||||||
|
{%- set net_discount = doc.get("items")|map(attribute="distributed_discount_amount")|select|sum -%}
|
||||||
|
{%- set gross_discount = net_discount and doc.apply_discount_on == "Grand Total" and doc.flags.show_inclusive_tax_in_print -%}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6"></div>
|
||||||
|
<div class="col-xs-6">
|
||||||
|
{%- if gross_discount -%}
|
||||||
|
{{ amount_row(_(doc.meta.get_label("discount_amount")), "- " ~ doc.get_formatted("discount_amount", doc)) }}
|
||||||
|
{{ amount_row(_("Total After Discount"), currency(doc.total - doc.discount_amount)) }}
|
||||||
|
{{ amount_row(_(doc.meta.get_label("net_total")), doc.get_formatted("net_total", doc)) }}
|
||||||
|
{%- elif net_discount -%}
|
||||||
|
{{ amount_row(_(doc.meta.get_label("discount_amount")), "- " ~ currency(net_discount)) }}
|
||||||
|
{{ amount_row(_(doc.meta.get_label("net_total")), doc.get_formatted("net_total", doc)) }}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- for charge in data -%}
|
||||||
|
{%- if (charge.tax_amount or print_settings.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
|
||||||
|
{{ amount_row(charge.get_formatted("description"), charge.get_formatted("tax_amount_after_discount_amount", doc)) }}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endfor -%}
|
||||||
|
{%- if doc.discount_amount and not net_discount -%}
|
||||||
|
{{ amount_row(_(doc.meta.get_label("discount_amount")), "- " ~ doc.get_formatted("discount_amount", doc)) }}
|
||||||
|
{%- endif -%}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{#- ERPNext's templates/print_formats/includes/total.html, paired with the taxes include:
|
||||||
|
with inclusive tax and a discount on Grand Total the Total line is the tax-inclusive item
|
||||||
|
total (the discount is printed as entered below it); with inclusive tax otherwise it is
|
||||||
|
the net before the Additional Discount, since net_total already has the discount's net
|
||||||
|
share (item.distributed_discount_amount) taken off. -#}
|
||||||
|
{%- set net_discount = doc.get("items")|map(attribute="distributed_discount_amount")|select|sum -%}
|
||||||
|
{%- set gross_discount = net_discount and doc.apply_discount_on == "Grand Total" and doc.flags.show_inclusive_tax_in_print -%}
|
||||||
|
<div class="row {% if df.bold %}important{% endif %} data-field">
|
||||||
|
{% if doc.flags.show_inclusive_tax_in_print and not gross_discount %}
|
||||||
|
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
|
||||||
|
<label>{{ _("Total (Without Tax)") }}</label></div>
|
||||||
|
<div class="col-xs-7 text-right value">
|
||||||
|
{{ frappe.format_value(doc.net_total + net_discount, {"fieldtype": "Currency", "options": "currency"}, doc) }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
|
||||||
|
<label>{{ _(df.label) }}</label></div>
|
||||||
|
<div class="col-xs-7 text-right value">
|
||||||
|
{{ doc.get_formatted("total", doc) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
@@ -8,7 +8,9 @@ from frappe.utils import nowdate
|
|||||||
|
|
||||||
from default_thai_company.tax_withholding import (
|
from default_thai_company.tax_withholding import (
|
||||||
ASSET_ACCOUNT,
|
ASSET_ACCOUNT,
|
||||||
|
INPUT_VAT_ACCOUNT,
|
||||||
LIABILITY_ACCOUNT,
|
LIABILITY_ACCOUNT,
|
||||||
|
OUTPUT_VAT_ACCOUNT,
|
||||||
get_payment_entry,
|
get_payment_entry,
|
||||||
thai_companies,
|
thai_companies,
|
||||||
)
|
)
|
||||||
@@ -44,16 +46,7 @@ class TestTaxWithholding(FrappeTestCase):
|
|||||||
).insert()
|
).insert()
|
||||||
cls.payable = f"{LIABILITY_ACCOUNT} - {ABBR}"
|
cls.payable = f"{LIABILITY_ACCOUNT} - {ABBR}"
|
||||||
cls.receivable = f"{ASSET_ACCOUNT} - {ABBR}"
|
cls.receivable = f"{ASSET_ACCOUNT} - {ABBR}"
|
||||||
|
cls.vat = frappe.get_doc("Account", f"{OUTPUT_VAT_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()
|
|
||||||
|
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
{
|
{
|
||||||
@@ -137,13 +130,16 @@ class TestTaxWithholding(FrappeTestCase):
|
|||||||
|
|
||||||
def test_company_creation_adds_accounts_and_links_categories(self):
|
def test_company_creation_adds_accounts_and_links_categories(self):
|
||||||
self.assertIn(COMPANY, thai_companies())
|
self.assertIn(COMPANY, thai_companies())
|
||||||
|
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(
|
self.assertEqual(
|
||||||
frappe.db.get_value("Account", self.payable, ["root_type", "parent_account"]),
|
frappe.db.get_value("Account", account, ["root_type", "parent_account", "account_type"]),
|
||||||
("Liability", f"Duties and Taxes - {ABBR}"),
|
expected,
|
||||||
)
|
account,
|
||||||
self.assertEqual(
|
|
||||||
frappe.db.get_value("Account", self.receivable, ["root_type", "parent_account"]),
|
|
||||||
("Asset", f"Tax Assets - {ABBR}"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
categories = frappe.get_all(
|
categories = frappe.get_all(
|
||||||
@@ -153,6 +149,67 @@ class TestTaxWithholding(FrappeTestCase):
|
|||||||
for name in categories:
|
for name in categories:
|
||||||
self.assertEqual(self.category_account(name), self.payable, name)
|
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_company_creation_adds_shipping_rule(self):
|
||||||
|
rule = frappe.get_doc("Shipping Rule", f"Shipping Charges - {ABBR}")
|
||||||
|
self.assertEqual(
|
||||||
|
(rule.company, rule.shipping_rule_type, rule.calculate_based_on, rule.account, rule.cost_center),
|
||||||
|
(COMPANY, "Selling", "Fixed", f"Shipping Charges - {ABBR}", f"Main - {ABBR}"),
|
||||||
|
)
|
||||||
|
self.assertEqual(frappe.db.get_value("Account", rule.account, "root_type"), "Income")
|
||||||
|
|
||||||
|
def test_shipping_amount_entered_on_transaction_survives_recalculation(self):
|
||||||
|
rule = f"Shipping Charges - {ABBR}"
|
||||||
|
so = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Sales Order",
|
||||||
|
"company": COMPANY,
|
||||||
|
"customer": CUSTOMER,
|
||||||
|
"transaction_date": nowdate(),
|
||||||
|
"delivery_date": nowdate(),
|
||||||
|
"shipping_rule": rule,
|
||||||
|
"items": [{"item_code": ITEM, "qty": 1, "rate": 1000}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
so.set_missing_values()
|
||||||
|
so.apply_shipping_rule()
|
||||||
|
(charge,) = so.taxes
|
||||||
|
self.assertEqual((charge.description, charge.tax_amount), (rule, 0))
|
||||||
|
|
||||||
|
charge.tax_amount = 150
|
||||||
|
so.insert()
|
||||||
|
so.apply_shipping_rule()
|
||||||
|
so.save()
|
||||||
|
self.assertEqual([t.tax_amount for t in so.taxes], [150])
|
||||||
|
self.assertEqual(so.grand_total, 1150)
|
||||||
|
|
||||||
def test_fixture_reimport_keeps_site_account_and_relinks(self):
|
def test_fixture_reimport_keeps_site_account_and_relinks(self):
|
||||||
alt = frappe.get_doc(
|
alt = frappe.get_doc(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
Net Payable After Withholding Tax,ยอดชำระสุทธิหลังหักภาษี ณ ที่จ่าย
|
||||||
|
Withholding Tax,ภาษีหัก ณ ที่จ่าย
|
||||||
|
Tax Withholding Category,ประเภทภาษีหัก ณ ที่จ่าย
|
||||||
|
Withholding tax deducted by customer,ภาษีหัก ณ ที่จ่ายที่ลูกค้าหักไว้
|
||||||
|
Tax Withholding Category {0} has no account for Company {1},ประเภทภาษีหัก ณ ที่จ่าย {0} ไม่มีบัญชีสำหรับบริษัท {1}
|
||||||
|
Account {0} not found for Company {1},ไม่พบบัญชี {0} สำหรับบริษัท {1}
|
||||||
|
Please set Tax Withholding Category on {0} {1} or on this Payment Entry,กรุณาระบุประเภทภาษีหัก ณ ที่จ่ายใน {0} {1} หรือในรายการชำระเงินนี้
|
||||||
|
Tax ID,เลขประจำตัวผู้เสียภาษี
|
||||||
|
Tax Id,เลขประจำตัวผู้เสียภาษี
|
||||||
|
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)
|
||||||
|
"Royalties, goodwill, copyright, other rights - Sec. 40(3), juristic payee (P.N.D.53)",ค่าแห่งกู๊ดวิลล์ ค่าลิขสิทธิ์ หรือสิทธิอย่างอื่น - มาตรา 40(3) ผู้รับเป็นนิติบุคคล (ภ.ง.ด.53)
|
||||||
|
"Interest - Sec. 40(4)(a), juristic payee excl. banks/finance companies (P.N.D.53)",ดอกเบี้ย - มาตรา 40(4)(ก) ผู้รับเป็นนิติบุคคล ยกเว้นธนาคาร/บริษัทเงินทุน (ภ.ง.ด.53)
|
||||||
|
"Interest - Sec. 40(4)(a), individual payee, Sec. 50(2) (P.N.D.2)",ดอกเบี้ย - มาตรา 40(4)(ก) ผู้รับเป็นบุคคลธรรมดา มาตรา 50(2) (ภ.ง.ด.2)
|
||||||
|
"Dividends, share of profits - Sec. 40(4)(b) (P.N.D.2 / P.N.D.53)",เงินปันผล เงินส่วนแบ่งของกำไร - มาตรา 40(4)(ข) (ภ.ง.ด.2 / ภ.ง.ด.53)
|
||||||
|
"Rent of property, hire-purchase of movable property - Sec. 40(5) (P.N.D.3 / P.N.D.53)",ค่าเช่าทรัพย์สิน ค่าเช่าซื้อสังหาริมทรัพย์ - มาตรา 40(5) (ภ.ง.ด.3 / ภ.ง.ด.53)
|
||||||
|
Rent of ships under the Merchant Marine Promotion Act - Sec. 40(5) (P.N.D.53),ค่าเช่าเรือตามกฎหมายว่าด้วยการส่งเสริมการพาณิชยนาวี - มาตรา 40(5) (ภ.ง.ด.53)
|
||||||
|
"Law, medicine, engineering, architecture, accounting, fine arts - Sec. 40(6) (P.N.D.3 / P.N.D.53)",วิชาชีพอิสระ กฎหมาย การประกอบโรคศิลปะ วิศวกรรม สถาปัตยกรรม การบัญชี ประณีตศิลปกรรม - มาตรา 40(6) (ภ.ง.ด.3 / ภ.ง.ด.53)
|
||||||
|
"Contract work, contractor supplies essential materials - Sec. 40(7) (P.N.D.3 / P.N.D.53)",ค่ารับเหมาที่ผู้รับเหมาต้องลงทุนจัดหาสัมภาระในส่วนสำคัญ - มาตรา 40(7) (ภ.ง.ด.3 / ภ.ง.ด.53)
|
||||||
|
"Hire of work, service fees - Sec. 40(8) (P.N.D.3 / P.N.D.53)",ค่าจ้างทำของ ค่าบริการ - มาตรา 40(8) (ภ.ง.ด.3 / ภ.ง.ด.53)
|
||||||
|
"Rewards, discounts, sales promotion benefits - Sec. 40(8) (P.N.D.3 / P.N.D.53)",รางวัล ส่วนลด หรือประโยชน์ใด ๆ เนื่องจากการส่งเสริมการขาย - มาตรา 40(8) (ภ.ง.ด.3 / ภ.ง.ด.53)
|
||||||
|
Advertising fees - Sec. 40(8) (P.N.D.3 / P.N.D.53),ค่าโฆษณา - มาตรา 40(8) (ภ.ง.ด.3 / ภ.ง.ด.53)
|
||||||
|
"Transport of goods or passengers, excl. public transport - Sec. 40(8) (P.N.D.3 / P.N.D.53)",ค่าขนส่งสินค้าหรือผู้โดยสาร ยกเว้นการขนส่งสาธารณะ - มาตรา 40(8) (ภ.ง.ด.3 / ภ.ง.ด.53)
|
||||||
|
Non-life insurance premiums - Sec. 40(8) (P.N.D.53),ค่าเบี้ยประกันวินาศภัย - มาตรา 40(8) (ภ.ง.ด.53)
|
||||||
|
"Prizes from contests, competitions, lucky draws - Sec. 40(8) (P.N.D.3 / P.N.D.53)",รางวัลจากการประกวด การแข่งขัน การชิงโชค - มาตรา 40(8) (ภ.ง.ด.3 / ภ.ง.ด.53)
|
||||||
|
Public entertainers resident in Thailand - Sec. 40(8) (P.N.D.3),นักแสดงสาธารณะที่มีภูมิลำเนาในประเทศไทย - มาตรา 40(8) (ภ.ง.ด.3)
|
||||||
|
"Rubber, cassava, jute, rice, maize, sugarcane, coffee beans, oil palm - Sec. 40(8) (P.N.D.3 / P.N.D.53)",ยางแผ่น มันสำปะหลัง ปอ ข้าว ข้าวโพด อ้อย เมล็ดกาแฟ ปาล์มน้ำมัน - มาตรา 40(8) (ภ.ง.ด.3 / ภ.ง.ด.53)
|
||||||
|
Aquatic animals bought from non-registered sellers - Sec. 40(8) (P.N.D.3 / P.N.D.53),สัตว์น้ำที่ซื้อจากผู้ขายที่ไม่ได้จดทะเบียนภาษีมูลค่าเพิ่ม - มาตรา 40(8) (ภ.ง.ด.3 / ภ.ง.ด.53)
|
||||||
|
"Sale of immovable property, juristic seller - Sec. 69 Ter (remitted at the Land Office)",การขายอสังหาริมทรัพย์ ผู้ขายเป็นนิติบุคคล - มาตรา 69 ตรี (นำส่ง ณ สำนักงานที่ดิน)
|
||||||
|
Sec. 40(2)-(6) income paid to non-resident individuals - Sec. 50(2) (P.N.D.3),เงินได้ตามมาตรา 40(2)-(6) จ่ายให้บุคคลธรรมดาที่มิได้เป็นผู้อยู่ในประเทศไทย - มาตรา 50(2) (ภ.ง.ด.3)
|
||||||
|
"Sec. 40(2)-(6) income excl. dividends to foreign companies not doing business in Thailand - Sec. 70, pre-DTA (P.N.D.54)",เงินได้ตามมาตรา 40(2)-(6) ยกเว้นเงินปันผล จ่ายให้บริษัทต่างประเทศที่มิได้ประกอบกิจการในประเทศไทย - มาตรา 70 ก่อนใช้อนุสัญญาภาษีซ้อน (ภ.ง.ด.54)
|
||||||
|
"Dividends to foreign companies not doing business in Thailand - Sec. 70, pre-DTA (P.N.D.54)",เงินปันผลจ่ายให้บริษัทต่างประเทศที่มิได้ประกอบกิจการในประเทศไทย - มาตรา 70 ก่อนใช้อนุสัญญาภาษีซ้อน (ภ.ง.ด.54)
|
||||||
|
Sales Invoice,ใบแจ้งหนี้
|
||||||
|
Sales Order,ใบสั่งขาย
|
||||||
|
Posting Date,วันที่
|
||||||
|
Payment Due Date,วันครบกำหนดชำระ
|
||||||
|
Contact Person,ผู้ติดต่อ
|
||||||
|
Mobile No,โทรศัพท์มือถือ
|
||||||
|
Contact Email,อีเมล
|
||||||
|
Description,รายละเอียด
|
||||||
|
Rate,ราคาต่อหน่วย
|
||||||
|
Amount,จำนวนเงิน
|
||||||
|
Total,รวม
|
||||||
|
Total (Without Tax),รวมก่อนภาษี
|
||||||
|
Net Total,ยอดรวมสุทธิ
|
||||||
|
Total After Discount,ยอดรวมหลังหักส่วนลด
|
||||||
|
Total Taxes and Charges,รวมภาษีและค่าธรรมเนียม
|
||||||
|
Grand Total,ยอดรวมทั้งสิ้น
|
||||||
|
Rounded Total,ยอดรวมปัดเศษ
|
||||||
|
Terms and Conditions Details,รายละเอียดข้อตกลงและเงื่อนไข
|
||||||
|
Page {0} of {1},หน้า {0} จาก {1}
|
||||||
|
Withholding Tax Payable,ภาษีหัก ณ ที่จ่ายค้างจ่าย
|
||||||
|
Withholding Tax Receivable,ภาษีถูกหัก ณ ที่จ่าย
|
||||||
|
Output VAT,ภาษีขาย
|
||||||
|
Input VAT,ภาษีซื้อ
|
||||||
|
@@ -1,6 +1,30 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
import frappe.utils
|
||||||
from erpnext import get_default_company
|
from erpnext import get_default_company
|
||||||
from frappe.contacts.doctype.address.address import get_default_address, render_address
|
from frappe.contacts.doctype.address.address import get_default_address, render_address
|
||||||
|
from num2words import num2words
|
||||||
|
|
||||||
|
|
||||||
|
def money_in_words(amount, currency):
|
||||||
|
"""Amount in words for the active language.
|
||||||
|
|
||||||
|
frappe.utils.money_in_words renders "<currency> <words> only." in every
|
||||||
|
language; Thai documents write "<words>บาทถ้วน" or "<words>บาท<words>สตางค์".
|
||||||
|
Currencies num2words cannot spell in Thai keep frappe's wording.
|
||||||
|
"""
|
||||||
|
if frappe.local.lang == "th":
|
||||||
|
try:
|
||||||
|
return num2words(amount, lang="th", to="currency", currency=currency)
|
||||||
|
except NotImplementedError:
|
||||||
|
pass
|
||||||
|
return frappe.utils.money_in_words(amount, currency)
|
||||||
|
|
||||||
|
|
||||||
|
def get_in_words(doc):
|
||||||
|
"""`in_words` for print formats: re-derived in the print language instead
|
||||||
|
of the language of whoever saved the document."""
|
||||||
|
amount = abs(doc.grand_total if doc.is_rounded_total_disabled() else doc.rounded_total)
|
||||||
|
return money_in_words(amount, doc.currency)
|
||||||
|
|
||||||
|
|
||||||
def get_letter_head_company(doc=None):
|
def get_letter_head_company(doc=None):
|
||||||
|
|||||||
@@ -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