Compare commits
2
Commits
caad6e77da
...
04735ca813
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04735ca813 | ||
|
|
381aab2c83 |
@@ -103,7 +103,8 @@ fixtures = [
|
|||||||
# ------------
|
# ------------
|
||||||
|
|
||||||
# before_install = "default_thai_company.install.before_install"
|
# 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
|
# Uninstallation
|
||||||
# ------------
|
# ------------
|
||||||
@@ -167,7 +168,10 @@ doc_events = {
|
|||||||
"before_import": "default_thai_company.tax_withholding.prepare_fixture_accounts",
|
"before_import": "default_thai_company.tax_withholding.prepare_fixture_accounts",
|
||||||
},
|
},
|
||||||
"Company": {
|
"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": {
|
"Payment Entry": {
|
||||||
"validate": "default_thai_company.tax_withholding.set_customer_withholding",
|
"validate": "default_thai_company.tax_withholding.set_customer_withholding",
|
||||||
|
|||||||
@@ -4,3 +4,5 @@
|
|||||||
|
|
||||||
[post_model_sync]
|
[post_model_sync]
|
||||||
# Patches added in this section will be executed after doctypes are migrated
|
# Patches added in this section will be executed after doctypes are migrated
|
||||||
|
default_thai_company.patches.create_vat_accounts
|
||||||
|
default_thai_company.patches.create_vat_templates
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from default_thai_company.tax_withholding import ensure_company_accounts, thai_companies
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
"""Output VAT / Input VAT were added to COMPANY_ACCOUNTS; Company.on_update
|
||||||
|
only runs on save, so give existing Thai companies the pair."""
|
||||||
|
for company in thai_companies():
|
||||||
|
ensure_company_accounts(company)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from default_thai_company.vat import setup_companies
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
setup_companies()
|
||||||
@@ -12,10 +12,23 @@ from frappe.utils import cint, flt
|
|||||||
|
|
||||||
from default_thai_company.utils import money_in_words
|
from default_thai_company.utils import money_in_words
|
||||||
|
|
||||||
# Liability: tax we withhold from suppliers and remit on P.N.D.3/53 (linked on every category).
|
# Tax accounts every Thai company gets (Company.on_update, fixture import, and the
|
||||||
# Asset: tax customers withhold from us, creditable against corporate income tax.
|
# create_vat_accounts patch for companies that predate the VAT pair).
|
||||||
|
# Withholding: liability = tax we withhold from suppliers, remitted on P.N.D.3/53
|
||||||
|
# (linked on every category); asset = tax customers withhold from us, creditable
|
||||||
|
# against corporate income tax.
|
||||||
|
# VAT: output (ภาษีขาย) collected on sales is payable, input (ภาษีซื้อ) paid on
|
||||||
|
# purchases is recoverable; the P.P.30 return nets the two, so they are kept apart.
|
||||||
LIABILITY_ACCOUNT = "Withholding Tax Payable"
|
LIABILITY_ACCOUNT = "Withholding Tax Payable"
|
||||||
ASSET_ACCOUNT = "Withholding Tax Receivable"
|
ASSET_ACCOUNT = "Withholding Tax Receivable"
|
||||||
|
OUTPUT_VAT_ACCOUNT = "Output VAT"
|
||||||
|
INPUT_VAT_ACCOUNT = "Input VAT"
|
||||||
|
COMPANY_ACCOUNTS = (
|
||||||
|
(LIABILITY_ACCOUNT, "Liability"),
|
||||||
|
(ASSET_ACCOUNT, "Asset"),
|
||||||
|
(OUTPUT_VAT_ACCOUNT, "Liability"),
|
||||||
|
(INPUT_VAT_ACCOUNT, "Asset"),
|
||||||
|
)
|
||||||
|
|
||||||
# Preferred parent groups by root type; first match wins, else the root account.
|
# Preferred parent groups by root type; first match wins, else the root account.
|
||||||
PARENT_GROUPS = {
|
PARENT_GROUPS = {
|
||||||
@@ -67,9 +80,10 @@ def find_parent_group(company, root_type):
|
|||||||
|
|
||||||
|
|
||||||
def ensure_company_accounts(company):
|
def ensure_company_accounts(company):
|
||||||
"""Create both withholding accounts for `company`; return the liability account name."""
|
"""Create every account in COMPANY_ACCOUNTS for `company`; return the
|
||||||
get_or_create_account(company, ASSET_ACCOUNT, "Asset")
|
withholding liability account name (the one Tax Withholding Categories link)."""
|
||||||
return get_or_create_account(company, LIABILITY_ACCOUNT, "Liability")
|
accounts = {name: get_or_create_account(company, name, root_type) for name, root_type in COMPANY_ACCOUNTS}
|
||||||
|
return accounts[LIABILITY_ACCOUNT]
|
||||||
|
|
||||||
|
|
||||||
def link_company(category, company, account):
|
def link_company(category, company, account):
|
||||||
@@ -104,9 +118,14 @@ def prepare_fixture_accounts(doc, method=None):
|
|||||||
link_company(doc, company, ensure_company_accounts(company))
|
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):
|
def setup_company(doc, method=None):
|
||||||
"""Company.on_update: create withholding accounts and link every WHT category."""
|
"""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
|
return
|
||||||
|
|
||||||
account = ensure_company_accounts(doc.name)
|
account = ensure_company_accounts(doc.name)
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ from frappe.utils import nowdate
|
|||||||
|
|
||||||
from default_thai_company.tax_withholding import (
|
from default_thai_company.tax_withholding import (
|
||||||
ASSET_ACCOUNT,
|
ASSET_ACCOUNT,
|
||||||
|
INPUT_VAT_ACCOUNT,
|
||||||
LIABILITY_ACCOUNT,
|
LIABILITY_ACCOUNT,
|
||||||
|
OUTPUT_VAT_ACCOUNT,
|
||||||
get_payment_entry,
|
get_payment_entry,
|
||||||
thai_companies,
|
thai_companies,
|
||||||
)
|
)
|
||||||
@@ -44,16 +46,7 @@ class TestTaxWithholding(FrappeTestCase):
|
|||||||
).insert()
|
).insert()
|
||||||
cls.payable = f"{LIABILITY_ACCOUNT} - {ABBR}"
|
cls.payable = f"{LIABILITY_ACCOUNT} - {ABBR}"
|
||||||
cls.receivable = f"{ASSET_ACCOUNT} - {ABBR}"
|
cls.receivable = f"{ASSET_ACCOUNT} - {ABBR}"
|
||||||
|
cls.vat = frappe.get_doc("Account", f"{OUTPUT_VAT_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(
|
frappe.get_doc(
|
||||||
{
|
{
|
||||||
@@ -137,13 +130,16 @@ class TestTaxWithholding(FrappeTestCase):
|
|||||||
|
|
||||||
def test_company_creation_adds_accounts_and_links_categories(self):
|
def test_company_creation_adds_accounts_and_links_categories(self):
|
||||||
self.assertIn(COMPANY, thai_companies())
|
self.assertIn(COMPANY, thai_companies())
|
||||||
|
for account, expected in (
|
||||||
|
(self.payable, ("Liability", f"Duties and Taxes - {ABBR}", "Tax")),
|
||||||
|
(self.receivable, ("Asset", f"Tax Assets - {ABBR}", "Tax")),
|
||||||
|
(f"{OUTPUT_VAT_ACCOUNT} - {ABBR}", ("Liability", f"Duties and Taxes - {ABBR}", "Tax")),
|
||||||
|
(f"{INPUT_VAT_ACCOUNT} - {ABBR}", ("Asset", f"Tax Assets - {ABBR}", "Tax")),
|
||||||
|
):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
frappe.db.get_value("Account", self.payable, ["root_type", "parent_account"]),
|
frappe.db.get_value("Account", account, ["root_type", "parent_account", "account_type"]),
|
||||||
("Liability", f"Duties and Taxes - {ABBR}"),
|
expected,
|
||||||
)
|
account,
|
||||||
self.assertEqual(
|
|
||||||
frappe.db.get_value("Account", self.receivable, ["root_type", "parent_account"]),
|
|
||||||
("Asset", f"Tax Assets - {ABBR}"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
categories = frappe.get_all(
|
categories = frappe.get_all(
|
||||||
@@ -153,6 +149,34 @@ class TestTaxWithholding(FrappeTestCase):
|
|||||||
for name in categories:
|
for name in categories:
|
||||||
self.assertEqual(self.category_account(name), self.payable, name)
|
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):
|
def test_fixture_reimport_keeps_site_account_and_relinks(self):
|
||||||
alt = frappe.get_doc(
|
alt = frappe.get_doc(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -53,3 +53,7 @@ Grand Total,ยอดรวมทั้งสิ้น
|
|||||||
Rounded Total,ยอดรวมปัดเศษ
|
Rounded Total,ยอดรวมปัดเศษ
|
||||||
Terms and Conditions Details,รายละเอียดข้อตกลงและเงื่อนไข
|
Terms and Conditions Details,รายละเอียดข้อตกลงและเงื่อนไข
|
||||||
Page {0} of {1},หน้า {0} จาก {1}
|
Page {0} of {1},หน้า {0} จาก {1}
|
||||||
|
Withholding Tax Payable,ภาษีหัก ณ ที่จ่ายค้างจ่าย
|
||||||
|
Withholding Tax Receivable,ภาษีถูกหัก ณ ที่จ่าย
|
||||||
|
Output VAT,ภาษีขาย
|
||||||
|
Input VAT,ภาษีซื้อ
|
||||||
|
|||||||
|
@@ -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)
|
||||||
Reference in New Issue
Block a user