fix(Payroll): incorrect component amount calculation if dependent on another payment days based component (#27349)
* fix(Payroll): incorrect component amount calculation if dependent on another payment days based component * fix: set component amount precision at the end * fix: consider default amount during taxt calculations * test: component amount dependent on another payment days based component * fix: test
This commit is contained in:
parent
7292f5476d
commit
bab644a249
@ -487,7 +487,7 @@ class SalarySlip(TransactionBase):
|
||||
self.calculate_component_amounts("deductions")
|
||||
|
||||
self.set_loan_repayment()
|
||||
self.set_component_amounts_based_on_payment_days()
|
||||
self.set_precision_for_component_amounts()
|
||||
self.set_net_pay()
|
||||
|
||||
def set_net_pay(self):
|
||||
@ -709,6 +709,17 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
component_row.amount = amount
|
||||
|
||||
self.update_component_amount_based_on_payment_days(component_row)
|
||||
|
||||
def update_component_amount_based_on_payment_days(self, component_row):
|
||||
joining_date, relieving_date = self.get_joining_and_relieving_dates()
|
||||
component_row.amount = self.get_amount_based_on_payment_days(component_row, joining_date, relieving_date)[0]
|
||||
|
||||
def set_precision_for_component_amounts(self):
|
||||
for component_type in ("earnings", "deductions"):
|
||||
for component_row in self.get(component_type):
|
||||
component_row.amount = flt(component_row.amount, component_row.precision("amount"))
|
||||
|
||||
def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period):
|
||||
if not payroll_period:
|
||||
frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}.")
|
||||
@ -866,14 +877,7 @@ class SalarySlip(TransactionBase):
|
||||
return total_tax_paid
|
||||
|
||||
def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0):
|
||||
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
|
||||
if not relieving_date:
|
||||
relieving_date = getdate(self.end_date)
|
||||
|
||||
if not joining_date:
|
||||
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
|
||||
joining_date, relieving_date = self.get_joining_and_relieving_dates()
|
||||
|
||||
taxable_earnings = 0
|
||||
additional_income = 0
|
||||
@ -884,7 +888,10 @@ class SalarySlip(TransactionBase):
|
||||
if based_on_payment_days:
|
||||
amount, additional_amount = self.get_amount_based_on_payment_days(earning, joining_date, relieving_date)
|
||||
else:
|
||||
amount, additional_amount = earning.amount, earning.additional_amount
|
||||
if earning.additional_amount:
|
||||
amount, additional_amount = earning.amount, earning.additional_amount
|
||||
else:
|
||||
amount, additional_amount = earning.default_amount, earning.additional_amount
|
||||
|
||||
if earning.is_tax_applicable:
|
||||
if additional_amount:
|
||||
@ -1055,7 +1062,7 @@ class SalarySlip(TransactionBase):
|
||||
total += amount
|
||||
return total
|
||||
|
||||
def set_component_amounts_based_on_payment_days(self):
|
||||
def get_joining_and_relieving_dates(self):
|
||||
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
|
||||
["date_of_joining", "relieving_date"])
|
||||
|
||||
@ -1065,9 +1072,7 @@ class SalarySlip(TransactionBase):
|
||||
if not joining_date:
|
||||
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):
|
||||
d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount"))
|
||||
return joining_date, relieving_date
|
||||
|
||||
def set_loan_repayment(self):
|
||||
self.total_loan_repayment = 0
|
||||
|
@ -17,6 +17,7 @@ from frappe.utils import (
|
||||
getdate,
|
||||
nowdate,
|
||||
)
|
||||
from frappe.utils.make_random import get_random
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
@ -134,6 +135,65 @@ class TestSalarySlip(unittest.TestCase):
|
||||
|
||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
|
||||
|
||||
def test_component_amount_dependent_on_another_payment_days_based_component(self):
|
||||
from erpnext.hr.doctype.attendance.attendance import mark_attendance
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
|
||||
create_salary_structure_assignment,
|
||||
)
|
||||
|
||||
no_of_days = self.get_no_of_days()
|
||||
# Payroll based on attendance
|
||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
|
||||
|
||||
salary_structure = make_salary_structure_for_payment_days_based_component_dependency()
|
||||
employee = make_employee("test_payment_days_based_component@salary.com", company="_Test Company")
|
||||
|
||||
# base = 50000
|
||||
create_salary_structure_assignment(employee, salary_structure.name, company="_Test Company", currency="INR")
|
||||
|
||||
# mark employee absent for a day since this case works fine if payment days are equal to working days
|
||||
month_start_date = get_first_day(nowdate())
|
||||
month_end_date = get_last_day(nowdate())
|
||||
|
||||
first_sunday = frappe.db.sql("""
|
||||
select holiday_date from `tabHoliday`
|
||||
where parent = 'Salary Slip Test Holiday List'
|
||||
and holiday_date between %s and %s
|
||||
order by holiday_date
|
||||
""", (month_start_date, month_end_date))[0][0]
|
||||
|
||||
mark_attendance(employee, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # counted as absent
|
||||
|
||||
# make salary slip and assert payment days
|
||||
ss = make_salary_slip_for_payment_days_dependency_test("test_payment_days_based_component@salary.com", salary_structure.name)
|
||||
self.assertEqual(ss.absent_days, 1)
|
||||
|
||||
days_in_month = no_of_days[0]
|
||||
no_of_holidays = no_of_days[1]
|
||||
|
||||
self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 1)
|
||||
|
||||
ss.reload()
|
||||
payment_days_based_comp_amount = 0
|
||||
for component in ss.earnings:
|
||||
if component.salary_component == "HRA - Payment Days":
|
||||
payment_days_based_comp_amount = flt(component.amount, component.precision("amount"))
|
||||
break
|
||||
|
||||
# check if the dependent component is calculated using the amount updated after payment days
|
||||
actual_amount = 0
|
||||
precision = 0
|
||||
for component in ss.deductions:
|
||||
if component.salary_component == "P - Employee Provident Fund":
|
||||
precision = component.precision("amount")
|
||||
actual_amount = flt(component.amount, precision)
|
||||
break
|
||||
|
||||
expected_amount = flt((flt(ss.gross_pay) - payment_days_based_comp_amount) * 0.12, precision)
|
||||
|
||||
self.assertEqual(actual_amount, expected_amount)
|
||||
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
|
||||
|
||||
def test_salary_slip_with_holidays_included(self):
|
||||
no_of_days = self.get_no_of_days()
|
||||
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
|
||||
@ -864,3 +924,91 @@ def make_holiday_list():
|
||||
holiday_list = holiday_list.name
|
||||
|
||||
return holiday_list
|
||||
|
||||
def make_salary_structure_for_payment_days_based_component_dependency():
|
||||
earnings = [
|
||||
{
|
||||
"salary_component": "Basic Salary - Payment Days",
|
||||
"abbr": "P_BS",
|
||||
"type": "Earning",
|
||||
"formula": "base",
|
||||
"amount_based_on_formula": 1
|
||||
},
|
||||
{
|
||||
"salary_component": "HRA - Payment Days",
|
||||
"abbr": "P_HRA",
|
||||
"type": "Earning",
|
||||
"depends_on_payment_days": 1,
|
||||
"amount_based_on_formula": 1,
|
||||
"formula": "base * 0.20"
|
||||
}
|
||||
]
|
||||
|
||||
make_salary_component(earnings, False, company_list=["_Test Company"])
|
||||
|
||||
deductions = [
|
||||
{
|
||||
"salary_component": "P - Professional Tax",
|
||||
"abbr": "P_PT",
|
||||
"type": "Deduction",
|
||||
"depends_on_payment_days": 1,
|
||||
"amount": 200.00
|
||||
},
|
||||
{
|
||||
"salary_component": "P - Employee Provident Fund",
|
||||
"abbr": "P_EPF",
|
||||
"type": "Deduction",
|
||||
"exempted_from_income_tax": 1,
|
||||
"amount_based_on_formula": 1,
|
||||
"depends_on_payment_days": 0,
|
||||
"formula": "(gross_pay - P_HRA) * 0.12"
|
||||
}
|
||||
]
|
||||
|
||||
make_salary_component(deductions, False, company_list=["_Test Company"])
|
||||
|
||||
salary_structure = "Salary Structure with PF"
|
||||
if frappe.db.exists("Salary Structure", salary_structure):
|
||||
frappe.db.delete("Salary Structure", salary_structure)
|
||||
|
||||
details = {
|
||||
"doctype": "Salary Structure",
|
||||
"name": salary_structure,
|
||||
"company": "_Test Company",
|
||||
"payroll_frequency": "Monthly",
|
||||
"payment_account": get_random("Account", filters={"account_currency": "INR"}),
|
||||
"currency": "INR"
|
||||
}
|
||||
|
||||
salary_structure_doc = frappe.get_doc(details)
|
||||
|
||||
for entry in earnings:
|
||||
salary_structure_doc.append("earnings", entry)
|
||||
|
||||
for entry in deductions:
|
||||
salary_structure_doc.append("deductions", entry)
|
||||
|
||||
salary_structure_doc.insert()
|
||||
salary_structure_doc.submit()
|
||||
|
||||
return salary_structure_doc
|
||||
|
||||
def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure):
|
||||
employee = frappe.db.get_value("Employee", {
|
||||
"user_id": employee
|
||||
},
|
||||
["name", "company", "employee_name"],
|
||||
as_dict=True)
|
||||
|
||||
salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": employee})})
|
||||
|
||||
if not salary_slip_name:
|
||||
salary_slip = make_salary_slip(salary_structure, employee=employee.name)
|
||||
salary_slip.employee_name = employee.employee_name
|
||||
salary_slip.payroll_frequency = "Monthly"
|
||||
salary_slip.posting_date = nowdate()
|
||||
salary_slip.insert()
|
||||
else:
|
||||
salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)
|
||||
|
||||
return salary_slip
|
Loading…
Reference in New Issue
Block a user