diff --git a/default_thai_company/hooks.py b/default_thai_company/hooks.py index 7586649..7f56839 100644 --- a/default_thai_company/hooks.py +++ b/default_thai_company/hooks.py @@ -236,6 +236,10 @@ doc_events = { "default_thai_company.payroll.setup_company", ], }, + # HRMS doctype; the hook only fires on sites that have it. + "Salary Component": { + "validate": "default_thai_company.payroll.set_component_accounts", + }, "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 5ebeec5..e24af99 100644 --- a/default_thai_company/patches.txt +++ b/default_thai_company/patches.txt @@ -8,3 +8,4 @@ default_thai_company.patches.create_vat_accounts default_thai_company.patches.create_vat_templates default_thai_company.patches.create_shipping_rules default_thai_company.patches.create_payroll_periods +default_thai_company.patches.setup_salary_components diff --git a/default_thai_company/patches/setup_salary_components.py b/default_thai_company/patches/setup_salary_components.py new file mode 100644 index 0000000..873a011 --- /dev/null +++ b/default_thai_company/patches/setup_salary_components.py @@ -0,0 +1,5 @@ +from default_thai_company.payroll import setup_companies + + +def execute(): + setup_companies() diff --git a/default_thai_company/payroll.py b/default_thai_company/payroll.py index e9422fa..e082713 100644 --- a/default_thai_company/payroll.py +++ b/default_thai_company/payroll.py @@ -1,18 +1,67 @@ import frappe from frappe.utils import getdate -from default_thai_company.tax_withholding import thai_companies +from default_thai_company.tax_withholding import get_or_create_account, thai_companies # Thai personal income tax (P.N.D.1/91) is assessed per calendar year whatever # the company's fiscal year, so the Payroll Period is January to December. # Payroll Period is named by prompt (site-wide unique) but bound to one company, # so each Thai company gets " - ", like the Shipping Rule. +# +# Payroll Entry refuses to post a component without an account for its company, +# so every Salary Component gets one for every Thai company (Salary +# Component.validate for the components HRMS seeds with the first company and +# the ones users add; Company.on_update for companies added later). +# Earnings expense to "Salary" (Standard chart, Indirect Expenses). The income +# tax component is tax withheld from employees, remitted on P.N.D.1; Thai books +# carry it as ภาษีหัก ณ ที่จ่ายค้างจ่าย, split by return form when split at all, +# so it gets its own withholding account beside the P.N.D.3/53 one the Tax +# Withholding Categories post to. Other deductions (social security, loans) +# are held in "Salary Deductions" (Current Liabilities) until remitted. +EARNING_ACCOUNT = "Salary" +EARNING_ACCOUNT_GROUPS = ("Indirect Expenses", "Expenses") +INCOME_TAX_ACCOUNT = "Withholding Tax Payable (P.N.D.1)" +DEDUCTION_ACCOUNT = "Salary Deductions" +DEDUCTION_ACCOUNT_GROUPS = ("Current Liabilities",) + def hrms_installed(): return "hrms" in frappe.get_installed_apps() +def salary_component_account(company, component): + if component.type == "Earning": + return get_or_create_account(company, EARNING_ACCOUNT, "Expense", "", EARNING_ACCOUNT_GROUPS) + if component.is_income_tax_component: + return get_or_create_account(company, INCOME_TAX_ACCOUNT, "Liability") + return get_or_create_account(company, DEDUCTION_ACCOUNT, "Liability", "", DEDUCTION_ACCOUNT_GROUPS) + + +def link_company(component, company): + """Append the company's account row to the component if missing. Statistical + components never post, so they get none.""" + if component.statistical_component or any(row.company == company for row in component.accounts): + return False + component.append( + "accounts", {"company": company, "account": salary_component_account(company, component)} + ) + return True + + +def set_component_accounts(doc, method=None): + """Salary Component.validate: an account for every Thai company.""" + for company in thai_companies(): + link_company(doc, company) + + +def ensure_component_accounts(company): + for name in frappe.get_all("Salary Component", pluck="name"): + component = frappe.get_doc("Salary Component", name) + if link_company(component, company): + component.save(ignore_permissions=True) + + def payroll_period_name(company, year): abbr = frappe.get_cached_value("Company", company, "abbr") return f"{year} - {abbr}" @@ -39,24 +88,27 @@ def ensure_payroll_period(company, year): def setup_company(doc, method=None): - """Company.on_update: this year's Payroll Period for a Thai company.""" + """Company.on_update: this year's Payroll Period and the Salary Component + accounts for a Thai company.""" if doc.country == "Thailand" and hrms_installed(): ensure_payroll_period(doc.name, getdate().year) + ensure_component_accounts(doc.name) def setup_companies(): - """after_install, the daily scheduler, and the create_payroll_periods patch: - this year's Payroll Period for every Thai company. The scheduler rolls the - default over on 1 January.""" + """after_install, the daily scheduler, and the setup_salary_components + patch: this year's Payroll Period and the Salary Component accounts for + every Thai company. The scheduler rolls the Payroll Period over on 1 January.""" if not hrms_installed(): return year = getdate().year for company in thai_companies(): ensure_payroll_period(company, year) + ensure_component_accounts(company) def after_app_install(app_name): - """Payroll Period only exists once hrms is installed; catch companies that - were created before it.""" + """Payroll Period and Salary Component accounts only exist once hrms is + installed; catch companies that were created before it.""" if app_name == "hrms": setup_companies() diff --git a/default_thai_company/translations/th.csv b/default_thai_company/translations/th.csv index 07c919b..761f3cd 100644 --- a/default_thai_company/translations/th.csv +++ b/default_thai_company/translations/th.csv @@ -74,6 +74,8 @@ Rounded Total,ยอดรวมปัดเศษ Terms and Conditions Details,รายละเอียดข้อตกลงและเงื่อนไข Page {0} of {1},หน้า {0} จาก {1} Withholding Tax Payable,ภาษีหัก ณ ที่จ่ายค้างจ่าย +Withholding Tax Payable (P.N.D.1),ภาษีหัก ณ ที่จ่ายค้างจ่าย (ภ.ง.ด.1) +Salary Deductions,รายการหักจากเงินเดือน Withholding Tax Receivable,ภาษีถูกหัก ณ ที่จ่าย Output VAT,ภาษีขาย Input VAT,ภาษีซื้อ