fix: deductions calculation based on gross pay (#20727)
* fix: deductions calculation based on gross pay * test: salary structure deduction based on gross pay Co-authored-by: Nabin Hait <nabinhait@gmail.com>
This commit is contained in:
parent
b5a670cdb9
commit
d42a4a6234
@ -39,19 +39,21 @@ class AdditionalSalary(Document):
|
|||||||
return amount_per_day * no_of_days
|
return amount_per_day * no_of_days
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_additional_salary_component(employee, start_date, end_date):
|
def get_additional_salary_component(employee, start_date, end_date, component_type):
|
||||||
additional_components = frappe.db.sql("""
|
additional_components = frappe.db.sql("""
|
||||||
select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
|
select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
|
||||||
from `tabAdditional Salary`
|
from `tabAdditional Salary`
|
||||||
where employee=%(employee)s
|
where employee=%(employee)s
|
||||||
and docstatus = 1
|
and docstatus = 1
|
||||||
and payroll_date between %(from_date)s and %(to_date)s
|
and payroll_date between %(from_date)s and %(to_date)s
|
||||||
|
and type = %(component_type)s
|
||||||
group by salary_component, overwrite_salary_structure_amount
|
group by salary_component, overwrite_salary_structure_amount
|
||||||
order by salary_component, overwrite_salary_structure_amount
|
order by salary_component, overwrite_salary_structure_amount
|
||||||
""", {
|
""", {
|
||||||
'employee': employee,
|
'employee': employee,
|
||||||
'from_date': start_date,
|
'from_date': start_date,
|
||||||
'to_date': end_date
|
'to_date': end_date,
|
||||||
|
'component_type': "Earning" if component_type == "earnings" else "Deduction"
|
||||||
}, as_dict=1)
|
}, as_dict=1)
|
||||||
|
|
||||||
additional_components_list = []
|
additional_components_list = []
|
||||||
|
@ -299,9 +299,11 @@ class SalarySlip(TransactionBase):
|
|||||||
|
|
||||||
def calculate_net_pay(self):
|
def calculate_net_pay(self):
|
||||||
if self.salary_structure:
|
if self.salary_structure:
|
||||||
self.calculate_component_amounts()
|
self.calculate_component_amounts("earnings")
|
||||||
|
|
||||||
self.gross_pay = self.get_component_totals("earnings")
|
self.gross_pay = self.get_component_totals("earnings")
|
||||||
|
|
||||||
|
if self.salary_structure:
|
||||||
|
self.calculate_component_amounts("deductions")
|
||||||
self.total_deduction = self.get_component_totals("deductions")
|
self.total_deduction = self.get_component_totals("deductions")
|
||||||
|
|
||||||
self.set_loan_repayment()
|
self.set_loan_repayment()
|
||||||
@ -309,25 +311,27 @@ class SalarySlip(TransactionBase):
|
|||||||
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
|
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
|
||||||
self.rounded_total = rounded(self.net_pay)
|
self.rounded_total = rounded(self.net_pay)
|
||||||
|
|
||||||
def calculate_component_amounts(self):
|
def calculate_component_amounts(self, component_type):
|
||||||
if not getattr(self, '_salary_structure_doc', None):
|
if not getattr(self, '_salary_structure_doc', None):
|
||||||
self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure)
|
self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure)
|
||||||
|
|
||||||
payroll_period = get_payroll_period(self.start_date, self.end_date, self.company)
|
payroll_period = get_payroll_period(self.start_date, self.end_date, self.company)
|
||||||
|
|
||||||
self.add_structure_components()
|
self.add_structure_components(component_type)
|
||||||
self.add_employee_benefits(payroll_period)
|
self.add_additional_salary_components(component_type)
|
||||||
self.add_additional_salary_components()
|
if component_type == "earnings":
|
||||||
self.add_tax_components(payroll_period)
|
self.add_employee_benefits(payroll_period)
|
||||||
self.set_component_amounts_based_on_payment_days()
|
else:
|
||||||
|
self.add_tax_components(payroll_period)
|
||||||
|
|
||||||
def add_structure_components(self):
|
self.set_component_amounts_based_on_payment_days(component_type)
|
||||||
|
|
||||||
|
def add_structure_components(self, component_type):
|
||||||
data = self.get_data_for_eval()
|
data = self.get_data_for_eval()
|
||||||
for key in ('earnings', 'deductions'):
|
for struct_row in self._salary_structure_doc.get(component_type):
|
||||||
for struct_row in self._salary_structure_doc.get(key):
|
amount = self.eval_condition_and_formula(struct_row, data)
|
||||||
amount = self.eval_condition_and_formula(struct_row, data)
|
if amount and struct_row.statistical_component == 0:
|
||||||
if amount and struct_row.statistical_component == 0:
|
self.update_component_row(struct_row, amount, component_type)
|
||||||
self.update_component_row(struct_row, amount, key)
|
|
||||||
|
|
||||||
def get_data_for_eval(self):
|
def get_data_for_eval(self):
|
||||||
'''Returns data for evaluating formula'''
|
'''Returns data for evaluating formula'''
|
||||||
@ -400,14 +404,15 @@ class SalarySlip(TransactionBase):
|
|||||||
amount = last_benefit.amount
|
amount = last_benefit.amount
|
||||||
self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings")
|
self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings")
|
||||||
|
|
||||||
def add_additional_salary_components(self):
|
def add_additional_salary_components(self, component_type):
|
||||||
additional_components = get_additional_salary_component(self.employee, self.start_date, self.end_date)
|
additional_components = get_additional_salary_component(self.employee,
|
||||||
|
self.start_date, self.end_date, component_type)
|
||||||
if additional_components:
|
if additional_components:
|
||||||
for additional_component in additional_components:
|
for additional_component in additional_components:
|
||||||
amount = additional_component.amount
|
amount = additional_component.amount
|
||||||
overwrite = additional_component.overwrite
|
overwrite = additional_component.overwrite
|
||||||
key = "earnings" if additional_component.type == "Earning" else "deductions"
|
self.update_component_row(frappe._dict(additional_component.struct_row), amount,
|
||||||
self.update_component_row(frappe._dict(additional_component.struct_row), amount, key, overwrite=overwrite)
|
component_type, overwrite=overwrite)
|
||||||
|
|
||||||
def add_tax_components(self, payroll_period):
|
def add_tax_components(self, payroll_period):
|
||||||
# Calculate variable_based_on_taxable_salary after all components updated in salary slip
|
# Calculate variable_based_on_taxable_salary after all components updated in salary slip
|
||||||
@ -736,7 +741,7 @@ class SalarySlip(TransactionBase):
|
|||||||
total += d.amount
|
total += d.amount
|
||||||
return total
|
return total
|
||||||
|
|
||||||
def set_component_amounts_based_on_payment_days(self):
|
def set_component_amounts_based_on_payment_days(self, component_type):
|
||||||
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
|
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
|
||||||
["date_of_joining", "relieving_date"])
|
["date_of_joining", "relieving_date"])
|
||||||
|
|
||||||
@ -746,9 +751,8 @@ class SalarySlip(TransactionBase):
|
|||||||
if not joining_date:
|
if not joining_date:
|
||||||
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
|
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
|
||||||
|
|
||||||
for component_type in ("earnings", "deductions"):
|
for d in self.get(component_type):
|
||||||
for d in self.get(component_type):
|
d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
|
||||||
d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
|
|
||||||
|
|
||||||
def set_loan_repayment(self):
|
def set_loan_repayment(self):
|
||||||
self.set('loans', [])
|
self.set('loans', [])
|
||||||
|
@ -25,7 +25,6 @@ class TestSalaryStructure(unittest.TestCase):
|
|||||||
make_employee("test_employee@salary.com")
|
make_employee("test_employee@salary.com")
|
||||||
make_employee("test_employee_2@salary.com")
|
make_employee("test_employee_2@salary.com")
|
||||||
|
|
||||||
|
|
||||||
def make_holiday_list(self):
|
def make_holiday_list(self):
|
||||||
if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"):
|
if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"):
|
||||||
holiday_list = frappe.get_doc({
|
holiday_list = frappe.get_doc({
|
||||||
@ -38,6 +37,29 @@ class TestSalaryStructure(unittest.TestCase):
|
|||||||
holiday_list.get_weekly_off_dates()
|
holiday_list.get_weekly_off_dates()
|
||||||
holiday_list.save()
|
holiday_list.save()
|
||||||
|
|
||||||
|
def test_salary_structure_deduction_based_on_gross_pay(self):
|
||||||
|
|
||||||
|
emp = make_employee("test_employee_3@salary.com")
|
||||||
|
|
||||||
|
sal_struct = make_salary_structure("Salary Structure 2", "Monthly", dont_submit = True)
|
||||||
|
|
||||||
|
sal_struct.earnings = [sal_struct.earnings[0]]
|
||||||
|
sal_struct.earnings[0].amount_based_on_formula = 1
|
||||||
|
sal_struct.earnings[0].formula = "base"
|
||||||
|
|
||||||
|
sal_struct.deductions = [sal_struct.deductions[0]]
|
||||||
|
|
||||||
|
sal_struct.deductions[0].amount_based_on_formula = 1
|
||||||
|
sal_struct.deductions[0].condition = "gross_pay > 100"
|
||||||
|
sal_struct.deductions[0].formula = "gross_pay * 0.2"
|
||||||
|
|
||||||
|
sal_struct.submit()
|
||||||
|
|
||||||
|
assignment = create_salary_structure_assignment(emp, "Salary Structure 2")
|
||||||
|
ss = make_salary_slip(sal_struct.name, employee = emp)
|
||||||
|
|
||||||
|
self.assertEqual(assignment.base * 0.2, ss.deductions[0].amount)
|
||||||
|
|
||||||
def test_amount_totals(self):
|
def test_amount_totals(self):
|
||||||
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
|
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
|
||||||
sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"})
|
sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user