diff --git a/default_thai_company/hooks.py b/default_thai_company/hooks.py index 6be69be..b951bd5 100644 --- a/default_thai_company/hooks.py +++ b/default_thai_company/hooks.py @@ -103,7 +103,8 @@ fixtures = [ # ------------ # before_install = "default_thai_company.install.before_install" -# after_install = "default_thai_company.install.after_install" +# Fixtures are synced before this runs; VAT schemes for companies that already exist. +after_install = "default_thai_company.vat.setup_companies" # Uninstallation # ------------ @@ -167,7 +168,10 @@ doc_events = { "before_import": "default_thai_company.tax_withholding.prepare_fixture_accounts", }, "Company": { - "on_update": "default_thai_company.tax_withholding.setup_company", + "on_update": [ + "default_thai_company.tax_withholding.setup_company", + "default_thai_company.vat.setup_company", + ], }, "Payment Entry": { "validate": "default_thai_company.tax_withholding.set_customer_withholding", diff --git a/default_thai_company/patches.txt b/default_thai_company/patches.txt index 4c363e9..e8da871 100644 --- a/default_thai_company/patches.txt +++ b/default_thai_company/patches.txt @@ -5,3 +5,4 @@ [post_model_sync] # Patches added in this section will be executed after doctypes are migrated default_thai_company.patches.create_vat_accounts +default_thai_company.patches.create_vat_templates diff --git a/default_thai_company/patches/create_vat_templates.py b/default_thai_company/patches/create_vat_templates.py new file mode 100644 index 0000000..2d43568 --- /dev/null +++ b/default_thai_company/patches/create_vat_templates.py @@ -0,0 +1,5 @@ +from default_thai_company.vat import setup_companies + + +def execute(): + setup_companies() diff --git a/default_thai_company/tax_withholding.py b/default_thai_company/tax_withholding.py index 83155e8..f0ac22c 100644 --- a/default_thai_company/tax_withholding.py +++ b/default_thai_company/tax_withholding.py @@ -118,9 +118,14 @@ def prepare_fixture_accounts(doc, method=None): link_company(doc, company, ensure_company_accounts(company)) +def company_ready(doc): + """A Thai company whose chart of accounts exists.""" + return doc.country == "Thailand" and frappe.db.exists("Account", {"company": doc.name}) + + def setup_company(doc, method=None): """Company.on_update: create the tax accounts and link every WHT category.""" - if doc.country != "Thailand" or not frappe.db.exists("Account", {"company": doc.name}): + if not company_ready(doc): return account = ensure_company_accounts(doc.name) diff --git a/default_thai_company/tests/test_tax_withholding.py b/default_thai_company/tests/test_tax_withholding.py index b7e442e..3e28a23 100644 --- a/default_thai_company/tests/test_tax_withholding.py +++ b/default_thai_company/tests/test_tax_withholding.py @@ -149,6 +149,34 @@ class TestTaxWithholding(FrappeTestCase): for name in categories: self.assertEqual(self.category_account(name), self.payable, name) + def test_company_creation_adds_vat_schemes(self): + for doctype, account in ( + ("Sales Taxes and Charges Template", f"{OUTPUT_VAT_ACCOUNT} - {ABBR}"), + ("Purchase Taxes and Charges Template", f"{INPUT_VAT_ACCOUNT} - {ABBR}"), + ): + schemes = {} + for name in frappe.get_all( + doctype, filters={"company": COMPANY, "title": ("like", "Thailand VAT%")}, pluck="name" + ): + doc = frappe.get_doc(doctype, name) + (row,) = doc.taxes + schemes[doc.title] = ( + doc.is_default, + row.account_head, + row.charge_type, + row.rate, + row.included_in_print_rate, + ) + self.assertEqual( + schemes, + { + "Thailand VAT 7%": (1, account, "On Net Total", 7.0, 0), + "Thailand VAT 7% (Included)": (0, account, "On Net Total", 7.0, 1), + "Thailand VAT 0%": (0, account, "On Net Total", 0.0, 0), + }, + doctype, + ) + def test_fixture_reimport_keeps_site_account_and_relinks(self): alt = frappe.get_doc( { diff --git a/default_thai_company/vat.py b/default_thai_company/vat.py new file mode 100644 index 0000000..7b10d84 --- /dev/null +++ b/default_thai_company/vat.py @@ -0,0 +1,78 @@ +import erpnext +import frappe + +from default_thai_company.tax_withholding import ( + INPUT_VAT_ACCOUNT, + OUTPUT_VAT_ACCOUNT, + company_ready, + get_or_create_account, + thai_companies, +) + +RATE = 7.0 + +# title, rate, included_in_print_rate. Exempt supplies take no template. +SCHEMES = ( + ("Thailand VAT 7%", RATE, 0), + ("Thailand VAT 7% (Included)", RATE, 1), # VAT-inclusive prices, the retail norm + ("Thailand VAT 0%", 0.0, 0), # zero-rated: exports, international transport +) +DEFAULT_SCHEME = "Thailand VAT 7%" + +# doctype, account, root type, extra row fields. Input VAT is recoverable, so on +# purchases it is "Total" (added to the bill, kept out of item valuation). +TEMPLATES = ( + ("Sales Taxes and Charges Template", OUTPUT_VAT_ACCOUNT, "Liability", {}), + ( + "Purchase Taxes and Charges Template", + INPUT_VAT_ACCOUNT, + "Asset", + {"category": "Total", "add_deduct_tax": "Add"}, + ), +) + + +def ensure_vat_templates(company): + """Create the VAT schemes for `company`, skipping titles that exist. The 7% + scheme becomes the default when the company has no default template.""" + cost_center = erpnext.get_default_cost_center(company) + for doctype, account_name, root_type, extra in TEMPLATES: + account = get_or_create_account(company, account_name, root_type) + has_default = frappe.db.exists(doctype, {"company": company, "is_default": 1}) + for title, rate, included in SCHEMES: + if frappe.db.exists(doctype, {"company": company, "title": title}): + continue + is_default = title == DEFAULT_SCHEME and not has_default + frappe.get_doc( + { + "doctype": doctype, + "title": title, + "company": company, + "is_default": int(is_default), + "taxes": [ + { + "charge_type": "On Net Total", + "account_head": account, + "rate": rate, + "description": f"VAT {rate:g}%", + "included_in_print_rate": included, + "cost_center": cost_center, + **extra, + } + ], + } + ).insert(ignore_permissions=True) + has_default = has_default or is_default + + +def setup_company(doc, method=None): + """Company.on_update: VAT schemes for a Thai company.""" + if company_ready(doc): + ensure_vat_templates(doc.name) + + +def setup_companies(): + """after_install and the create_vat_templates patch: companies that exist + before this code did never pass through `setup_company`.""" + for company in thai_companies(): + ensure_vat_templates(company)