Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
631396edbe | ||
|
|
3f14b3220e | ||
|
|
f6daadbe0a | ||
|
|
05ca734e8a | ||
|
|
f7d755a59e | ||
|
|
46b2047f1c |
@@ -12,25 +12,6 @@ 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.
|
|
||||||
|
|
||||||
### Withholding tax certificate
|
|
||||||
|
|
||||||
A paid Purchase Invoice with *Apply Tax Withholding Amount* gets a **Withholding Tax Certificate**
|
|
||||||
button that downloads the Revenue Department's Sec. 50 bis form (หนังสือรับรองการหักภาษี ณ ที่จ่าย,
|
|
||||||
two copies) as a PDF: the *Withholding Tax Certificate* print format lays the invoice's data
|
|
||||||
over the official form (`public/images`). The row the payment is reported on comes from
|
|
||||||
*Type of Income Paid* on the Tax Withholding Category (set for the shipped categories; pick it
|
|
||||||
for your own), and the ภ.ง.ด. return is ticked from the Supplier's type: ภ.ง.ด.53 for juristic
|
|
||||||
payees, ภ.ง.ด.1ก / 2 / 3 by income type for individuals. เล่มที่ / เลขที่ (certificate book
|
|
||||||
numbers) and ลำดับที่ (the line in the return) are left for the accountant.
|
|
||||||
|
|
||||||
### 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:
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
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")
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"custom_fields": [],
|
|
||||||
"custom_perms": [],
|
|
||||||
"doctype": "Payment Entry",
|
|
||||||
"links": [],
|
|
||||||
"property_setters": [
|
|
||||||
{
|
|
||||||
"_assign": null,
|
|
||||||
"_comments": null,
|
|
||||||
"_liked_by": null,
|
|
||||||
"_user_tags": null,
|
|
||||||
"creation": "2026-09-11 17:18:28.670630",
|
|
||||||
"default_value": null,
|
|
||||||
"doc_type": "Payment Entry",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype_or_field": "DocField",
|
|
||||||
"field_name": "apply_tax_withholding_amount",
|
|
||||||
"idx": 0,
|
|
||||||
"is_system_generated": 0,
|
|
||||||
"modified": "2026-09-11 12:00:00",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": null,
|
|
||||||
"name": "Payment Entry-apply_tax_withholding_amount-depends_on",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"property": "depends_on",
|
|
||||||
"property_type": "Data",
|
|
||||||
"row_name": null,
|
|
||||||
"value": "eval:doc.party_type == 'Supplier' || (doc.party_type == 'Customer' && doc.payment_type == 'Receive')"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sync_on_migrate": 1
|
|
||||||
}
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
{
|
|
||||||
"custom_fields": [
|
|
||||||
{
|
|
||||||
"_assign": null,
|
|
||||||
"_comments": null,
|
|
||||||
"_liked_by": null,
|
|
||||||
"_user_tags": null,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 1,
|
|
||||||
"collapsible": 0,
|
|
||||||
"collapsible_depends_on": null,
|
|
||||||
"columns": 0,
|
|
||||||
"creation": "2026-09-11 17:18:48.182536",
|
|
||||||
"default": null,
|
|
||||||
"depends_on": "eval:doc.tax_withholding_category",
|
|
||||||
"description": null,
|
|
||||||
"docstatus": 0,
|
|
||||||
"dt": "Sales Invoice",
|
|
||||||
"fetch_from": null,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "amount_after_withholding",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 0,
|
|
||||||
"hide_border": 0,
|
|
||||||
"hide_days": 0,
|
|
||||||
"hide_seconds": 0,
|
|
||||||
"idx": 80,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_preview": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"insert_after": "withholding_tax_amount",
|
|
||||||
"is_system_generated": 0,
|
|
||||||
"is_virtual": 0,
|
|
||||||
"label": "Net Payable After Withholding Tax",
|
|
||||||
"length": 0,
|
|
||||||
"link_filters": null,
|
|
||||||
"mandatory_depends_on": null,
|
|
||||||
"modified": "2026-09-11 17:18:48.182536",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": null,
|
|
||||||
"name": "Sales Invoice-amount_after_withholding",
|
|
||||||
"no_copy": 1,
|
|
||||||
"non_negative": 0,
|
|
||||||
"options": "currency",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permlevel": 0,
|
|
||||||
"placeholder": null,
|
|
||||||
"precision": null,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 1,
|
|
||||||
"print_width": null,
|
|
||||||
"read_only": 1,
|
|
||||||
"read_only_depends_on": null,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"show_dashboard": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_assign": null,
|
|
||||||
"_comments": null,
|
|
||||||
"_liked_by": null,
|
|
||||||
"_user_tags": null,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"collapsible_depends_on": null,
|
|
||||||
"columns": 0,
|
|
||||||
"creation": "2026-09-11 17:18:44.710456",
|
|
||||||
"default": null,
|
|
||||||
"depends_on": null,
|
|
||||||
"description": null,
|
|
||||||
"docstatus": 0,
|
|
||||||
"dt": "Sales Invoice",
|
|
||||||
"fetch_from": "customer.tax_withholding_category",
|
|
||||||
"fetch_if_empty": 1,
|
|
||||||
"fieldname": "tax_withholding_category",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 0,
|
|
||||||
"hide_border": 0,
|
|
||||||
"hide_days": 0,
|
|
||||||
"hide_seconds": 0,
|
|
||||||
"idx": 57,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_preview": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"insert_after": "taxes_and_charges",
|
|
||||||
"is_system_generated": 0,
|
|
||||||
"is_virtual": 0,
|
|
||||||
"label": "Tax Withholding Category",
|
|
||||||
"length": 0,
|
|
||||||
"link_filters": null,
|
|
||||||
"mandatory_depends_on": null,
|
|
||||||
"modified": "2026-09-11 18:00:00",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": null,
|
|
||||||
"name": "Sales Invoice-tax_withholding_category",
|
|
||||||
"no_copy": 0,
|
|
||||||
"non_negative": 0,
|
|
||||||
"options": "Tax Withholding Category",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permlevel": 0,
|
|
||||||
"placeholder": null,
|
|
||||||
"precision": null,
|
|
||||||
"print_hide": 1,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": null,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_depends_on": null,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"show_dashboard": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_assign": null,
|
|
||||||
"_comments": null,
|
|
||||||
"_liked_by": null,
|
|
||||||
"_user_tags": null,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"collapsible_depends_on": null,
|
|
||||||
"columns": 0,
|
|
||||||
"creation": "2026-09-11 17:18:47.244523",
|
|
||||||
"default": null,
|
|
||||||
"depends_on": "eval:doc.tax_withholding_category",
|
|
||||||
"description": null,
|
|
||||||
"docstatus": 0,
|
|
||||||
"dt": "Sales Invoice",
|
|
||||||
"fetch_from": null,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "withholding_tax_amount",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"hidden": 0,
|
|
||||||
"hide_border": 0,
|
|
||||||
"hide_days": 0,
|
|
||||||
"hide_seconds": 0,
|
|
||||||
"idx": 79,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_preview": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"insert_after": "rounded_total",
|
|
||||||
"is_system_generated": 0,
|
|
||||||
"is_virtual": 0,
|
|
||||||
"label": "Withholding Tax",
|
|
||||||
"length": 0,
|
|
||||||
"link_filters": null,
|
|
||||||
"mandatory_depends_on": null,
|
|
||||||
"modified": "2026-09-11 18:00:00",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": null,
|
|
||||||
"name": "Sales Invoice-withholding_tax_amount",
|
|
||||||
"no_copy": 1,
|
|
||||||
"non_negative": 0,
|
|
||||||
"options": "currency",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permlevel": 0,
|
|
||||||
"placeholder": null,
|
|
||||||
"precision": null,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 1,
|
|
||||||
"print_width": null,
|
|
||||||
"read_only": 1,
|
|
||||||
"read_only_depends_on": null,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"show_dashboard": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"custom_perms": [],
|
|
||||||
"doctype": "Sales Invoice",
|
|
||||||
"links": [],
|
|
||||||
"property_setters": [
|
|
||||||
{
|
|
||||||
"_assign": null,
|
|
||||||
"_comments": null,
|
|
||||||
"_liked_by": null,
|
|
||||||
"_user_tags": null,
|
|
||||||
"creation": "2026-09-11 17:18:28.581896",
|
|
||||||
"default_value": null,
|
|
||||||
"doc_type": "Sales Invoice",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype_or_field": "DocType",
|
|
||||||
"field_name": null,
|
|
||||||
"idx": 0,
|
|
||||||
"is_system_generated": 1,
|
|
||||||
"modified": "2026-09-10 13:55:13.996942",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": null,
|
|
||||||
"name": "Sales Invoice-main-default_print_format",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"property": "default_print_format",
|
|
||||||
"property_type": "Data",
|
|
||||||
"row_name": null,
|
|
||||||
"value": "Default Standard Sales Invoice"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sync_on_migrate": 1
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
{
|
|
||||||
"custom_fields": [
|
|
||||||
{
|
|
||||||
"_assign": null,
|
|
||||||
"_comments": null,
|
|
||||||
"_liked_by": null,
|
|
||||||
"_user_tags": null,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"collapsible_depends_on": null,
|
|
||||||
"columns": 0,
|
|
||||||
"creation": "2026-09-22 09:00:00.000000",
|
|
||||||
"default": null,
|
|
||||||
"depends_on": null,
|
|
||||||
"description": "Row of the withholding tax certificate (50 Tawi) the payment is reported on; Other prints the category name in row 6.",
|
|
||||||
"docstatus": 0,
|
|
||||||
"dt": "Tax Withholding Category",
|
|
||||||
"fetch_from": null,
|
|
||||||
"fetch_if_empty": 0,
|
|
||||||
"fieldname": "income_type",
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"hidden": 0,
|
|
||||||
"hide_border": 0,
|
|
||||||
"hide_days": 0,
|
|
||||||
"hide_seconds": 0,
|
|
||||||
"idx": 2,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_preview": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"insert_after": "category_name",
|
|
||||||
"is_system_generated": 0,
|
|
||||||
"is_virtual": 0,
|
|
||||||
"label": "Type of Income Paid",
|
|
||||||
"length": 0,
|
|
||||||
"link_filters": null,
|
|
||||||
"mandatory_depends_on": null,
|
|
||||||
"modified": "2026-09-22 09:00:00.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": null,
|
|
||||||
"name": "Tax Withholding Category-income_type",
|
|
||||||
"no_copy": 0,
|
|
||||||
"non_negative": 0,
|
|
||||||
"options": "\nSalary and Wages - Sec. 40(1)\nFees and Commissions - Sec. 40(2)\nRoyalties - Sec. 40(3)\nInterest - Sec. 40(4)(a)\nDividends - Sec. 40(4)(b)\nSec. 3 Tera (Services, Rent, Contract Work etc.)\nOther",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permlevel": 0,
|
|
||||||
"placeholder": null,
|
|
||||||
"precision": null,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": null,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_depends_on": null,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"show_dashboard": 0,
|
|
||||||
"sort_options": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"custom_perms": [],
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"links": [],
|
|
||||||
"property_setters": [],
|
|
||||||
"sync_on_migrate": 1
|
|
||||||
}
|
|
||||||
@@ -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 %}{{ _(\"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 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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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)"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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-15 06:44:37.653888",
|
"modified": "2026-09-10 09:51:23.933752",
|
||||||
"name": "Default Letter Head",
|
"name": "Default Letter Head",
|
||||||
"source": "HTML"
|
"source": "HTML"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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
@@ -0,0 +1,18 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"default_value": null,
|
||||||
|
"doc_type": "Sales Invoice",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Property Setter",
|
||||||
|
"doctype_or_field": "DocType",
|
||||||
|
"field_name": null,
|
||||||
|
"is_system_generated": 1,
|
||||||
|
"modified": "2026-09-10 13:55:13.996942",
|
||||||
|
"module": null,
|
||||||
|
"name": "Sales Invoice-main-default_print_format",
|
||||||
|
"property": "default_print_format",
|
||||||
|
"property_type": "Data",
|
||||||
|
"row_name": null,
|
||||||
|
"value": "Default Standard Sales Invoice"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,596 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Commission, brokerage, agency fees - Sec. 40(2), juristic payee (P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Fees and Commissions - Sec. 40(2)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 3% - Commission",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 3% - Commission",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 3.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Royalties, goodwill, copyright, other rights - Sec. 40(3), juristic payee (P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Royalties - Sec. 40(3)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 3% - Royalty",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 3% - Royalty",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 3.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Interest - Sec. 40(4)(a), juristic payee excl. banks/finance companies (P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Interest - Sec. 40(4)(a)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 1% - Interest (Company)",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 1% - Interest (Company)",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 1.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Interest - Sec. 40(4)(a), individual payee, Sec. 50(2) (P.N.D.2)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Interest - Sec. 40(4)(a)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 15% - Interest (Individual)",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 15% - Interest (Individual)",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1.0,
|
|
||||||
"tax_withholding_rate": 15.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Dividends, share of profits - Sec. 40(4)(b) (P.N.D.2 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Dividends - Sec. 40(4)(b)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 10% - Dividend",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 10% - Dividend",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1.0,
|
|
||||||
"tax_withholding_rate": 10.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Rent of property, hire-purchase of movable property - Sec. 40(5) (P.N.D.3 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 5% - Rent",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 5% - Rent",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 5.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Rent of ships under the Merchant Marine Promotion Act - Sec. 40(5) (P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 1% - Ship Rental",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 1% - Ship Rental",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 1.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Law, medicine, engineering, architecture, accounting, fine arts - Sec. 40(6) (P.N.D.3 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 3% - Professional Fee",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 3% - Professional Fee",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 3.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Contract work, contractor supplies essential materials - Sec. 40(7) (P.N.D.3 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 3% - Contract Work",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 3% - Contract Work",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 3.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Hire of work, service fees - Sec. 40(8) (P.N.D.3 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 3% - Service",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 3% - Service",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 3.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Rewards, discounts, sales promotion benefits - Sec. 40(8) (P.N.D.3 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 3% - Sales Promotion",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 3% - Sales Promotion",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 3.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Advertising fees - Sec. 40(8) (P.N.D.3 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 2% - Advertising",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 2% - Advertising",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 2.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Transport of goods or passengers, excl. public transport - Sec. 40(8) (P.N.D.3 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 1% - Transportation",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 1% - Transportation",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 1.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Non-life insurance premiums - Sec. 40(8) (P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 1% - Non-Life Insurance",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 1% - Non-Life Insurance",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 1.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Prizes from contests, competitions, lucky draws - Sec. 40(8) (P.N.D.3 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 5% - Prize",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 5% - Prize",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 5.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Public entertainers resident in Thailand - Sec. 40(8) (P.N.D.3)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 5% - Public Entertainer",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 5% - Public Entertainer",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 5.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Rubber, cassava, jute, rice, maize, sugarcane, coffee beans, oil palm - Sec. 40(8) (P.N.D.3 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 0.75% - Agricultural Produce",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 0.75% - Agricultural Produce",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 0.75,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Aquatic animals bought from non-registered sellers - Sec. 40(8) (P.N.D.3 / P.N.D.53)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 1% - Aquatic Animals",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 1% - Aquatic Animals",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1000.0,
|
|
||||||
"tax_withholding_rate": 1.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Sale of immovable property, juristic seller - Sec. 69 Ter (remitted at the Land Office)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Other",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 1% - Immovable Property (Company)",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 1% - Immovable Property (Company)",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1.0,
|
|
||||||
"tax_withholding_rate": 1.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Sec. 40(2)-(6) income paid to non-resident individuals - Sec. 50(2) (P.N.D.3)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Other",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 15% - Non-Resident Individual",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 15% - Non-Resident Individual",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1.0,
|
|
||||||
"tax_withholding_rate": 15.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Sec. 40(2)-(6) income excl. dividends to foreign companies not doing business in Thailand - Sec. 70, pre-DTA (P.N.D.54)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Other",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 15% - Foreign Company",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 15% - Foreign Company",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1.0,
|
|
||||||
"tax_withholding_rate": 15.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"accounts": [],
|
|
||||||
"category_name": "Dividends to foreign companies not doing business in Thailand - Sec. 70, pre-DTA (P.N.D.54)",
|
|
||||||
"consider_party_ledger_amount": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Category",
|
|
||||||
"income_type": "Other",
|
|
||||||
"modified": "2026-09-22 12:00:00.000000",
|
|
||||||
"name": "WHT 10% - Foreign Company Dividend",
|
|
||||||
"rates": [
|
|
||||||
{
|
|
||||||
"cumulative_threshold": 0.0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Tax Withholding Rate",
|
|
||||||
"from_date": "2024-01-01",
|
|
||||||
"idx": 1,
|
|
||||||
"parent": "WHT 10% - Foreign Company Dividend",
|
|
||||||
"parentfield": "rates",
|
|
||||||
"parenttype": "Tax Withholding Category",
|
|
||||||
"single_threshold": 1.0,
|
|
||||||
"tax_withholding_rate": 10.0,
|
|
||||||
"to_date": "2099-12-31"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"round_off_tax_amount": 0,
|
|
||||||
"tax_on_excess_amount": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -43,10 +43,7 @@ required_apps = ["erpnext"]
|
|||||||
# page_js = {"page" : "public/js/file.js"}
|
# page_js = {"page" : "public/js/file.js"}
|
||||||
|
|
||||||
# include js in doctype views
|
# include js in doctype views
|
||||||
doctype_js = {
|
# doctype_js = {"doctype" : "public/js/doctype.js"}
|
||||||
"Payment Entry": "public/js/payment_entry.js",
|
|
||||||
"Purchase Invoice": "public/js/purchase_invoice.js",
|
|
||||||
}
|
|
||||||
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
|
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
|
||||||
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
|
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
|
||||||
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
|
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
|
||||||
@@ -81,81 +78,27 @@ 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",
|
|
||||||
"default_thai_company.tax_withholding.get_withholding_certificate",
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fixtures
|
# Fixtures
|
||||||
# --------
|
# --------
|
||||||
|
|
||||||
# Custom Fields and Property Setters live in default_thai_company/custom/*.json
|
|
||||||
# (synced in place on install and migrate); fixtures are for whole documents.
|
|
||||||
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",
|
"doctype": "Property Setter",
|
||||||
"filters": [
|
"filters": [["name", "in", ["Sales Invoice-main-default_print_format"]]],
|
||||||
[
|
|
||||||
"name",
|
|
||||||
"in",
|
|
||||||
[
|
|
||||||
"Default Standard Sales Invoice",
|
|
||||||
"Default Standard Tax Invoice/Receipt",
|
|
||||||
"Default Standard Quotation",
|
|
||||||
"Withholding Tax Certificate",
|
|
||||||
],
|
|
||||||
]
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{"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
|
||||||
# ------------
|
# ------------
|
||||||
|
|
||||||
# Fixtures are synced before Custom Fields on install and migrate; the Tax Withholding
|
# before_install = "default_thai_company.install.before_install"
|
||||||
# Category fixtures carry values for the app's Custom Fields, so create those first.
|
# after_install = "default_thai_company.install.after_install"
|
||||||
before_install = "default_thai_company.tax_withholding.sync_category_customizations"
|
|
||||||
before_migrate = "default_thai_company.tax_withholding.sync_category_customizations"
|
|
||||||
# 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",
|
|
||||||
"default_thai_company.payroll.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
|
||||||
# ------------
|
# ------------
|
||||||
@@ -169,8 +112,7 @@ after_migrate = "default_thai_company.assets.rebuild_locations"
|
|||||||
# Name of the app being installed is passed as an argument
|
# Name of the app being installed is passed as an argument
|
||||||
|
|
||||||
# before_app_install = "default_thai_company.utils.before_app_install"
|
# before_app_install = "default_thai_company.utils.before_app_install"
|
||||||
# Payroll Period for companies created before hrms was installed.
|
# after_app_install = "default_thai_company.utils.after_app_install"
|
||||||
after_app_install = "default_thai_company.payroll.after_app_install"
|
|
||||||
|
|
||||||
# Integration Cleanup
|
# Integration Cleanup
|
||||||
# -------------------
|
# -------------------
|
||||||
@@ -202,11 +144,9 @@ after_app_install = "default_thai_company.payroll.after_app_install"
|
|||||||
# ---------------
|
# ---------------
|
||||||
# Override standard doctype classes
|
# Override standard doctype classes
|
||||||
|
|
||||||
override_doctype_class = {
|
# override_doctype_class = {
|
||||||
"Sales Invoice": "default_thai_company.tax_withholding.ThaiSalesInvoice",
|
# "ToDo": "custom_app.overrides.CustomToDo"
|
||||||
"Payment Entry": "default_thai_company.tax_withholding.ThaiPaymentEntry",
|
# }
|
||||||
"Shipping Rule": "default_thai_company.shipping.ThaiShippingRule",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Document Events
|
# Document Events
|
||||||
# ---------------
|
# ---------------
|
||||||
@@ -216,34 +156,29 @@ doc_events = {
|
|||||||
"Letter Head": {
|
"Letter Head": {
|
||||||
"before_import": "default_thai_company.utils.stash_letter_head_source",
|
"before_import": "default_thai_company.utils.stash_letter_head_source",
|
||||||
"before_insert": "default_thai_company.utils.restore_letter_head_source",
|
"before_insert": "default_thai_company.utils.restore_letter_head_source",
|
||||||
},
|
}
|
||||||
"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",
|
|
||||||
"default_thai_company.vat.setup_company",
|
|
||||||
"default_thai_company.assets.setup_company",
|
|
||||||
"default_thai_company.shipping.setup_company",
|
|
||||||
"default_thai_company.payroll.setup_company",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"Payment Entry": {
|
|
||||||
"validate": "default_thai_company.tax_withholding.set_customer_withholding",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Scheduled Tasks
|
# Scheduled Tasks
|
||||||
# ---------------
|
# ---------------
|
||||||
|
|
||||||
# Payroll Period is per calendar year; roll the default over on 1 January.
|
# scheduler_events = {
|
||||||
scheduler_events = {
|
# "all": [
|
||||||
"daily": ["default_thai_company.payroll.setup_companies"],
|
# "default_thai_company.tasks.all"
|
||||||
}
|
# ],
|
||||||
|
# "daily": [
|
||||||
|
# "default_thai_company.tasks.daily"
|
||||||
|
# ],
|
||||||
|
# "hourly": [
|
||||||
|
# "default_thai_company.tasks.hourly"
|
||||||
|
# ],
|
||||||
|
# "weekly": [
|
||||||
|
# "default_thai_company.tasks.weekly"
|
||||||
|
# ],
|
||||||
|
# "monthly": [
|
||||||
|
# "default_thai_company.tasks.monthly"
|
||||||
|
# ],
|
||||||
|
# }
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
# -------
|
# -------
|
||||||
@@ -252,11 +187,11 @@ scheduler_events = {
|
|||||||
|
|
||||||
# Overriding Methods
|
# Overriding Methods
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
|
#
|
||||||
override_whitelisted_methods = {
|
# override_whitelisted_methods = {
|
||||||
"erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry": "default_thai_company.tax_withholding.get_payment_entry",
|
# "frappe.desk.doctype.event.event.get_events": "default_thai_company.event.get_events"
|
||||||
}
|
# }
|
||||||
|
#
|
||||||
# each overriding function accepts a `data` argument;
|
# each overriding function accepts a `data` argument;
|
||||||
# generated from the base implementation of the doctype dashboard,
|
# generated from the base implementation of the doctype dashboard,
|
||||||
# along with any modifications made in other Frappe apps
|
# along with any modifications made in other Frappe apps
|
||||||
|
|||||||
@@ -4,7 +4,3 @@
|
|||||||
|
|
||||||
[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
|
|
||||||
default_thai_company.patches.create_payroll_periods
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
from default_thai_company.payroll import setup_companies
|
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
|
||||||
setup_companies()
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from default_thai_company.shipping import setup_companies
|
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
|
||||||
setup_companies()
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from default_thai_company.vat import setup_companies
|
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
|
||||||
setup_companies()
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import frappe
|
|
||||||
from frappe.utils import getdate
|
|
||||||
|
|
||||||
from default_thai_company.tax_withholding import thai_companies
|
|
||||||
|
|
||||||
# Thai personal income tax (P.N.D.1/91) is assessed per calendar year whatever
|
|
||||||
# the company's fiscal year, so the Payroll Period is January to December.
|
|
||||||
# Payroll Period is named by prompt (site-wide unique) but bound to one company,
|
|
||||||
# so each Thai company gets "<year> - <abbr>", like the Shipping Rule.
|
|
||||||
|
|
||||||
|
|
||||||
def hrms_installed():
|
|
||||||
return "hrms" in frappe.get_installed_apps()
|
|
||||||
|
|
||||||
|
|
||||||
def payroll_period_name(company, year):
|
|
||||||
abbr = frappe.get_cached_value("Company", company, "abbr")
|
|
||||||
return f"{year} - {abbr}"
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_payroll_period(company, year):
|
|
||||||
"""Calendar-year Payroll Period for `company`. No-op when any period of the
|
|
||||||
company touches the year: an existing one covers it, or the company runs its
|
|
||||||
own scheme and a second period would only fail the overlap check."""
|
|
||||||
start, end = f"{year}-01-01", f"{year}-12-31"
|
|
||||||
if frappe.db.exists(
|
|
||||||
"Payroll Period", {"company": company, "start_date": ("<=", end), "end_date": (">=", start)}
|
|
||||||
):
|
|
||||||
return
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Payroll Period",
|
|
||||||
"name": payroll_period_name(company, year),
|
|
||||||
"company": company,
|
|
||||||
"start_date": start,
|
|
||||||
"end_date": end,
|
|
||||||
}
|
|
||||||
).insert(ignore_permissions=True)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_company(doc, method=None):
|
|
||||||
"""Company.on_update: this year's Payroll Period for a Thai company."""
|
|
||||||
if doc.country == "Thailand" and hrms_installed():
|
|
||||||
ensure_payroll_period(doc.name, getdate().year)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_companies():
|
|
||||||
"""after_install, the daily scheduler, and the create_payroll_periods patch:
|
|
||||||
this year's Payroll Period for every Thai company. The scheduler rolls the
|
|
||||||
default over on 1 January."""
|
|
||||||
if not hrms_installed():
|
|
||||||
return
|
|
||||||
year = getdate().year
|
|
||||||
for company in thai_companies():
|
|
||||||
ensure_payroll_period(company, year)
|
|
||||||
|
|
||||||
|
|
||||||
def after_app_install(app_name):
|
|
||||||
"""Payroll Period only exists once hrms is installed; catch companies that
|
|
||||||
were created before it."""
|
|
||||||
if app_name == "hrms":
|
|
||||||
setup_companies()
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
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.
Binary file not shown.
|
Before Width: | Height: | Size: 226 KiB |
@@ -1,29 +0,0 @@
|
|||||||
frappe.ui.form.on("Payment Entry", {
|
|
||||||
apply_tax_withholding_amount(frm) {
|
|
||||||
if (frm.doc.party_type !== "Customer" || frm.doc.payment_type !== "Receive") return;
|
|
||||||
if (!frm.doc.apply_tax_withholding_amount) {
|
|
||||||
frm.events.recompute_customer_withholding(frm);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// ERPNext's handler looks the category up on Supplier and clears it for a
|
|
||||||
// Customer; wait for that request to settle, then set the Customer's value.
|
|
||||||
frappe.db.get_value("Customer", frm.doc.party, "tax_withholding_category").then(({ message }) => {
|
|
||||||
frappe.after_ajax(() => {
|
|
||||||
frm.set_value("tax_withholding_category", message.tax_withholding_category);
|
|
||||||
frm.events.recompute_customer_withholding(frm);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
tax_withholding_category(frm) {
|
|
||||||
if (frm.doc.party_type === "Customer" && frm.doc.payment_type === "Receive") {
|
|
||||||
frm.events.recompute_customer_withholding(frm);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
recompute_customer_withholding(frm) {
|
|
||||||
// allocate_amount_to_references is overridden server-side to refresh the withholding row
|
|
||||||
if (!frm.doc.references || !frm.doc.references.length) return;
|
|
||||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
frappe.ui.form.on("Purchase Invoice", {
|
|
||||||
refresh(frm) {
|
|
||||||
// The certificate is issued once the tax has been withheld, i.e. on payment.
|
|
||||||
if (!frm.doc.apply_tds || frm.doc.docstatus !== 1 || frm.doc.status !== "Paid") return;
|
|
||||||
// The Withholding Tax Certificate print format renders the Revenue Department's
|
|
||||||
// Sec. 50 bis form (fixtures/print_format.json) as a PDF.
|
|
||||||
frm.add_custom_button(__("Withholding Tax Certificate"), () => {
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
doctype: frm.doctype,
|
|
||||||
name: frm.docname,
|
|
||||||
format: "Withholding Tax Certificate",
|
|
||||||
no_letterhead: 1,
|
|
||||||
_lang: "th",
|
|
||||||
});
|
|
||||||
const w = window.open(
|
|
||||||
frappe.urllib.get_full_url(
|
|
||||||
`/api/method/frappe.utils.print_format.download_pdf?${params}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (!w) frappe.msgprint(__("Please enable pop-ups"));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,517 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
import erpnext
|
|
||||||
import frappe
|
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import PaymentEntry
|
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry as _get_payment_entry
|
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import SalesInvoice
|
|
||||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
|
||||||
get_tax_withholding_details,
|
|
||||||
normal_round,
|
|
||||||
)
|
|
||||||
from frappe import _
|
|
||||||
from frappe.contacts.doctype.address.address import get_default_address
|
|
||||||
from frappe.modules.utils import sync_customizations_for_doctype
|
|
||||||
from frappe.utils import cint, flt, fmt_money, getdate
|
|
||||||
|
|
||||||
from default_thai_company.utils import money_in_words
|
|
||||||
|
|
||||||
# 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 = {
|
|
||||||
"Liability": ("Duties and Taxes", "Current Liabilities"),
|
|
||||||
"Asset": ("Tax Assets", "Current Assets"),
|
|
||||||
}
|
|
||||||
|
|
||||||
CATEGORY_FILTERS = {"name": ("like", "WHT %")}
|
|
||||||
|
|
||||||
|
|
||||||
def thai_companies():
|
|
||||||
return frappe.get_all("Company", filters={"country": "Thailand"}, pluck="name")
|
|
||||||
|
|
||||||
|
|
||||||
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}
|
|
||||||
)
|
|
||||||
if name:
|
|
||||||
return name
|
|
||||||
|
|
||||||
account = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Account",
|
|
||||||
"company": company,
|
|
||||||
"account_name": account_name,
|
|
||||||
"parent_account": find_parent_group(
|
|
||||||
company, root_type, parent_groups or PARENT_GROUPS[root_type]
|
|
||||||
),
|
|
||||||
"root_type": root_type,
|
|
||||||
"account_type": account_type,
|
|
||||||
"is_group": 0,
|
|
||||||
}
|
|
||||||
).insert(ignore_permissions=True)
|
|
||||||
return account.name
|
|
||||||
|
|
||||||
|
|
||||||
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},
|
|
||||||
)
|
|
||||||
if parent:
|
|
||||||
return parent
|
|
||||||
|
|
||||||
return frappe.db.get_value(
|
|
||||||
"Account",
|
|
||||||
{"company": company, "root_type": root_type, "is_group": 1, "parent_account": ("is", "not set")},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_company_accounts(company):
|
|
||||||
"""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):
|
|
||||||
"""Append `account` for `company` to the category's accounts table if missing."""
|
|
||||||
if any(row.company == company for row in category.accounts):
|
|
||||||
return False
|
|
||||||
category.append("accounts", {"company": company, "account": account})
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_fixture_accounts(doc, method=None):
|
|
||||||
"""Tax Withholding Category fixtures ship without `accounts` (company-specific).
|
|
||||||
|
|
||||||
Fixture import re-inserts the doc on every migrate, so: carry over the rows
|
|
||||||
already configured on this site, then link every Thai company, creating the
|
|
||||||
withholding accounts on first use. Mandatory is relaxed for sites without a
|
|
||||||
Thai company yet (setup wizard not run); `setup_company` links them later.
|
|
||||||
"""
|
|
||||||
doc.flags.ignore_mandatory = True
|
|
||||||
|
|
||||||
if not doc.accounts and frappe.db.exists(doc.doctype, doc.name):
|
|
||||||
for row in frappe.get_all(
|
|
||||||
"Tax Withholding Account",
|
|
||||||
filters={"parent": doc.name, "parenttype": doc.doctype},
|
|
||||||
fields=["company", "account"],
|
|
||||||
order_by="idx",
|
|
||||||
):
|
|
||||||
doc.append("accounts", row)
|
|
||||||
|
|
||||||
for company in thai_companies():
|
|
||||||
if not any(row.company == company for row in doc.accounts):
|
|
||||||
link_company(doc, company, ensure_company_accounts(company))
|
|
||||||
|
|
||||||
|
|
||||||
def sync_category_customizations():
|
|
||||||
"""before_install / before_migrate: create the Tax Withholding Category Custom
|
|
||||||
Fields (custom/tax_withholding_category.json) ahead of the fixture import.
|
|
||||||
Frappe syncs fixtures before customizations, and a fixture value for a field
|
|
||||||
that does not exist yet is dropped."""
|
|
||||||
folder = frappe.get_app_path("default_thai_company", "default_thai_company", "custom")
|
|
||||||
filename = "tax_withholding_category.json"
|
|
||||||
with open(os.path.join(folder, filename)) as f:
|
|
||||||
sync_customizations_for_doctype(json.load(f), folder, filename)
|
|
||||||
|
|
||||||
|
|
||||||
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 the tax accounts and link every WHT category."""
|
|
||||||
if not company_ready(doc):
|
|
||||||
return
|
|
||||||
|
|
||||||
account = ensure_company_accounts(doc.name)
|
|
||||||
for name in frappe.get_all("Tax Withholding Category", filters=CATEGORY_FILTERS, pluck="name"):
|
|
||||||
category = frappe.get_doc("Tax Withholding Category", name)
|
|
||||||
if link_company(category, doc.name, account):
|
|
||||||
category.save(ignore_permissions=True)
|
|
||||||
|
|
||||||
|
|
||||||
def is_thai_company(company):
|
|
||||||
return frappe.get_cached_value("Company", company, "country") == "Thailand"
|
|
||||||
|
|
||||||
|
|
||||||
def get_withholding_details(category, posting_date, company):
|
|
||||||
details = get_tax_withholding_details(category, posting_date, company)
|
|
||||||
if not details:
|
|
||||||
frappe.throw(
|
|
||||||
_("Tax Withholding Category {0} has no account for Company {1}").format(category, company)
|
|
||||||
)
|
|
||||||
return details
|
|
||||||
|
|
||||||
|
|
||||||
def withholding_on(details, taxable, base_taxable, precision):
|
|
||||||
"""Withholding for a taxable amount; the single threshold is checked in company currency."""
|
|
||||||
if details.threshold and flt(base_taxable) < flt(details.threshold):
|
|
||||||
return 0.0
|
|
||||||
amount = flt(taxable) * flt(details.rate) / 100
|
|
||||||
return normal_round(amount) if cint(details.round_off_tax_amount) else flt(amount, precision)
|
|
||||||
|
|
||||||
|
|
||||||
class ThaiSalesInvoice(SalesInvoice):
|
|
||||||
def set_tax_withholding(self):
|
|
||||||
"""Thai customers withhold at payment (see `set_customer_withholding`);
|
|
||||||
ERPNext's customer-side handling is Indian TCS, which adds tax on top of
|
|
||||||
the invoice. For Thai companies only show the expected withholding and
|
|
||||||
the net payable; totals and GL are untouched."""
|
|
||||||
if not is_thai_company(self.company):
|
|
||||||
return super().set_tax_withholding()
|
|
||||||
|
|
||||||
self.withholding_tax_amount = self.amount_after_withholding = 0
|
|
||||||
if not self.tax_withholding_category:
|
|
||||||
return
|
|
||||||
|
|
||||||
details = get_withholding_details(self.tax_withholding_category, self.posting_date, self.company)
|
|
||||||
self.withholding_tax_amount = withholding_on(
|
|
||||||
details, self.net_total, self.base_net_total, self.precision("withholding_tax_amount")
|
|
||||||
)
|
|
||||||
if self.withholding_tax_amount:
|
|
||||||
self.amount_after_withholding = flt(
|
|
||||||
(self.rounded_total or self.grand_total) - self.withholding_tax_amount,
|
|
||||||
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):
|
|
||||||
def build_gl_map(self):
|
|
||||||
"""A "Deduct" tax row on a receipt posts bank Dr gross and bank Cr
|
|
||||||
withholding; ERPNext merges them into one entry but only nets it when
|
|
||||||
the bank entry itself carries `post_net_value`. Set it so the bank
|
|
||||||
ledger shows the amount that actually arrived."""
|
|
||||||
gl_entries = super().build_gl_map()
|
|
||||||
if self.payment_type == "Receive" and self.party_type == "Customer" and self.get("taxes"):
|
|
||||||
for entry in gl_entries:
|
|
||||||
if entry.account == self.paid_to:
|
|
||||||
entry.post_net_value = True
|
|
||||||
return gl_entries
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def allocate_amount_to_references(self, *args, **kwargs):
|
|
||||||
"""Called by the form after Get Outstanding Invoices and on Paid Amount
|
|
||||||
changes; keep the withholding row in step with the allocation."""
|
|
||||||
super().allocate_amount_to_references(*args, **kwargs)
|
|
||||||
if self.party_type == "Customer" and self.payment_type == "Receive" and self.source_exchange_rate:
|
|
||||||
auto_apply_customer_withholding(self)
|
|
||||||
set_customer_withholding(self)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_payment_entry(
|
|
||||||
dt,
|
|
||||||
dn,
|
|
||||||
party_amount=None,
|
|
||||||
bank_account=None,
|
|
||||||
bank_amount=None,
|
|
||||||
party_type=None,
|
|
||||||
payment_type=None,
|
|
||||||
reference_date=None,
|
|
||||||
created_from_payment_request=False,
|
|
||||||
):
|
|
||||||
"""Create > Payment from a Sales Invoice: arrive with withholding applied.
|
|
||||||
|
|
||||||
Signature mirrors ERPNext's so `frappe.call` drops request-only args
|
|
||||||
(`cmd`, ...) instead of forwarding them.
|
|
||||||
"""
|
|
||||||
pe = _get_payment_entry(
|
|
||||||
dt,
|
|
||||||
dn,
|
|
||||||
party_amount=party_amount,
|
|
||||||
bank_account=bank_account,
|
|
||||||
bank_amount=bank_amount,
|
|
||||||
party_type=party_type,
|
|
||||||
payment_type=payment_type,
|
|
||||||
reference_date=reference_date,
|
|
||||||
created_from_payment_request=created_from_payment_request,
|
|
||||||
)
|
|
||||||
if auto_apply_customer_withholding(pe) and pe.source_exchange_rate:
|
|
||||||
set_customer_withholding(pe)
|
|
||||||
return pe
|
|
||||||
|
|
||||||
|
|
||||||
def auto_apply_customer_withholding(doc):
|
|
||||||
"""Tick Apply Tax Withholding Amount on a new customer receipt whose
|
|
||||||
references include a Sales Invoice that carries withholding. Only for
|
|
||||||
unsaved entries: after the first save the checkbox is the user's."""
|
|
||||||
if (
|
|
||||||
doc.party_type != "Customer"
|
|
||||||
or doc.payment_type != "Receive"
|
|
||||||
or doc.apply_tax_withholding_amount
|
|
||||||
or not doc.is_new()
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
|
|
||||||
invoices = [r.reference_name for r in doc.references if r.reference_doctype == "Sales Invoice"]
|
|
||||||
if not invoices or not frappe.db.exists(
|
|
||||||
"Sales Invoice", {"name": ("in", invoices), "withholding_tax_amount": (">", 0)}
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
|
|
||||||
doc.apply_tax_withholding_amount = 1
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def set_customer_withholding(doc, method=None):
|
|
||||||
"""Payment Entry.validate: tax withheld by the customer on a Receive entry
|
|
||||||
is posted like ERPNext's supplier TDS, as a "Deduct" row in the taxes
|
|
||||||
table against the withholding receivable account.
|
|
||||||
|
|
||||||
`paid_amount` is the gross amount settled against the invoices (what
|
|
||||||
"Get Outstanding Invoices" fills in); the bank receives paid minus the
|
|
||||||
withholding (`received_amount_after_tax`). Withholding is rate x pre-VAT
|
|
||||||
amount of each allocated reference, prorated by the allocation. Runs after
|
|
||||||
the controller's validate, then re-runs the tax computation so the row's
|
|
||||||
base amounts and totals are final within this save.
|
|
||||||
"""
|
|
||||||
if doc.party_type != "Customer" or doc.payment_type != "Receive":
|
|
||||||
return
|
|
||||||
|
|
||||||
if method == "validate":
|
|
||||||
auto_apply_customer_withholding(doc)
|
|
||||||
|
|
||||||
account = frappe.db.get_value(
|
|
||||||
"Account",
|
|
||||||
{"company": doc.company, "account_name": ASSET_ACCOUNT, "root_type": "Asset", "is_group": 0},
|
|
||||||
)
|
|
||||||
row = next((d for d in doc.taxes if d.account_head == account), None) if account else None
|
|
||||||
|
|
||||||
if not doc.apply_tax_withholding_amount:
|
|
||||||
if row:
|
|
||||||
doc.remove(row)
|
|
||||||
doc.apply_taxes()
|
|
||||||
doc.set_amounts_after_tax()
|
|
||||||
return
|
|
||||||
|
|
||||||
if not account:
|
|
||||||
frappe.throw(_("Account {0} not found for Company {1}").format(ASSET_ACCOUNT, doc.company))
|
|
||||||
|
|
||||||
if not doc.tax_withholding_category:
|
|
||||||
doc.tax_withholding_category = frappe.db.get_value("Customer", doc.party, "tax_withholding_category")
|
|
||||||
|
|
||||||
amount, description = get_customer_withholding(doc)
|
|
||||||
|
|
||||||
if not amount:
|
|
||||||
if row:
|
|
||||||
doc.remove(row)
|
|
||||||
else:
|
|
||||||
if not row:
|
|
||||||
row = doc.append(
|
|
||||||
"taxes", {"account_head": account, "charge_type": "Actual", "add_deduct_tax": "Deduct"}
|
|
||||||
)
|
|
||||||
row.tax_amount = amount
|
|
||||||
row.description = description
|
|
||||||
row.cost_center = doc.cost_center or erpnext.get_default_cost_center(doc.company)
|
|
||||||
|
|
||||||
doc.apply_taxes()
|
|
||||||
doc.set_amounts_after_tax()
|
|
||||||
|
|
||||||
|
|
||||||
def get_customer_withholding(doc):
|
|
||||||
"""Total withheld across allocated references, in company currency.
|
|
||||||
|
|
||||||
A Sales Invoice's own category wins over the Payment Entry's; Sales Orders
|
|
||||||
use the Payment Entry's. Withholding is prorated by allocated / grand total
|
|
||||||
on the pre-VAT amount, per reference.
|
|
||||||
"""
|
|
||||||
total = 0.0
|
|
||||||
details_by_category = {}
|
|
||||||
precision = doc.precision("difference_amount")
|
|
||||||
|
|
||||||
for ref in doc.references:
|
|
||||||
if ref.reference_doctype not in ("Sales Invoice", "Sales Order") or not ref.allocated_amount:
|
|
||||||
continue
|
|
||||||
|
|
||||||
fields = ["net_total", "grand_total"]
|
|
||||||
if ref.reference_doctype == "Sales Invoice":
|
|
||||||
fields.append("tax_withholding_category")
|
|
||||||
values = frappe.db.get_value(ref.reference_doctype, ref.reference_name, fields, as_dict=True)
|
|
||||||
if not values.grand_total:
|
|
||||||
continue
|
|
||||||
|
|
||||||
category = values.get("tax_withholding_category") or doc.tax_withholding_category
|
|
||||||
if not category:
|
|
||||||
frappe.throw(
|
|
||||||
_("Please set Tax Withholding Category on {0} {1} or on this Payment Entry").format(
|
|
||||||
_(ref.reference_doctype), ref.reference_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if category not in details_by_category:
|
|
||||||
details_by_category[category] = get_withholding_details(category, doc.posting_date, doc.company)
|
|
||||||
|
|
||||||
base_taxable = (
|
|
||||||
flt(ref.allocated_amount)
|
|
||||||
* flt(values.net_total)
|
|
||||||
/ flt(values.grand_total)
|
|
||||||
* flt(doc.source_exchange_rate)
|
|
||||||
)
|
|
||||||
total += withholding_on(details_by_category[category], base_taxable, base_taxable, precision)
|
|
||||||
|
|
||||||
if len(details_by_category) == 1:
|
|
||||||
# category_name; translations/th.csv carries the fixture categories
|
|
||||||
description = _(next(iter(details_by_category.values())).description)
|
|
||||||
else:
|
|
||||||
description = _("Withholding tax deducted by customer")
|
|
||||||
return flt(total, precision), description
|
|
||||||
|
|
||||||
|
|
||||||
# Withholding tax certificate (หนังสือรับรองการหักภาษี ณ ที่จ่าย, Sec. 50 bis): the income
|
|
||||||
# row the payment is reported on, from Tax Withholding Category.income_type (custom field).
|
|
||||||
# Rows: 1 = Sec. 40(1), 2 = 40(2), 3 = 40(3), 4a = 40(4)(a), 4b = 40(4)(b) dividends
|
|
||||||
# paid from profits taxed at the standard 20% rate (form row 4(b)(1)(1.3)), 5 = payments
|
|
||||||
# withheld under Revenue Department orders issued under Sec. 3 tera, 6 = other (specified).
|
|
||||||
CERTIFICATE_ROWS = {
|
|
||||||
"Salary and Wages - Sec. 40(1)": "1",
|
|
||||||
"Fees and Commissions - Sec. 40(2)": "2",
|
|
||||||
"Royalties - Sec. 40(3)": "3",
|
|
||||||
"Interest - Sec. 40(4)(a)": "4a",
|
|
||||||
"Dividends - Sec. 40(4)(b)": "4b",
|
|
||||||
"Sec. 3 Tera (Services, Rent, Contract Work etc.)": "5",
|
|
||||||
}
|
|
||||||
|
|
||||||
THAI_MONTHS = (
|
|
||||||
"มกราคม",
|
|
||||||
"กุมภาพันธ์",
|
|
||||||
"มีนาคม",
|
|
||||||
"เมษายน",
|
|
||||||
"พฤษภาคม",
|
|
||||||
"มิถุนายน",
|
|
||||||
"กรกฎาคม",
|
|
||||||
"สิงหาคม",
|
|
||||||
"กันยายน",
|
|
||||||
"ตุลาคม",
|
|
||||||
"พฤศจิกายน",
|
|
||||||
"ธันวาคม",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def pnd_form(row, supplier_type):
|
|
||||||
"""P.N.D. return the certificate row is filed on, as numbered on the form:
|
|
||||||
1 = ภ.ง.ด.1ก, 2 = ภ.ง.ด.2, 3 = ภ.ง.ด.3, 53 = ภ.ง.ด.53.
|
|
||||||
|
|
||||||
Juristic payees (Company; Partnership, taken as registered) file on
|
|
||||||
ภ.ง.ด.53 whatever the income; individuals by income type: salary on
|
|
||||||
ภ.ง.ด.1ก, Sec. 40(3)/(4) on ภ.ง.ด.2, the rest on ภ.ง.ด.3.
|
|
||||||
"""
|
|
||||||
if supplier_type != "Individual":
|
|
||||||
return "53"
|
|
||||||
if row == "1":
|
|
||||||
return "1"
|
|
||||||
if row in ("3", "4a", "4b"):
|
|
||||||
return "2"
|
|
||||||
return "3"
|
|
||||||
|
|
||||||
|
|
||||||
def one_line_address(address_name):
|
|
||||||
"""Address in the order of the Thailand Address Template, on one line."""
|
|
||||||
if not address_name:
|
|
||||||
return None
|
|
||||||
address = frappe.get_cached_doc("Address", address_name)
|
|
||||||
parts = [
|
|
||||||
address.address_line1,
|
|
||||||
address.address_line2,
|
|
||||||
address.county,
|
|
||||||
address.city,
|
|
||||||
address.state,
|
|
||||||
address.pincode,
|
|
||||||
]
|
|
||||||
if address.country and address.country != "Thailand":
|
|
||||||
parts.append(address.country)
|
|
||||||
return " ".join(part.strip() for part in parts if part and part.strip())
|
|
||||||
|
|
||||||
|
|
||||||
def tax_id_digits(tax_id):
|
|
||||||
"""The 13 digits of a Thai tax ID for the form's boxes, or None when it is not one."""
|
|
||||||
digits = "".join(ch for ch in (tax_id or "") if ch.isdigit())
|
|
||||||
return digits if len(digits) == 13 else None
|
|
||||||
|
|
||||||
|
|
||||||
def get_withholding_certificate(doc):
|
|
||||||
"""Certificate data for a Purchase Invoice with Apply Tax Withholding Amount.
|
|
||||||
|
|
||||||
Amounts are in company currency; the form is Thai, so dates are Buddhist Era
|
|
||||||
and the tax in words is Thai whatever the print language.
|
|
||||||
"""
|
|
||||||
company = frappe.get_cached_doc("Company", doc.company)
|
|
||||||
currency = erpnext.get_company_currency(doc.company)
|
|
||||||
precision = doc.precision("base_grand_total")
|
|
||||||
|
|
||||||
category = (
|
|
||||||
frappe.get_cached_doc("Tax Withholding Category", doc.tax_withholding_category)
|
|
||||||
if doc.tax_withholding_category
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
row = CERTIFICATE_ROWS.get(category.income_type if category else None, "6")
|
|
||||||
supplier_type = frappe.get_cached_value("Supplier", doc.supplier, "supplier_type")
|
|
||||||
|
|
||||||
amount = flt(doc.base_tax_withholding_net_total, precision)
|
|
||||||
tax = flt(
|
|
||||||
sum(abs(flt(t.base_tax_amount)) for t in doc.get("taxes") if cint(t.is_tax_withholding_account)),
|
|
||||||
precision,
|
|
||||||
)
|
|
||||||
posting_date = getdate(doc.posting_date)
|
|
||||||
|
|
||||||
return frappe._dict(
|
|
||||||
payer=frappe._dict(
|
|
||||||
name=company.company_name,
|
|
||||||
tax_id=company.tax_id,
|
|
||||||
tax_id_digits=tax_id_digits(company.tax_id),
|
|
||||||
address=one_line_address(get_default_address("Company", doc.company)),
|
|
||||||
),
|
|
||||||
payee=frappe._dict(
|
|
||||||
name=doc.supplier_name,
|
|
||||||
tax_id=doc.tax_id,
|
|
||||||
tax_id_digits=tax_id_digits(doc.tax_id),
|
|
||||||
address=one_line_address(doc.supplier_address or get_default_address("Supplier", doc.supplier)),
|
|
||||||
),
|
|
||||||
row=row,
|
|
||||||
# row 6 prints what was paid for; translations/th.csv carries the fixture categories
|
|
||||||
row_note=_(category.category_name) if row == "6" and category else None,
|
|
||||||
pnd=pnd_form(row, supplier_type),
|
|
||||||
date=f"{posting_date.day:02d}/{posting_date.month:02d}/{posting_date.year + 543}",
|
|
||||||
amount=fmt_money(amount, precision),
|
|
||||||
tax=fmt_money(tax, precision),
|
|
||||||
tax_in_words=money_in_words(tax, currency, lang="th"),
|
|
||||||
issued=frappe._dict(
|
|
||||||
day=posting_date.day,
|
|
||||||
month=THAI_MONTHS[posting_date.month - 1],
|
|
||||||
year=posting_date.year + 543,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
{#- 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>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{#- 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>
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
{#- หนังสือรับรองการหักภาษี ณ ที่จ่าย (Sec. 50 bis certificate) for a Purchase Invoice.
|
|
||||||
|
|
||||||
The Revenue Department's fillable form (approve_wh3_081156.pdf, A4 = 595 x 842 pt) is the page
|
|
||||||
background; every value sits in the rectangle of the corresponding AcroForm field, given here as
|
|
||||||
the PDF's (x0, y0, x1, y1) with the origin at the bottom-left so the numbers can be checked against
|
|
||||||
the form. Two identical copies are printed: copy 1 goes with the payee's tax return, copy 2 is the
|
|
||||||
payee's record. -#}
|
|
||||||
{%- set c = get_withholding_certificate(doc) -%}
|
|
||||||
{%- set rows = {
|
|
||||||
"1": {"date": (327, 533, 403, 546), "pay": (411, 533, 490, 547), "tax": (496, 534, 560, 547)},
|
|
||||||
"2": {"date": (328, 519, 403, 533), "pay": (410, 520, 489, 534), "tax": (496, 519, 560, 532)},
|
|
||||||
"3": {"date": (328, 504, 403, 518), "pay": (411, 504, 490, 517), "tax": (496, 504, 560, 517)},
|
|
||||||
"4a": {"date": (328, 490, 403, 505), "pay": (412, 490, 491, 504), "tax": (496, 490, 560, 503)},
|
|
||||||
"4b": {"date": (328, 402, 404, 416), "pay": (410, 403, 489, 417), "tax": (497, 403, 562, 416)},
|
|
||||||
"5": {"date": (327, 216, 403, 230), "pay": (409, 216, 489, 230), "tax": (496, 215, 561, 230)},
|
|
||||||
"6": {"date": (327, 199, 403, 213), "pay": (409, 199, 489, 213), "tax": (496, 198, 561, 213)},
|
|
||||||
} -%}
|
|
||||||
{%- set pnd_boxes = {
|
|
||||||
"1": (209, 603, 222, 615),
|
|
||||||
"2": (395, 602, 407, 615),
|
|
||||||
"3": (471, 602, 484, 615),
|
|
||||||
"53": (395, 584, 407, 597),
|
|
||||||
} -%}
|
|
||||||
{#- Left edges of the printed 13-digit ID boxes (12 pt wide, grouped 1-4-5-2-1), measured on the
|
|
||||||
form; the AcroForm comb field does not line up with them. -#}
|
|
||||||
{%- set id_cells = (375, 393, 405, 417, 429, 447.5, 459.5, 471.5, 483.5, 495.5, 513.5, 525.5, 545) -%}
|
|
||||||
|
|
||||||
{%- macro box(rect, text, align="left", size=10) -%}
|
|
||||||
<div class="f" style="left: {{ rect[0] }}pt; top: {{ 842 - rect[3] }}pt; width: {{ rect[2] - rect[0] }}pt; height: {{ rect[3] - rect[1] }}pt; line-height: {{ rect[3] - rect[1] }}pt; text-align: {{ align }}; font-size: {{ size }}pt;">{{ text }}</div>
|
|
||||||
{%- endmacro -%}
|
|
||||||
|
|
||||||
{%- macro tick(rect) -%}
|
|
||||||
<div class="tick" style="left: {{ rect[0] }}pt; top: {{ 842 - rect[3] }}pt; width: {{ rect[2] - rect[0] }}pt; height: {{ rect[3] - rect[1] }}pt;"></div>
|
|
||||||
{%- endmacro -%}
|
|
||||||
|
|
||||||
{%- macro tax_id(rect, party) -%}
|
|
||||||
{%- if party.tax_id_digits -%}
|
|
||||||
{%- for digit in party.tax_id_digits -%}
|
|
||||||
{%- set x = id_cells[loop.index0] -%}
|
|
||||||
{{ box((x, rect[1], x + 12, rect[3]), digit, "center") }}
|
|
||||||
{%- endfor -%}
|
|
||||||
{%- elif party.tax_id -%}
|
|
||||||
{{ box(rect, party.tax_id, "center") }}
|
|
||||||
{%- endif -%}
|
|
||||||
{%- endmacro -%}
|
|
||||||
|
|
||||||
{%- macro amount_row(key) -%}
|
|
||||||
{{ box(rows[key].date, c.date, "center") }}
|
|
||||||
{{ box(rows[key].pay, c.amount, "right") }}
|
|
||||||
{{ box(rows[key].tax, c.tax, "right") }}
|
|
||||||
{%- endmacro -%}
|
|
||||||
|
|
||||||
{%- for _ in range(2) -%}
|
|
||||||
<div class="wht-page">
|
|
||||||
<img class="wht-form" src="/assets/default_thai_company/images/withholding_tax_certificate.png" alt="">
|
|
||||||
|
|
||||||
{#- Invoice reference in the top-right margin; เล่มที่ / เลขที่ are the certificate book's numbers. -#}
|
|
||||||
{{ box((400, 821, 560, 835), doc.name, "right", 8) }}
|
|
||||||
|
|
||||||
{#- ผู้มีหน้าที่หักภาษี ณ ที่จ่าย: the company -#}
|
|
||||||
{{ tax_id((375, 744, 558, 759), c.payer) }}
|
|
||||||
{{ box((54, 729, 316, 745), c.payer.name) }}
|
|
||||||
{{ box((61, 706, 550, 722), c.payer.address or "", "left", 9) }}
|
|
||||||
|
|
||||||
{#- ผู้ถูกหักภาษี ณ ที่จ่าย: the supplier -#}
|
|
||||||
{{ tax_id((375, 676, 558, 690), c.payee) }}
|
|
||||||
{{ box((53, 658, 315, 671), c.payee.name) }}
|
|
||||||
{{ box((59, 627, 550, 643), c.payee.address or "", "left", 9) }}
|
|
||||||
|
|
||||||
{{ tick(pnd_boxes[c.pnd]) }}
|
|
||||||
|
|
||||||
{%- if c.row == "6" %}
|
|
||||||
{{ box((96, 197, 325, 214), c.row_note or "", "left", 8) }}
|
|
||||||
{%- endif %}
|
|
||||||
{{ amount_row(c.row) }}
|
|
||||||
{{ box((409, 180, 488, 196), c.amount, "right") }}
|
|
||||||
{{ box((496, 180, 560, 196), c.tax, "right") }}
|
|
||||||
{{ box((185, 158, 558, 177), c.tax_in_words) }}
|
|
||||||
|
|
||||||
{#- ผู้จ่ายเงิน (1) หัก ณ ที่จ่าย -#}
|
|
||||||
{{ tick((82, 119, 94, 131)) }}
|
|
||||||
|
|
||||||
{{ box((342, 72, 366, 87), c.issued.day, "center") }}
|
|
||||||
{{ box((364, 71, 428, 87), c.issued.month, "center") }}
|
|
||||||
{{ box((429, 72, 470, 87), c.issued.year, "center") }}
|
|
||||||
</div>
|
|
||||||
{%- endfor -%}
|
|
||||||
@@ -1,422 +0,0 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
import frappe
|
|
||||||
from frappe.modules.import_file import import_file_by_path
|
|
||||||
from frappe.modules.utils import sync_customizations_for_doctype
|
|
||||||
from frappe.tests.utils import FrappeTestCase
|
|
||||||
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,
|
|
||||||
get_withholding_certificate,
|
|
||||||
thai_companies,
|
|
||||||
)
|
|
||||||
|
|
||||||
COMPANY = "_Test WHT Company"
|
|
||||||
ABBR = "_TWC"
|
|
||||||
CUSTOMER = "_Test WHT Customer"
|
|
||||||
SUPPLIER = "_Test WHT Supplier"
|
|
||||||
INDIVIDUAL = "_Test WHT Individual"
|
|
||||||
ITEM = "_Test WHT Service"
|
|
||||||
FIXTURE = frappe.get_app_path("default_thai_company", "fixtures", "tax_withholding_category.json")
|
|
||||||
CUSTOM_DIR = frappe.get_app_path("default_thai_company", "default_thai_company", "custom")
|
|
||||||
|
|
||||||
|
|
||||||
class TestTaxWithholding(FrappeTestCase):
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
super().setUpClass()
|
|
||||||
# Custom Fields first: the category fixtures carry income_type. Fixture
|
|
||||||
# categories must exist before the company is created so the Company hook
|
|
||||||
# has something to link; force=True re-imports.
|
|
||||||
for fname in ("tax_withholding_category.json", "sales_invoice.json", "payment_entry.json"):
|
|
||||||
with open(f"{CUSTOM_DIR}/{fname}") as f:
|
|
||||||
sync_customizations_for_doctype(json.load(f), CUSTOM_DIR, fname)
|
|
||||||
import_file_by_path(FIXTURE, force=True, data_import=True)
|
|
||||||
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Company",
|
|
||||||
"company_name": COMPANY,
|
|
||||||
"abbr": ABBR,
|
|
||||||
"country": "Thailand",
|
|
||||||
"default_currency": "THB",
|
|
||||||
"chart_of_accounts": "Standard",
|
|
||||||
"tax_id": "0105551234567",
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
cls.payable = f"{LIABILITY_ACCOUNT} - {ABBR}"
|
|
||||||
cls.receivable = f"{ASSET_ACCOUNT} - {ABBR}"
|
|
||||||
cls.vat = frappe.get_doc("Account", f"{OUTPUT_VAT_ACCOUNT} - {ABBR}")
|
|
||||||
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Customer",
|
|
||||||
"customer_name": CUSTOMER,
|
|
||||||
"customer_type": "Company",
|
|
||||||
"customer_group": frappe.db.get_value("Customer Group", {"is_group": 0}),
|
|
||||||
"territory": frappe.db.get_value("Territory", {"is_group": 0}),
|
|
||||||
"tax_withholding_category": "WHT 3% - Service",
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Item",
|
|
||||||
"item_code": ITEM,
|
|
||||||
"item_group": frappe.db.get_value("Item Group", {"is_group": 0}),
|
|
||||||
"is_stock_item": 0,
|
|
||||||
"stock_uom": "Nos",
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
supplier_group = frappe.db.get_value("Supplier Group", {"is_group": 0})
|
|
||||||
for name, supplier_type, tax_id in (
|
|
||||||
(SUPPLIER, "Company", "0-1234-56789-01-2"),
|
|
||||||
(INDIVIDUAL, "Individual", "1234567890123"),
|
|
||||||
):
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Supplier",
|
|
||||||
"supplier_name": name,
|
|
||||||
"supplier_type": supplier_type,
|
|
||||||
"supplier_group": supplier_group,
|
|
||||||
"tax_id": tax_id,
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Address",
|
|
||||||
"address_title": SUPPLIER,
|
|
||||||
"address_type": "Billing",
|
|
||||||
"address_line1": "99/9 Moo 5",
|
|
||||||
"address_line2": "Soi Sukhumvit 24",
|
|
||||||
"city": "Khlong Toei",
|
|
||||||
"state": "Bangkok",
|
|
||||||
"pincode": "10110",
|
|
||||||
"country": "Thailand",
|
|
||||||
"links": [{"link_doctype": "Supplier", "link_name": SUPPLIER}],
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
def make_invoice(self, rate=10000, category=None, inclusive=False):
|
|
||||||
si = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Sales Invoice",
|
|
||||||
"company": COMPANY,
|
|
||||||
"customer": CUSTOMER,
|
|
||||||
"posting_date": nowdate(),
|
|
||||||
"due_date": nowdate(),
|
|
||||||
"tax_withholding_category": category,
|
|
||||||
"items": [{"item_code": ITEM, "qty": 1, "rate": rate}],
|
|
||||||
"taxes": [
|
|
||||||
{
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"account_head": self.vat.name,
|
|
||||||
"rate": 7,
|
|
||||||
"description": "VAT 7%",
|
|
||||||
"included_in_print_rate": int(inclusive),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
si.set_missing_values()
|
|
||||||
return si.submit()
|
|
||||||
|
|
||||||
def make_receipt(self, invoice, allocated, apply=1):
|
|
||||||
"""Paid amount is the gross allocation; withholding reduces what reaches the bank."""
|
|
||||||
return frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Payment Entry",
|
|
||||||
"company": COMPANY,
|
|
||||||
"payment_type": "Receive",
|
|
||||||
"party_type": "Customer",
|
|
||||||
"party": CUSTOMER,
|
|
||||||
"posting_date": nowdate(),
|
|
||||||
"paid_from": f"Debtors - {ABBR}",
|
|
||||||
"paid_to": f"Cash - {ABBR}",
|
|
||||||
"paid_amount": allocated,
|
|
||||||
"received_amount": allocated,
|
|
||||||
"apply_tax_withholding_amount": apply,
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"reference_doctype": "Sales Invoice",
|
|
||||||
"reference_name": invoice.name,
|
|
||||||
"allocated_amount": allocated,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
def withheld(self, pe):
|
|
||||||
return [(t.account_head, t.add_deduct_tax, t.tax_amount) for t in pe.taxes]
|
|
||||||
|
|
||||||
def make_purchase_invoice(self, supplier, category, rate=100000):
|
|
||||||
pi = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Purchase Invoice",
|
|
||||||
"company": COMPANY,
|
|
||||||
"supplier": supplier,
|
|
||||||
"set_posting_time": 1,
|
|
||||||
"posting_date": "2026-09-16",
|
|
||||||
"due_date": "2026-09-16",
|
|
||||||
"apply_tds": 1,
|
|
||||||
"tax_withholding_category": category,
|
|
||||||
"items": [{"item_code": ITEM, "qty": 1, "rate": rate}],
|
|
||||||
"taxes": [
|
|
||||||
{
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"account_head": f"{INPUT_VAT_ACCOUNT} - {ABBR}",
|
|
||||||
"rate": 7,
|
|
||||||
"description": "VAT 7%",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
pi.set_missing_values()
|
|
||||||
return pi.insert()
|
|
||||||
|
|
||||||
def category_account(self, category, company=COMPANY):
|
|
||||||
return frappe.db.get_value(
|
|
||||||
"Tax Withholding Account",
|
|
||||||
{"parent": category, "parenttype": "Tax Withholding Category", "company": company},
|
|
||||||
"account",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_company_creation_adds_accounts_and_links_categories(self):
|
|
||||||
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(
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
self.assertEqual(len(categories), 22)
|
|
||||||
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_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):
|
|
||||||
alt = frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Account",
|
|
||||||
"company": COMPANY,
|
|
||||||
"account_name": "Alternative WHT Payable",
|
|
||||||
"parent_account": f"Duties and Taxes - {ABBR}",
|
|
||||||
"account_type": "Tax",
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
rent = frappe.get_doc("Tax Withholding Category", "WHT 5% - Rent")
|
|
||||||
for row in rent.accounts:
|
|
||||||
if row.company == COMPANY:
|
|
||||||
row.account = alt.name
|
|
||||||
rent.save()
|
|
||||||
|
|
||||||
import_file_by_path(FIXTURE, force=True, data_import=True)
|
|
||||||
|
|
||||||
self.assertEqual(self.category_account("WHT 5% - Rent"), alt.name)
|
|
||||||
self.assertEqual(self.category_account("WHT 3% - Service"), self.payable)
|
|
||||||
rates = frappe.get_doc("Tax Withholding Category", "WHT 5% - Rent").rates
|
|
||||||
self.assertEqual([(r.tax_withholding_rate, r.single_threshold) for r in rates], [(5.0, 1000.0)])
|
|
||||||
|
|
||||||
def test_sales_invoice_shows_withholding_without_grossing_up(self):
|
|
||||||
si = self.make_invoice()
|
|
||||||
self.assertEqual((si.net_total, si.grand_total), (10000.0, 10700.0))
|
|
||||||
self.assertEqual([t.account_head for t in si.taxes], [self.vat.name])
|
|
||||||
self.assertEqual(si.tax_withholding_category, "WHT 3% - Service") # fetched from Customer
|
|
||||||
self.assertEqual((si.withholding_tax_amount, si.amount_after_withholding), (300.0, 10400.0))
|
|
||||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 10700.0)
|
|
||||||
|
|
||||||
def test_sales_invoice_below_threshold_shows_nothing(self):
|
|
||||||
si = self.make_invoice(rate=800)
|
|
||||||
self.assertEqual((si.withholding_tax_amount, si.amount_after_withholding), (0.0, 0.0))
|
|
||||||
|
|
||||||
def test_receipt_uses_invoice_category_over_customer_category(self):
|
|
||||||
si = self.make_invoice(category="WHT 5% - Rent")
|
|
||||||
self.assertEqual(si.withholding_tax_amount, 500.0)
|
|
||||||
pe = self.make_receipt(si, allocated=10700)
|
|
||||||
self.assertEqual(self.withheld(pe), [(self.receivable, "Deduct", 500.0)])
|
|
||||||
self.assertEqual(pe.received_amount_after_tax, 10200.0)
|
|
||||||
|
|
||||||
def test_receipt_deducts_withholding_and_settles_invoice(self):
|
|
||||||
si = self.make_invoice()
|
|
||||||
pe = self.make_receipt(si, allocated=10700)
|
|
||||||
|
|
||||||
self.assertEqual(pe.tax_withholding_category, "WHT 3% - Service")
|
|
||||||
self.assertEqual(self.withheld(pe), [(self.receivable, "Deduct", 300.0)])
|
|
||||||
self.assertEqual((pe.paid_amount, pe.received_amount_after_tax), (10700.0, 10400.0))
|
|
||||||
self.assertEqual((pe.unallocated_amount, pe.difference_amount), (0, 0))
|
|
||||||
|
|
||||||
pe.submit()
|
|
||||||
gl = {}
|
|
||||||
for g in frappe.get_all(
|
|
||||||
"GL Entry", filters={"voucher_no": pe.name}, fields=["account", "debit", "credit"]
|
|
||||||
):
|
|
||||||
gl.setdefault(g.account, [0, 0])
|
|
||||||
gl[g.account][0] += g.debit
|
|
||||||
gl[g.account][1] += g.credit
|
|
||||||
self.assertEqual(gl[f"Cash - {ABBR}"], [10400.0, 0.0])
|
|
||||||
self.assertEqual(gl[self.receivable], [300.0, 0.0])
|
|
||||||
self.assertEqual(gl[f"Debtors - {ABBR}"], [0.0, 10700.0])
|
|
||||||
self.assertEqual(len(gl), 3)
|
|
||||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 0)
|
|
||||||
|
|
||||||
def test_inclusive_vat_invoice_withholds_on_pre_vat_amount(self):
|
|
||||||
si = self.make_invoice(inclusive=True) # 10,000 incl. 7% VAT -> net 9,345.79
|
|
||||||
self.assertEqual((si.net_total, si.grand_total), (9345.79, 10000.0))
|
|
||||||
self.assertEqual(si.withholding_tax_amount, 280.37)
|
|
||||||
pe = self.make_receipt(si, allocated=10000)
|
|
||||||
self.assertEqual(self.withheld(pe), [(self.receivable, "Deduct", 280.37)])
|
|
||||||
self.assertEqual(pe.received_amount_after_tax, 9719.63)
|
|
||||||
|
|
||||||
def test_partial_allocation_prorates_withholding(self):
|
|
||||||
si = self.make_invoice()
|
|
||||||
pe = self.make_receipt(si, allocated=5350)
|
|
||||||
self.assertEqual(self.withheld(pe), [(self.receivable, "Deduct", 150.0)])
|
|
||||||
self.assertEqual(pe.received_amount_after_tax, 5200.0)
|
|
||||||
|
|
||||||
def test_below_single_threshold_has_no_deduction(self):
|
|
||||||
si = self.make_invoice(rate=800) # net 800 < 1,000 threshold
|
|
||||||
pe = self.make_receipt(si, allocated=856, apply=0)
|
|
||||||
self.assertEqual(pe.apply_tax_withholding_amount, 0) # invoice carries no withholding
|
|
||||||
self.assertEqual(pe.taxes, [])
|
|
||||||
self.assertEqual(pe.received_amount_after_tax, 856.0)
|
|
||||||
|
|
||||||
def test_create_payment_from_invoice_applies_withholding(self):
|
|
||||||
si = self.make_invoice()
|
|
||||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account=f"Cash - {ABBR}")
|
|
||||||
self.assertEqual(pe.apply_tax_withholding_amount, 1)
|
|
||||||
self.assertEqual(pe.tax_withholding_category, "WHT 3% - Service")
|
|
||||||
self.assertEqual(self.withheld(pe), [(self.receivable, "Deduct", 300.0)])
|
|
||||||
self.assertEqual((pe.paid_amount, pe.received_amount_after_tax), (10700.0, 10400.0))
|
|
||||||
pe.insert()
|
|
||||||
self.assertEqual(self.withheld(pe), [(self.receivable, "Deduct", 300.0)])
|
|
||||||
|
|
||||||
def test_new_receipt_referencing_withheld_invoice_applies_on_save(self):
|
|
||||||
si = self.make_invoice()
|
|
||||||
pe = self.make_receipt(si, allocated=10700, apply=0)
|
|
||||||
self.assertEqual(pe.apply_tax_withholding_amount, 1)
|
|
||||||
self.assertEqual(self.withheld(pe), [(self.receivable, "Deduct", 300.0)])
|
|
||||||
|
|
||||||
def test_unticking_after_save_removes_withholding(self):
|
|
||||||
si = self.make_invoice()
|
|
||||||
pe = self.make_receipt(si, allocated=10700)
|
|
||||||
pe.apply_tax_withholding_amount = 0
|
|
||||||
pe.save()
|
|
||||||
self.assertEqual(pe.apply_tax_withholding_amount, 0) # saved docs keep the user's choice
|
|
||||||
self.assertEqual(pe.taxes, [])
|
|
||||||
self.assertEqual(pe.received_amount_after_tax, 10700.0)
|
|
||||||
|
|
||||||
def test_reallocation_on_form_recomputes_withholding(self):
|
|
||||||
si = self.make_invoice()
|
|
||||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account=f"Cash - {ABBR}")
|
|
||||||
pe.paid_amount = pe.received_amount = 5350
|
|
||||||
pe.allocate_amount_to_references(
|
|
||||||
paid_amount=5350, paid_amount_change=True, allocate_payment_amount=True
|
|
||||||
)
|
|
||||||
self.assertEqual(pe.references[0].allocated_amount, 5350.0)
|
|
||||||
self.assertEqual(self.withheld(pe), [(self.receivable, "Deduct", 150.0)])
|
|
||||||
|
|
||||||
def test_certificate_reports_withholding_on_the_category_row(self):
|
|
||||||
pi = self.make_purchase_invoice(SUPPLIER, "WHT 3% - Professional Fee")
|
|
||||||
self.assertEqual(pi.grand_total, 104000.0) # 100,000 + 7% VAT - 3% withheld
|
|
||||||
|
|
||||||
c = get_withholding_certificate(pi)
|
|
||||||
self.assertEqual((c.row, c.row_note, c.pnd), ("5", None, "53"))
|
|
||||||
self.assertEqual((c.amount, c.tax, c.tax_in_words), ("100,000.00", "3,000.00", "สามพันบาทถ้วน"))
|
|
||||||
self.assertEqual(
|
|
||||||
(c.date, dict(c.issued)), ("16/09/2569", {"day": 16, "month": "กันยายน", "year": 2569})
|
|
||||||
)
|
|
||||||
self.assertEqual((c.payer.name, c.payer.tax_id_digits), (COMPANY, "0105551234567"))
|
|
||||||
self.assertEqual(
|
|
||||||
(c.payee.name, c.payee.tax_id_digits, c.payee.address),
|
|
||||||
(SUPPLIER, "0123456789012", "99/9 Moo 5 Soi Sukhumvit 24 Khlong Toei Bangkok 10110"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_certificate_return_follows_payee_type_and_income(self):
|
|
||||||
c = get_withholding_certificate(
|
|
||||||
self.make_purchase_invoice(INDIVIDUAL, "WHT 15% - Interest (Individual)")
|
|
||||||
)
|
|
||||||
self.assertEqual((c.row, c.pnd, c.tax), ("4a", "2", "15,000.00"))
|
|
||||||
self.assertIsNone(c.payee.address)
|
|
||||||
|
|
||||||
c = get_withholding_certificate(
|
|
||||||
self.make_purchase_invoice(INDIVIDUAL, "WHT 15% - Non-Resident Individual")
|
|
||||||
)
|
|
||||||
self.assertEqual((c.row, c.pnd), ("6", "3"))
|
|
||||||
self.assertEqual(
|
|
||||||
c.row_note, "Sec. 40(2)-(6) income paid to non-resident individuals - Sec. 50(2) (P.N.D.3)"
|
|
||||||
)
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
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,ภาษีซื้อ
|
|
||||||
Withholding Tax Certificate,หนังสือรับรองการหักภาษี ณ ที่จ่าย
|
|
||||||
Type of Income Paid,ประเภทเงินได้พึงประเมินที่จ่าย
|
|
||||||
Salary and Wages - Sec. 40(1),เงินเดือน ค่าจ้าง ฯลฯ ตามมาตรา 40(1)
|
|
||||||
Fees and Commissions - Sec. 40(2),ค่าธรรมเนียม ค่านายหน้า ฯลฯ ตามมาตรา 40(2)
|
|
||||||
Royalties - Sec. 40(3),ค่าแห่งลิขสิทธิ์ ฯลฯ ตามมาตรา 40(3)
|
|
||||||
Interest - Sec. 40(4)(a),ดอกเบี้ย ฯลฯ ตามมาตรา 40(4)(ก)
|
|
||||||
Dividends - Sec. 40(4)(b),เงินปันผล เงินส่วนแบ่งกำไร ฯลฯ ตามมาตรา 40(4)(ข)
|
|
||||||
"Sec. 3 Tera (Services, Rent, Contract Work etc.)",ตามคำสั่งกรมสรรพากรที่ออกตามมาตรา 3 เตรส (ค่าบริการ ค่าเช่า ค่าจ้างทำของ ฯลฯ)
|
|
||||||
|
@@ -1,30 +1,6 @@
|
|||||||
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, lang=None):
|
|
||||||
"""Amount in words for `lang` (default: 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 (lang or 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):
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
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