diff --git a/default_thai_company/fixtures/tax_withholding_category.json b/default_thai_company/fixtures/tax_withholding_category.json new file mode 100644 index 0000000..98e8811 --- /dev/null +++ b/default_thai_company/fixtures/tax_withholding_category.json @@ -0,0 +1,574 @@ +[ + { + "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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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", + "modified": "2026-09-11 09: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 + } +] diff --git a/default_thai_company/hooks.py b/default_thai_company/hooks.py index 7139065..37b17c4 100644 --- a/default_thai_company/hooks.py +++ b/default_thai_company/hooks.py @@ -92,6 +92,7 @@ fixtures = [ "doctype": "Property Setter", "filters": [["name", "in", ["Sales Invoice-main-default_print_format"]]], }, + {"doctype": "Tax Withholding Category", "filters": [["name", "like", "WHT %"]]}, ] # Installation @@ -156,7 +157,13 @@ doc_events = { "Letter Head": { "before_import": "default_thai_company.utils.stash_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", + }, + "Company": { + "on_update": "default_thai_company.tax_withholding.setup_company", + }, } # Scheduled Tasks diff --git a/default_thai_company/tax_withholding.py b/default_thai_company/tax_withholding.py new file mode 100644 index 0000000..93311f3 --- /dev/null +++ b/default_thai_company/tax_withholding.py @@ -0,0 +1,104 @@ +import frappe + +# Liability: tax we withhold from suppliers and remit on P.N.D.3/53 (linked on every category). +# Asset: tax customers withhold from us, creditable against corporate income tax. +LIABILITY_ACCOUNT = "Withholding Tax Payable" +ASSET_ACCOUNT = "Withholding Tax Receivable" + +# 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): + 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), + "root_type": root_type, + "account_type": "Tax", + "is_group": 0, + } + ).insert(ignore_permissions=True) + return account.name + + +def find_parent_group(company, root_type): + for account_name in PARENT_GROUPS[root_type]: + 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 both withholding accounts for `company`; return the liability account name.""" + get_or_create_account(company, ASSET_ACCOUNT, "Asset") + return get_or_create_account(company, LIABILITY_ACCOUNT, "Liability") + + +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 setup_company(doc, method=None): + """Company.on_update: create withholding accounts and link every WHT category.""" + if doc.country != "Thailand" or not frappe.db.exists("Account", {"company": doc.name}): + 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)