diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 07149c25658..a5c892687c5 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -622,7 +622,7 @@ class SalarySlip(TransactionBase): self.add_tax_components(payroll_period) def add_structure_components(self, component_type): - data = self.get_data_for_eval() + data, default_data = self.get_data_for_eval() timesheet_component = frappe.db.get_value( "Salary Structure", self.salary_structure, "salary_component" ) @@ -635,7 +635,10 @@ class SalarySlip(TransactionBase): if ( amount or (struct_row.amount_based_on_formula and amount is not None) ) and struct_row.statistical_component == 0: - self.update_component_row(struct_row, amount, component_type, data=data) + default_amount = self.eval_condition_and_formula(struct_row, default_data) + self.update_component_row( + struct_row, amount, component_type, data=data, default_amount=default_amount + ) def get_data_for_eval(self): """Returns data for evaluating formula""" @@ -679,11 +682,15 @@ class SalarySlip(TransactionBase): for sc in salary_components: data.setdefault(sc.salary_component_abbr, 0) + # shallow copy of data to store default amounts (without payment days) for tax calculation + default_data = data.copy() + for key in ("earnings", "deductions"): for d in self.get(key): + default_data[d.abbr] = d.default_amount data[d.abbr] = d.amount - return data + return data, default_data def eval_condition_and_formula(self, d, data): try: @@ -789,7 +796,14 @@ class SalarySlip(TransactionBase): self.update_component_row(tax_row, tax_amount, "deductions") def update_component_row( - self, component_data, amount, component_type, additional_salary=None, is_recurring=0, data=None + self, + component_data, + amount, + component_type, + additional_salary=None, + is_recurring=0, + data=None, + default_amount=None, ): component_row = None for d in self.get(component_type): @@ -850,7 +864,7 @@ class SalarySlip(TransactionBase): additional_salary.deduct_full_tax_on_selected_payroll_date ) else: - component_row.default_amount = amount + component_row.default_amount = default_amount or amount component_row.additional_amount = 0 component_row.deduct_full_tax_on_selected_payroll_date = ( component_data.deduct_full_tax_on_selected_payroll_date @@ -1283,7 +1297,7 @@ class SalarySlip(TransactionBase): )[0].total_amount def calculate_tax_by_tax_slab(self, annual_taxable_earning, tax_slab): - data = self.get_data_for_eval() + data, default_data = self.get_data_for_eval() data.update({"annual_taxable_earning": annual_taxable_earning}) tax_amount = 0 for slab in tax_slab.slabs: diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 8604ed453b7..987c1ac281e 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -7,7 +7,7 @@ import unittest import frappe from frappe.model.document import Document -from frappe.tests.utils import change_settings +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import ( add_days, add_months, @@ -35,13 +35,12 @@ from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_detail from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip -class TestSalarySlip(unittest.TestCase): +class TestSalarySlip(FrappeTestCase): def setUp(self): setup_test() frappe.flags.pop("via_payroll_entry", None) def tearDown(self): - frappe.db.rollback() frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0) frappe.set_user("Administrator") @@ -925,6 +924,41 @@ class TestSalarySlip(unittest.TestCase): # undelete fixture data frappe.db.rollback() + @change_settings( + "Payroll Settings", + { + "payroll_based_on": "Attendance", + "consider_unmarked_attendance_as": "Present", + "include_holidays_in_total_working_days": True, + }, + ) + def test_default_amount(self): + # Special Allowance (SA) uses another component Basic (BS) in it's formula : BD * .5 + # Basic has "Depends on Payment Days" enabled + # Test default amount for SA is based on default amount for BS (irrespective of PD) + # Test amount for SA is based on amount for BS (based on PD) + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + + month_start_date = get_first_day(nowdate()) + joining_date = add_days(month_start_date, 3) + employee = make_employee("test_tax_for_mid_joinee@salary.com", date_of_joining=joining_date) + + salary_structure = make_salary_structure( + "Stucture to test tax", + "Monthly", + test_tax=True, + from_date=joining_date, + employee=employee, + ) + + ss = make_salary_slip(salary_structure.name, employee=employee) + + # default amount for SA (special allowance = BS*0.5) should be based on default amount for basic + self.assertEqual(ss.earnings[2].default_amount, 25000) + self.assertEqual( + ss.earnings[2].amount, flt(ss.earnings[0].amount * 0.5, ss.earnings[0].precision("amount")) + ) + def test_tax_for_recurring_additional_salary(self): frappe.db.sql("""delete from `tabPayroll Period`""") frappe.db.sql("""delete from `tabSalary Component`""") @@ -1054,7 +1088,7 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None, po def make_salary_component(salary_components, test_tax, company_list=None): for salary_component in salary_components: if frappe.db.exists("Salary Component", salary_component["salary_component"]): - continue + frappe.delete_doc("Salary Component", salary_component["salary_component"], force=True) if test_tax: if salary_component["type"] == "Earning": @@ -1442,6 +1476,10 @@ def setup_test(): "Salary Slip", "Attendance", "Additional Salary", + "Employee Tax Exemption Declaration", + "Employee Tax Exemption Proof Submission", + "Employee Benefit Claim", + "Salary Structure Assignment", ]: frappe.db.sql("delete from `tab%s`" % dt) diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index acc416fca3f..c598904bbe4 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -150,6 +150,7 @@ def make_salary_structure( currency=erpnext.get_default_currency(), payroll_period=None, include_flexi_benefits=False, + base=None, ): if test_tax: frappe.db.sql("""delete from `tabSalary Structure` where name=%s""", (salary_structure)) @@ -200,6 +201,7 @@ def make_salary_structure( company=company, currency=currency, payroll_period=payroll_period, + base=base, ) return salary_structure_doc