Compare commits
8
Commits
8d3b843da8
...
2d93fe45c2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d93fe45c2 | ||
|
|
9b99be9abd | ||
|
|
6a082edc56 | ||
|
|
abeb2af0c8 | ||
|
|
ed4062590a | ||
|
|
4767d4bb46 | ||
|
|
6819242521 | ||
|
|
8b3cf9af1e |
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
{
|
||||||
|
"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,18 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -43,7 +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" : "public/js/doctype.js"}
|
doctype_js = {"Payment Entry": "public/js/payment_entry.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"}
|
||||||
@@ -84,14 +84,13 @@ jinja = {
|
|||||||
# 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", "filters": [["name", "in", ["Default Standard Sales Invoice"]]]},
|
||||||
{
|
{"doctype": "Tax Withholding Category", "filters": [["name", "like", "WHT %"]]},
|
||||||
"doctype": "Property Setter",
|
|
||||||
"filters": [["name", "in", ["Sales Invoice-main-default_print_format"]]],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
@@ -144,9 +143,10 @@ fixtures = [
|
|||||||
# ---------------
|
# ---------------
|
||||||
# Override standard doctype classes
|
# Override standard doctype classes
|
||||||
|
|
||||||
# override_doctype_class = {
|
override_doctype_class = {
|
||||||
# "ToDo": "custom_app.overrides.CustomToDo"
|
"Sales Invoice": "default_thai_company.tax_withholding.ThaiSalesInvoice",
|
||||||
# }
|
"Payment Entry": "default_thai_company.tax_withholding.ThaiPaymentEntry",
|
||||||
|
}
|
||||||
|
|
||||||
# Document Events
|
# Document Events
|
||||||
# ---------------
|
# ---------------
|
||||||
@@ -156,7 +156,16 @@ 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",
|
||||||
|
},
|
||||||
|
"Company": {
|
||||||
|
"on_update": "default_thai_company.tax_withholding.setup_company",
|
||||||
|
},
|
||||||
|
"Payment Entry": {
|
||||||
|
"validate": "default_thai_company.tax_withholding.set_customer_withholding",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Scheduled Tasks
|
# Scheduled Tasks
|
||||||
@@ -187,11 +196,11 @@ doc_events = {
|
|||||||
|
|
||||||
# Overriding Methods
|
# Overriding Methods
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
#
|
|
||||||
# override_whitelisted_methods = {
|
override_whitelisted_methods = {
|
||||||
# "frappe.desk.doctype.event.event.get_events": "default_thai_company.event.get_events"
|
"erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry": "default_thai_company.tax_withholding.get_payment_entry",
|
||||||
# }
|
}
|
||||||
#
|
|
||||||
# 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
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
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.utils import cint, flt
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
|
||||||
|
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"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
description = next(iter(details_by_category.values())).description
|
||||||
|
else:
|
||||||
|
description = _("Withholding tax deducted by customer")
|
||||||
|
return flt(total, precision), description
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
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,
|
||||||
|
LIABILITY_ACCOUNT,
|
||||||
|
get_payment_entry,
|
||||||
|
thai_companies,
|
||||||
|
)
|
||||||
|
|
||||||
|
COMPANY = "_Test WHT Company"
|
||||||
|
ABBR = "_TWC"
|
||||||
|
CUSTOMER = "_Test WHT Customer"
|
||||||
|
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()
|
||||||
|
# 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"):
|
||||||
|
with open(f"{CUSTOM_DIR}/{fname}") as f:
|
||||||
|
sync_customizations_for_doctype(json.load(f), CUSTOM_DIR, fname)
|
||||||
|
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Company",
|
||||||
|
"company_name": COMPANY,
|
||||||
|
"abbr": ABBR,
|
||||||
|
"country": "Thailand",
|
||||||
|
"default_currency": "THB",
|
||||||
|
"chart_of_accounts": "Standard",
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
cls.payable = f"{LIABILITY_ACCOUNT} - {ABBR}"
|
||||||
|
cls.receivable = f"{ASSET_ACCOUNT} - {ABBR}"
|
||||||
|
|
||||||
|
cls.vat = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"company": COMPANY,
|
||||||
|
"account_name": "Output VAT",
|
||||||
|
"parent_account": f"Duties and Taxes - {ABBR}",
|
||||||
|
"account_type": "Tax",
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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 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())
|
||||||
|
self.assertEqual(
|
||||||
|
frappe.db.get_value("Account", self.payable, ["root_type", "parent_account"]),
|
||||||
|
("Liability", f"Duties and Taxes - {ABBR}"),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
frappe.db.get_value("Account", self.receivable, ["root_type", "parent_account"]),
|
||||||
|
("Asset", f"Tax Assets - {ABBR}"),
|
||||||
|
)
|
||||||
|
|
||||||
|
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_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)])
|
||||||
Reference in New Issue
Block a user