From 42857b13772848db0da255244056d17e8e9d565e Mon Sep 17 00:00:00 2001 From: kurogeek Date: Tue, 22 Sep 2026 03:29:17 +0000 Subject: [PATCH] feat: Type of Income Paid on Tax Withholding Category Custom Select income_type: the row of the withholding tax certificate (50 tawi) a payment under the category is reported on, set for every shipped category. Frappe syncs fixtures before customizations on install and migrate, so a fixture value for a Custom Field that does not exist yet is dropped; before_install / before_migrate create the category fields first. --- .../custom/tax_withholding_category.json | 73 +++++++++++++++++++ .../fixtures/tax_withholding_category.json | 66 +++++++++++------ default_thai_company/hooks.py | 5 +- default_thai_company/tax_withholding.py | 15 ++++ .../tests/test_tax_withholding.py | 9 ++- default_thai_company/translations/th.csv | 7 ++ 6 files changed, 148 insertions(+), 27 deletions(-) create mode 100644 default_thai_company/default_thai_company/custom/tax_withholding_category.json diff --git a/default_thai_company/default_thai_company/custom/tax_withholding_category.json b/default_thai_company/default_thai_company/custom/tax_withholding_category.json new file mode 100644 index 0000000..a62e6cb --- /dev/null +++ b/default_thai_company/default_thai_company/custom/tax_withholding_category.json @@ -0,0 +1,73 @@ +{ + "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 +} diff --git a/default_thai_company/fixtures/tax_withholding_category.json b/default_thai_company/fixtures/tax_withholding_category.json index 98e8811..f902cdb 100644 --- a/default_thai_company/fixtures/tax_withholding_category.json +++ b/default_thai_company/fixtures/tax_withholding_category.json @@ -5,7 +5,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Fees and Commissions - Sec. 40(2)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 3% - Commission", "rates": [ { @@ -31,7 +32,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Royalties - Sec. 40(3)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 3% - Royalty", "rates": [ { @@ -57,7 +59,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Interest - Sec. 40(4)(a)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 1% - Interest (Company)", "rates": [ { @@ -83,7 +86,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Interest - Sec. 40(4)(a)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 15% - Interest (Individual)", "rates": [ { @@ -109,7 +113,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Dividends - Sec. 40(4)(b)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 10% - Dividend", "rates": [ { @@ -135,7 +140,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 5% - Rent", "rates": [ { @@ -161,7 +167,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 1% - Ship Rental", "rates": [ { @@ -187,7 +194,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 3% - Professional Fee", "rates": [ { @@ -213,7 +221,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 3% - Contract Work", "rates": [ { @@ -239,7 +248,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 3% - Service", "rates": [ { @@ -265,7 +275,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 3% - Sales Promotion", "rates": [ { @@ -291,7 +302,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 2% - Advertising", "rates": [ { @@ -317,7 +329,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 1% - Transportation", "rates": [ { @@ -343,7 +356,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "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": [ { @@ -369,7 +383,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 5% - Prize", "rates": [ { @@ -395,7 +410,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 5% - Public Entertainer", "rates": [ { @@ -421,7 +437,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "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": [ { @@ -447,7 +464,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Sec. 3 Tera (Services, Rent, Contract Work etc.)", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 1% - Aquatic Animals", "rates": [ { @@ -473,7 +491,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Other", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 1% - Immovable Property (Company)", "rates": [ { @@ -499,7 +518,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Other", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 15% - Non-Resident Individual", "rates": [ { @@ -525,7 +545,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Other", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 15% - Foreign Company", "rates": [ { @@ -551,7 +572,8 @@ "consider_party_ledger_amount": 0, "docstatus": 0, "doctype": "Tax Withholding Category", - "modified": "2026-09-11 09:00:00.000000", + "income_type": "Other", + "modified": "2026-09-22 12:00:00.000000", "name": "WHT 10% - Foreign Company Dividend", "rates": [ { diff --git a/default_thai_company/hooks.py b/default_thai_company/hooks.py index 3be319a..832b8c3 100644 --- a/default_thai_company/hooks.py +++ b/default_thai_company/hooks.py @@ -138,7 +138,10 @@ fixtures = [ # Installation # ------------ -# before_install = "default_thai_company.install.before_install" +# Fixtures are synced before Custom Fields on install and migrate; the Tax Withholding +# Category fixtures carry values for the app's Custom Fields, so create those first. +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 = [ diff --git a/default_thai_company/tax_withholding.py b/default_thai_company/tax_withholding.py index da8f831..63896bd 100644 --- a/default_thai_company/tax_withholding.py +++ b/default_thai_company/tax_withholding.py @@ -1,3 +1,6 @@ +import json +import os + import erpnext import frappe from erpnext.accounts.doctype.payment_entry.payment_entry import PaymentEntry @@ -8,6 +11,7 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category normal_round, ) from frappe import _ +from frappe.modules.utils import sync_customizations_for_doctype from frappe.utils import cint, flt from default_thai_company.utils import money_in_words @@ -122,6 +126,17 @@ def prepare_fixture_accounts(doc, method=None): 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}) diff --git a/default_thai_company/tests/test_tax_withholding.py b/default_thai_company/tests/test_tax_withholding.py index e79117f..703f6ee 100644 --- a/default_thai_company/tests/test_tax_withholding.py +++ b/default_thai_company/tests/test_tax_withholding.py @@ -27,12 +27,13 @@ class TestTaxWithholding(FrappeTestCase): @classmethod def setUpClass(cls): super().setUpClass() - # Fixture categories must exist before the company is created so the - # Company hook has something to link; force=True re-imports. - import_file_by_path(FIXTURE, force=True, data_import=True) - for fname in ("sales_invoice.json", "payment_entry.json"): + # 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( { diff --git a/default_thai_company/translations/th.csv b/default_thai_company/translations/th.csv index dda2a59..c64b81e 100644 --- a/default_thai_company/translations/th.csv +++ b/default_thai_company/translations/th.csv @@ -60,3 +60,10 @@ Withholding Tax Payable,ภาษีหัก ณ ที่จ่ายค้า Withholding Tax Receivable,ภาษีถูกหัก ณ ที่จ่าย Output VAT,ภาษีขาย Input VAT,ภาษีซื้อ +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 เตรส (ค่าบริการ ค่าเช่า ค่าจ้างทำของ ฯลฯ)