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>
diff --git a/erpnext/hr/doctype/additional_salary/additional_salary.py b/erpnext/hr/doctype/additional_salary/additional_salary.py
index 8498b3d..bc7dcee 100644
--- a/erpnext/hr/doctype/additional_salary/additional_salary.py
+++ b/erpnext/hr/doctype/additional_salary/additional_salary.py
@@ -39,19 +39,21 @@
 		return amount_per_day * no_of_days
 
 @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("""
 		select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
 		from `tabAdditional Salary`
 		where employee=%(employee)s
 			and docstatus = 1
 			and payroll_date between %(from_date)s and %(to_date)s
+			and type = %(component_type)s
 		group by salary_component, overwrite_salary_structure_amount
 		order by salary_component, overwrite_salary_structure_amount
 	""", {
 		'employee': employee,
 		'from_date': start_date,
-		'to_date': end_date
+		'to_date': end_date,
+		'component_type': "Earning" if component_type == "earnings" else "Deduction"
 	}, as_dict=1)
 
 	additional_components_list = []
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index eee7974..d03a3dd 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -299,9 +299,11 @@
 
 	def calculate_net_pay(self):
 		if self.salary_structure:
-			self.calculate_component_amounts()
-
+			self.calculate_component_amounts("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.set_loan_repayment()
@@ -309,25 +311,27 @@
 		self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
 		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):
 			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)
 
-		self.add_structure_components()
-		self.add_employee_benefits(payroll_period)
-		self.add_additional_salary_components()
-		self.add_tax_components(payroll_period)
-		self.set_component_amounts_based_on_payment_days()
+		self.add_structure_components(component_type)
+		self.add_additional_salary_components(component_type)
+		if component_type == "earnings":
+			self.add_employee_benefits(payroll_period)
+		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()
-		for key in ('earnings', 'deductions'):
-			for struct_row in self._salary_structure_doc.get(key):
-				amount = self.eval_condition_and_formula(struct_row, data)
-				if amount and struct_row.statistical_component == 0:
-					self.update_component_row(struct_row, amount, key)
+		for struct_row in self._salary_structure_doc.get(component_type):
+			amount = self.eval_condition_and_formula(struct_row, data)
+			if amount and struct_row.statistical_component == 0:
+				self.update_component_row(struct_row, amount, component_type)
 
 	def get_data_for_eval(self):
 		'''Returns data for evaluating formula'''
@@ -400,14 +404,15 @@
 						amount = last_benefit.amount
 						self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings")
 
-	def add_additional_salary_components(self):
-		additional_components = get_additional_salary_component(self.employee, self.start_date, self.end_date)
+	def add_additional_salary_components(self, component_type):
+		additional_components = get_additional_salary_component(self.employee,
+			self.start_date, self.end_date, component_type)
 		if additional_components:
 			for additional_component in additional_components:
 				amount = additional_component.amount
 				overwrite = additional_component.overwrite
-				key = "earnings" if additional_component.type == "Earning" else "deductions"
-				self.update_component_row(frappe._dict(additional_component.struct_row), amount, key, overwrite=overwrite)
+				self.update_component_row(frappe._dict(additional_component.struct_row), amount,
+					component_type, overwrite=overwrite)
 
 	def add_tax_components(self, payroll_period):
 		# Calculate variable_based_on_taxable_salary after all components updated in salary slip
@@ -736,7 +741,7 @@
 				total += d.amount
 		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,
 			["date_of_joining", "relieving_date"])
 
@@ -746,9 +751,8 @@
 		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 = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
+		for d in self.get(component_type):
+			d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
 
 	def set_loan_repayment(self):
 		self.set('loans', [])
diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
index 7815094..6ca6dfd 100644
--- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py
@@ -25,7 +25,6 @@
 		make_employee("test_employee@salary.com")
 		make_employee("test_employee_2@salary.com")
 
-
 	def make_holiday_list(self):
 		if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"):
 			holiday_list = frappe.get_doc({
@@ -38,6 +37,29 @@
 			holiday_list.get_weekly_off_dates()
 			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):
 		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"})