Repository: frappe/hrms

Issue:
Unable to Submit Payroll Entry for 2023
### Information about bug

**Description:**

**Steps to Reproduce:**
1. Navigate to Payroll > Payroll Entry.
2. Create a new Payroll Entry for the year 2023 for Apr Month.
3. While attempting to submit the Payroll Entry, an error occurs.

**Issue:**

The issue arises from the Salary Slip associated with the Payroll Entry. The Income Tax Slabs are being fetched from the Salary Structure Assignment without respecting the time range. Specifically, the Salary Slip retrieves the last Salary Structure Assignment, and inside this assignment, there is an Income Tax Slab with an effective from date greater than the Payroll Entry start date.

```py
	def get_income_tax_slabs(self):
		income_tax_slab, ss_assignment_name = frappe.db.get_value(
			"Salary Structure Assignment",
			{"employee": self.employee, "salary_structure": self.salary_structure, "docstatus": 1},
			["income_tax_slab", "name"],
		)
```

**Expected Behavior:**
The Payroll Entry should consider the correct Salary Structure Assignment based on the time range, ensuring that the Income Tax Slabs are applicable for the specified period.

**Solution**
Resolving this by fetching the appropriate income tax slab based on the salary structure assignment valid from_date and the income tax master valid effective_from  date

```py
        def get_income_tax_slabs(self):
            income_tax_slab, ss_assignment_name = frappe.db.get_value(
                "Salary Structure Assignment",
                {"employee": self.employee, "salary_structure": self.salary_structure, "docstatus": 1, "from_date": ("<=",  self.end_date)},
                ["income_tax_slab", "name"],
                order_by="from_date desc",
            )

```


### Module

Payroll

### Version

- ERPNext version: 14.61.0
- Frappe version: 14.62.3
- HRMS version: 14.21.2

### Installation method

manual install

### Relevant log output / Stack trace / Full Error Message.

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:

Resolving patch:
diff --git a/hrms/payroll/doctype/salary_slip/salary_slip.py b/hrms/payroll/doctype/salary_slip/salary_slip.py
--- a/hrms/payroll/doctype/salary_slip/salary_slip.py
+++ b/hrms/payroll/doctype/salary_slip/salary_slip.py
@@ -1393,10 +1393,15 @@ def calculate_variable_tax(self, tax_component):
 	def get_income_tax_slabs(self):
 		income_tax_slab, ss_assignment_name = frappe.db.get_value(
 			"Salary Structure Assignment",
-			{"employee": self.employee, "salary_structure": self.salary_structure, "docstatus": 1},
+			{
+				"employee": self.employee,
+				"salary_structure": self.salary_structure,
+				"docstatus": 1,
+				"from_date": ("<=", self.end_date),
+			},
 			["income_tax_slab", "name"],
+			order_by="from_date desc",
 		)
-
 		if not income_tax_slab:
 			frappe.throw(
 				_("Income Tax Slab not set in Salary Structure Assignment: {0}").format(ss_assignment_name)


Test patch:
diff --git a/hrms/payroll/doctype/salary_slip/test_salary_slip.py b/hrms/payroll/doctype/salary_slip/test_salary_slip.py
--- a/hrms/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/hrms/payroll/doctype/salary_slip/test_salary_slip.py
@@ -40,7 +40,9 @@
 	make_salary_slip_from_timesheet,
 )
 from hrms.payroll.doctype.salary_structure.salary_structure import make_salary_slip
-from hrms.tests.test_utils import get_first_sunday
+from hrms.tests.test_utils import create_company, get_first_sunday
+
+HOLIDAY_LIST = "Salary Slip Test Holiday List"
 
 
 class TestSalarySlip(FrappeTestCase):
@@ -62,9 +64,7 @@ def test_employee_status_inactive(self):
 		employee_doc.reload()
 
 		make_holiday_list()
-		frappe.db.set_value(
-			"Company", employee_doc.company, "default_holiday_list", "Salary Slip Test Holiday List"
-		)
+		frappe.db.set_value("Company", employee_doc.company, "default_holiday_list", HOLIDAY_LIST)
 
 		frappe.db.sql(
 			"""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'"""
@@ -228,7 +228,7 @@ def test_payment_days_for_mid_joinee_including_holidays_and_unmarked_days(self):
 
 		for days in range(date_diff(relieving_date, joining_date) + 1):
 			date = add_days(joining_date, days)
-			if not is_holiday("Salary Slip Test Holiday List", date):
+			if not is_holiday(HOLIDAY_LIST, date):
 				mark_attendance(new_emp_id, date, "Present", ignore_validate=True)
 			else:
 				holidays += 1
@@ -274,7 +274,7 @@ def test_payment_days_for_mid_joinee_excluding_holidays(self):
 
 		for days in range(date_diff(relieving_date, joining_date) + 1):
 			date = add_days(joining_date, days)
-			if not is_holiday("Salary Slip Test Holiday List", date):
+			if not is_holiday(HOLIDAY_LIST, date):
 				mark_attendance(new_emp_id, date, "Present", ignore_validate=True)
 			else:
 				holidays += 1
@@ -338,7 +338,7 @@ def test_payment_days_in_salary_slip_based_on_timesheet(self):
 		emp = make_employee(
 			"test_employee_timesheet@salary.com",
 			company="_Test Company",
-			holiday_list="Salary Slip Test Holiday List",
+			holiday_list=HOLIDAY_LIST,
 		)
 		frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"})
 
@@ -1484,6 +1484,60 @@ def test_variable_tax_component(self):
 		self.assertEqual(test_tds.accounts[0].company, salary_slip.company)
 		self.assertListEqual(tax_component, ["_Test TDS"])
 
+	def test_get_income_tax_slabs(self):
+		from hrms.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+
+		company = create_company("_Test Tax Slab").name
+		frappe.db.delete("Payroll Period", {"company": company})
+		employee = make_employee("test_tax_slab@salary.com", company=company, holiday_list=HOLIDAY_LIST)
+
+		fiscal_year = get_fiscal_year(getdate(), as_dict=True)
+		year_start, year_end = fiscal_year.year_start_date, fiscal_year.year_end_date
+		mid_year = add_months(year_start, 6)
+
+		payroll_period = create_payroll_period(
+			name="Payroll Period Test",
+			company=company,
+			start_date=year_start,
+			end_date=year_end,
+		)
+
+		tax_slabs = []
+		salary_structures = []
+		for start_date, end_date in [(year_start, mid_year), (mid_year, year_end)]:
+			tax_slab = create_tax_slab(
+				payroll_period,
+				allow_tax_exemption=True,
+				currency="INR",
+				effective_date=getdate(start_date),
+				company=company,
+			)
+			tax_slabs.append(tax_slab)
+			salary_structure = make_salary_structure(
+				f"Structure {start_date}",
+				"Monthly",
+				employee=employee,
+				company=company,
+				currency="INR",
+				from_date=start_date,
+				payroll_period=payroll_period,
+				allow_duplicate=True,
+			)
+			salary_structures.append(salary_structure)
+
+		# fetches the correct tax slab for backdated salary slip
+		salary_slip = make_salary_slip(
+			salary_structures[0].name, employee=employee, posting_date=year_start
+		)
+		salary_slip.insert()
+		self.assertEqual(salary_slip.get_income_tax_slabs().name, tax_slabs[0])
+
+		salary_slip = make_salary_slip(
+			salary_structures[1].name, employee=employee, posting_date=add_days(mid_year, 1)
+		)
+		salary_slip.insert()
+		self.assertEqual(salary_slip.get_income_tax_slabs().name, tax_slabs[1])
+
 
 class TestSalarySlipSafeEval(FrappeTestCase):
 	def test_safe_eval_for_salary_slip(self):
@@ -1575,10 +1629,13 @@ def make_employee_salary_slip(emp_id, payroll_frequency, salary_structure=None,
 	salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": emp_id})
 
 	if not salary_slip_name:
-		salary_slip = make_salary_slip(salary_structure_doc.name, employee=employee.name)
+		date = posting_date or nowdate()
+		salary_slip = make_salary_slip(
+			salary_structure_doc.name, employee=employee.name, posting_date=date
+		)
 		salary_slip.employee_name = employee.employee_name
 		salary_slip.payroll_frequency = payroll_frequency
-		salary_slip.posting_date = posting_date or nowdate()
+		salary_slip.posting_date = date
 		salary_slip.insert()
 	else:
 		salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)
@@ -2011,7 +2068,7 @@ def setup_test():
 	make_payroll_period()
 
 	frappe.db.set_value(
-		"Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List"
+		"Company", erpnext.get_default_company(), "default_holiday_list", HOLIDAY_LIST
 	)
 
 	frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 0)
@@ -2041,7 +2098,7 @@ def make_payroll_period():
 
 def make_holiday_list(list_name=None, from_date=None, to_date=None, add_weekly_offs=True):
 	fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
-	name = list_name or "Salary Slip Test Holiday List"
+	name = list_name or HOLIDAY_LIST
 
 	frappe.delete_doc_if_exists("Holiday List", name, force=True)
 
diff --git a/hrms/payroll/doctype/salary_structure/test_salary_structure.py b/hrms/payroll/doctype/salary_structure/test_salary_structure.py
--- a/hrms/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/hrms/payroll/doctype/salary_structure/test_salary_structure.py
@@ -168,6 +168,7 @@ def make_salary_structure(
 	payroll_period=None,
 	include_flexi_benefits=False,
 	base=None,
+	allow_duplicate=False,
 ):
 	if frappe.db.exists("Salary Structure", salary_structure):
 		frappe.db.delete("Salary Structure", salary_structure)
@@ -196,7 +197,7 @@ def make_salary_structure(
 	if not dont_submit:
 		salary_structure_doc.submit()
 
-	filters = {"employee": employee, "docstatus": 1}
+	filters = {"employee": employee, "docstatus": 1, "salary_structure": salary_structure}
 	if not from_date and payroll_period:
 		from_date = payroll_period.start_date
 
@@ -216,6 +217,7 @@ def make_salary_structure(
 			currency=currency,
 			payroll_period=payroll_period,
 			base=base,
+			allow_duplicate=allow_duplicate,
 		)
 
 	return salary_structure_doc