Merge pull request #31802 from ruchamahabal/fix-salary-slip-tds-v13
This commit is contained in:
@@ -622,7 +622,7 @@ class SalarySlip(TransactionBase):
|
|||||||
self.add_tax_components(payroll_period)
|
self.add_tax_components(payroll_period)
|
||||||
|
|
||||||
def add_structure_components(self, component_type):
|
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(
|
timesheet_component = frappe.db.get_value(
|
||||||
"Salary Structure", self.salary_structure, "salary_component"
|
"Salary Structure", self.salary_structure, "salary_component"
|
||||||
)
|
)
|
||||||
@@ -635,7 +635,10 @@ class SalarySlip(TransactionBase):
|
|||||||
if (
|
if (
|
||||||
amount or (struct_row.amount_based_on_formula and amount is not None)
|
amount or (struct_row.amount_based_on_formula and amount is not None)
|
||||||
) and struct_row.statistical_component == 0:
|
) 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):
|
def get_data_for_eval(self):
|
||||||
"""Returns data for evaluating formula"""
|
"""Returns data for evaluating formula"""
|
||||||
@@ -679,11 +682,15 @@ class SalarySlip(TransactionBase):
|
|||||||
for sc in salary_components:
|
for sc in salary_components:
|
||||||
data.setdefault(sc.salary_component_abbr, 0)
|
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 key in ("earnings", "deductions"):
|
||||||
for d in self.get(key):
|
for d in self.get(key):
|
||||||
|
default_data[d.abbr] = d.default_amount
|
||||||
data[d.abbr] = d.amount
|
data[d.abbr] = d.amount
|
||||||
|
|
||||||
return data
|
return data, default_data
|
||||||
|
|
||||||
def eval_condition_and_formula(self, d, data):
|
def eval_condition_and_formula(self, d, data):
|
||||||
try:
|
try:
|
||||||
@@ -789,7 +796,14 @@ class SalarySlip(TransactionBase):
|
|||||||
self.update_component_row(tax_row, tax_amount, "deductions")
|
self.update_component_row(tax_row, tax_amount, "deductions")
|
||||||
|
|
||||||
def update_component_row(
|
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
|
component_row = None
|
||||||
for d in self.get(component_type):
|
for d in self.get(component_type):
|
||||||
@@ -850,7 +864,7 @@ class SalarySlip(TransactionBase):
|
|||||||
additional_salary.deduct_full_tax_on_selected_payroll_date
|
additional_salary.deduct_full_tax_on_selected_payroll_date
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
component_row.default_amount = amount
|
component_row.default_amount = default_amount or amount
|
||||||
component_row.additional_amount = 0
|
component_row.additional_amount = 0
|
||||||
component_row.deduct_full_tax_on_selected_payroll_date = (
|
component_row.deduct_full_tax_on_selected_payroll_date = (
|
||||||
component_data.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
|
)[0].total_amount
|
||||||
|
|
||||||
def calculate_tax_by_tax_slab(self, annual_taxable_earning, tax_slab):
|
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})
|
data.update({"annual_taxable_earning": annual_taxable_earning})
|
||||||
tax_amount = 0
|
tax_amount = 0
|
||||||
for slab in tax_slab.slabs:
|
for slab in tax_slab.slabs:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
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 (
|
from frappe.utils import (
|
||||||
add_days,
|
add_days,
|
||||||
add_months,
|
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
|
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
|
||||||
|
|
||||||
|
|
||||||
class TestSalarySlip(unittest.TestCase):
|
class TestSalarySlip(FrappeTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
setup_test()
|
setup_test()
|
||||||
frappe.flags.pop("via_payroll_entry", None)
|
frappe.flags.pop("via_payroll_entry", None)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.db.rollback()
|
|
||||||
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
|
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
@@ -925,6 +924,41 @@ class TestSalarySlip(unittest.TestCase):
|
|||||||
# undelete fixture data
|
# undelete fixture data
|
||||||
frappe.db.rollback()
|
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):
|
def test_tax_for_recurring_additional_salary(self):
|
||||||
frappe.db.sql("""delete from `tabPayroll Period`""")
|
frappe.db.sql("""delete from `tabPayroll Period`""")
|
||||||
frappe.db.sql("""delete from `tabSalary Component`""")
|
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):
|
def make_salary_component(salary_components, test_tax, company_list=None):
|
||||||
for salary_component in salary_components:
|
for salary_component in salary_components:
|
||||||
if frappe.db.exists("Salary Component", salary_component["salary_component"]):
|
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 test_tax:
|
||||||
if salary_component["type"] == "Earning":
|
if salary_component["type"] == "Earning":
|
||||||
@@ -1442,6 +1476,10 @@ def setup_test():
|
|||||||
"Salary Slip",
|
"Salary Slip",
|
||||||
"Attendance",
|
"Attendance",
|
||||||
"Additional Salary",
|
"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)
|
frappe.db.sql("delete from `tab%s`" % dt)
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ def make_salary_structure(
|
|||||||
currency=erpnext.get_default_currency(),
|
currency=erpnext.get_default_currency(),
|
||||||
payroll_period=None,
|
payroll_period=None,
|
||||||
include_flexi_benefits=False,
|
include_flexi_benefits=False,
|
||||||
|
base=None,
|
||||||
):
|
):
|
||||||
if test_tax:
|
if test_tax:
|
||||||
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""", (salary_structure))
|
frappe.db.sql("""delete from `tabSalary Structure` where name=%s""", (salary_structure))
|
||||||
@@ -200,6 +201,7 @@ def make_salary_structure(
|
|||||||
company=company,
|
company=company,
|
||||||
currency=currency,
|
currency=currency,
|
||||||
payroll_period=payroll_period,
|
payroll_period=payroll_period,
|
||||||
|
base=base,
|
||||||
)
|
)
|
||||||
|
|
||||||
return salary_structure_doc
|
return salary_structure_doc
|
||||||
|
|||||||
Reference in New Issue
Block a user