Merge pull request #29729 from resilient-tech/allow_regional

fix: allow `regional_overrides` hook to be set in subsequent apps
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js
index 685f2d6..2ba649d 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.js
+++ b/erpnext/accounts/report/gross_profit/gross_profit.js
@@ -42,6 +42,11 @@
 	"parent_field": "parent_invoice",
 	"initial_depth": 3,
 	"formatter": function(value, row, column, data, default_formatter) {
+		if (column.fieldname == "sales_invoice" && column.options == "Item" && data.indent == 0) {
+			column._options = "Sales Invoice";
+		} else {
+			column._options = "Item";
+		}
 		value = default_formatter(value, row, column, data);
 
 		if (data && (data.indent == 0.0 || row[1].content == "Total")) {
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 9a63afc..645e97e 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -682,17 +682,18 @@
 
 		bin1 = frappe.db.get_value("Bin",
 			filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
-			fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1)
+			fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], as_dict=1)
 
 		# Submit PO
 		po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
 
 		bin2 = frappe.db.get_value("Bin",
 			filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
-			fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1)
+			fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], as_dict=1)
 
 		self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
 		self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10)
+		self.assertNotEqual(bin1.modified, bin2.modified)
 
 		# Create stock transfer
 		rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item",
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
index 41a9558..c11a821 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -8,11 +8,10 @@
 import frappe
 from frappe import _, bold
 from frappe.model.document import Document
-from frappe.utils import date_diff, flt, formatdate, get_datetime, get_last_day, getdate
+from frappe.utils import date_diff, flt, formatdate, get_last_day, getdate
 
 
 class LeavePolicyAssignment(Document):
-
 	def validate(self):
 		self.validate_policy_assignment_overlap()
 		self.set_dates()
@@ -94,10 +93,12 @@
 			new_leaves_allocated = 0
 
 		elif leave_type_details.get(leave_type).is_earned_leave == 1:
-			if self.assignment_based_on == "Leave Period":
-				new_leaves_allocated = self.get_leaves_for_passed_months(leave_type, new_leaves_allocated, leave_type_details, date_of_joining)
-			else:
+			if not self.assignment_based_on:
 				new_leaves_allocated = 0
+			else:
+				# get leaves for past months if assignment is based on Leave Period / Joining Date
+				new_leaves_allocated = self.get_leaves_for_passed_months(leave_type, new_leaves_allocated, leave_type_details, date_of_joining)
+
 		# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
 		elif getdate(date_of_joining) > getdate(self.effective_from):
 			remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1))
@@ -108,25 +109,24 @@
 	def get_leaves_for_passed_months(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
 		from erpnext.hr.utils import get_monthly_earned_leave
 
-		current_month = get_datetime(frappe.flags.current_date).month or get_datetime().month
-		current_year = get_datetime(frappe.flags.current_date).year or get_datetime().year
+		current_date = frappe.flags.current_date or getdate()
+		if current_date > getdate(self.effective_to):
+			current_date = getdate(self.effective_to)
 
-		from_date = frappe.db.get_value("Leave Period", self.leave_period, "from_date")
-		if getdate(date_of_joining) > getdate(from_date):
-			from_date = date_of_joining
-
-		from_date_month = get_datetime(from_date).month
-		from_date_year = get_datetime(from_date).year
+		from_date = getdate(self.effective_from)
+		if getdate(date_of_joining) > from_date:
+			from_date = getdate(date_of_joining)
 
 		months_passed = 0
+		based_on_doj = leave_type_details.get(leave_type).based_on_date_of_joining
 
-		if current_year == from_date_year and current_month > from_date_month:
-			months_passed = current_month - from_date_month
-			months_passed = add_current_month_if_applicable(months_passed)
+		if current_date.year == from_date.year and current_date.month >= from_date.month:
+			months_passed = current_date.month - from_date.month
+			months_passed = add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj)
 
-		elif current_year > from_date_year:
-			months_passed = (12 - from_date_month) + current_month
-			months_passed = add_current_month_if_applicable(months_passed)
+		elif current_date.year > from_date.year:
+			months_passed = (12 - from_date.month) + current_date.month
+			months_passed = add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj)
 
 		if months_passed > 0:
 			monthly_earned_leave = get_monthly_earned_leave(new_leaves_allocated,
@@ -138,13 +138,19 @@
 		return new_leaves_allocated
 
 
-def add_current_month_if_applicable(months_passed):
+def add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj):
 	date = getdate(frappe.flags.current_date) or getdate()
-	last_day_of_month = get_last_day(date)
 
-	# if its the last day of the month, then that month should also be considered
-	if last_day_of_month == date:
-		months_passed += 1
+	if based_on_doj:
+		# if leave type allocation is based on DOJ, and the date of assignment creation is same as DOJ,
+		# then the month should be considered
+		if date.day == date_of_joining.day:
+			months_passed += 1
+	else:
+		last_day_of_month = get_last_day(date)
+		# if its the last day of the month, then that month should be considered
+		if last_day_of_month == date:
+			months_passed += 1
 
 	return months_passed
 
@@ -183,7 +189,7 @@
 def get_leave_type_details():
 	leave_type_details = frappe._dict()
 	leave_types = frappe.get_all("Leave Type",
-		fields=["name", "is_lwp", "is_earned_leave", "is_compensatory",
+		fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "based_on_date_of_joining",
 			"is_carry_forward", "expire_carry_forwarded_leaves_after_days", "earned_leave_frequency", "rounding"])
 	for d in leave_types:
 		leave_type_details.setdefault(d.name, d)
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
index 8c76ca1..a19ddce 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -20,36 +20,31 @@
 class TestLeavePolicyAssignment(unittest.TestCase):
 	def setUp(self):
 		for doctype in ["Leave Period", "Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
-			frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec
+			frappe.db.delete(doctype)
+
+		employee = get_employee()
+		self.original_doj = employee.date_of_joining
+		self.employee = employee
 
 	def test_grant_leaves(self):
 		leave_period = get_leave_period()
-		employee = get_employee()
-
-		# create the leave policy with leave type "_Test Leave Type", allocation = 10
+		# allocation = 10
 		leave_policy = create_leave_policy()
 		leave_policy.submit()
 
-
 		data = {
 			"assignment_based_on": "Leave Period",
 			"leave_policy": leave_policy.name,
 			"leave_period": leave_period.name
 		}
-
-		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
-
-		leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
-		leave_policy_assignment_doc.reload()
-
-		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+		self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 1)
 
 		leave_allocation = frappe.get_list("Leave Allocation", filters={
-			"employee": employee.name,
+			"employee": self.employee.name,
 			"leave_policy":leave_policy.name,
 			"leave_policy_assignment": leave_policy_assignments[0],
 			"docstatus": 1})[0]
-
 		leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
 
 		self.assertEqual(leave_alloc_doc.new_leaves_allocated, 10)
@@ -61,63 +56,46 @@
 
 	def test_allow_to_grant_all_leave_after_cancellation_of_every_leave_allocation(self):
 		leave_period = get_leave_period()
-		employee = get_employee()
-
 		# create the leave policy with leave type "_Test Leave Type", allocation = 10
 		leave_policy = create_leave_policy()
 		leave_policy.submit()
 
-
 		data = {
 			"assignment_based_on": "Leave Period",
 			"leave_policy": leave_policy.name,
 			"leave_period": leave_period.name
 		}
-
-		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
-
-		leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
-		leave_policy_assignment_doc.reload()
-
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
 
 		# every leave is allocated no more leave can be granted now
-		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
-
+		self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 1)
 		leave_allocation = frappe.get_list("Leave Allocation", filters={
-			"employee": employee.name,
+			"employee": self.employee.name,
 			"leave_policy":leave_policy.name,
 			"leave_policy_assignment": leave_policy_assignments[0],
 			"docstatus": 1})[0]
 
 		leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
-
-		# User all allowed to grant leave when there is no allocation against assignment
 		leave_alloc_doc.cancel()
 		leave_alloc_doc.delete()
-
-		leave_policy_assignment_doc.reload()
-
-
-		# User are now allowed to grant leave
-		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 0)
+		self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 0)
 
 	def test_earned_leave_allocation(self):
 		leave_period = create_leave_period("Test Earned Leave Period")
-		employee = get_employee()
 		leave_type = create_earned_leave_type("Test Earned Leave")
 
 		leave_policy = frappe.get_doc({
 			"doctype": "Leave Policy",
 			"title": "Test Leave Policy",
 			"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 6}]
-		}).insert()
+		}).submit()
 
 		data = {
 			"assignment_based_on": "Leave Period",
 			"leave_policy": leave_policy.name,
 			"leave_period": leave_period.name
 		}
-		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
 
 		# leaves allocated should be 0 since it is an earned leave and allocation happens via scheduler based on set frequency
 		leaves_allocated = frappe.db.get_value("Leave Allocation", {
@@ -125,16 +103,8 @@
 		}, "total_leaves_allocated")
 		self.assertEqual(leaves_allocated, 0)
 
-	def test_earned_leave_allocation_for_passed_months(self):
-		employee = get_employee()
-		leave_type = create_earned_leave_type("Test Earned Leave")
-		leave_period = create_leave_period("Test Earned Leave Period",
-			start_date=get_first_day(add_months(getdate(), -1)))
-		leave_policy = frappe.get_doc({
-			"doctype": "Leave Policy",
-			"title": "Test Leave Policy",
-			"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
-		}).insert()
+	def test_earned_leave_alloc_for_passed_months_based_on_leave_period(self):
+		leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -1)))
 
 		# Case 1: assignment created one month after the leave period, should allocate 1 leave
 		frappe.flags.current_date = get_first_day(getdate())
@@ -143,24 +113,15 @@
 			"leave_policy": leave_policy.name,
 			"leave_period": leave_period.name
 		}
-		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
 
 		leaves_allocated = frappe.db.get_value("Leave Allocation", {
 			"leave_policy_assignment": leave_policy_assignments[0]
 		}, "total_leaves_allocated")
 		self.assertEqual(leaves_allocated, 1)
 
-	def test_earned_leave_allocation_for_passed_months_on_month_end(self):
-		employee = get_employee()
-		leave_type = create_earned_leave_type("Test Earned Leave")
-		leave_period = create_leave_period("Test Earned Leave Period",
-			start_date=get_first_day(add_months(getdate(), -2)))
-		leave_policy = frappe.get_doc({
-			"doctype": "Leave Policy",
-			"title": "Test Leave Policy",
-			"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
-		}).insert()
-
+	def test_earned_leave_alloc_for_passed_months_on_month_end_based_on_leave_period(self):
+		leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)))
 		# Case 2: assignment created on the last day of the leave period's latter month
 		# should allocate 1 leave for current month even though the month has not ended
 		# since the daily job might have already executed
@@ -171,7 +132,7 @@
 			"leave_policy": leave_policy.name,
 			"leave_period": leave_period.name
 		}
-		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
 
 		leaves_allocated = frappe.db.get_value("Leave Allocation", {
 			"leave_policy_assignment": leave_policy_assignments[0]
@@ -188,33 +149,17 @@
 		}, "total_leaves_allocated")
 		self.assertEqual(leaves_allocated, 3)
 
-	def test_earned_leave_allocation_for_passed_months_with_carry_forwarded_leaves(self):
+	def test_earned_leave_alloc_for_passed_months_with_cf_leaves_based_on_leave_period(self):
 		from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
 
-		employee = get_employee()
-		leave_type = create_earned_leave_type("Test Earned Leave")
-		leave_period = create_leave_period("Test Earned Leave Period",
-			start_date=get_first_day(add_months(getdate(), -2)))
-		leave_policy = frappe.get_doc({
-			"doctype": "Leave Policy",
-			"title": "Test Leave Policy",
-			"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
-		}).insert()
-
+		leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)))
 		# initial leave allocation = 5
-		leave_allocation = create_leave_allocation(
-			employee=employee.name,
-			employee_name=employee.employee_name,
-			leave_type=leave_type.name,
-			from_date=add_months(getdate(), -12),
-			to_date=add_months(getdate(), -3),
-			new_leaves_allocated=5,
-			carry_forward=0)
+		leave_allocation = create_leave_allocation(employee=self.employee.name, employee_name=self.employee.employee_name, leave_type="Test Earned Leave",
+			from_date=add_months(getdate(), -12), to_date=add_months(getdate(), -3), new_leaves_allocated=5, carry_forward=0)
 		leave_allocation.submit()
 
 		# Case 3: assignment created on the last day of the leave period's latter month with carry forwarding
 		frappe.flags.current_date = get_last_day(add_months(getdate(), -1))
-
 		data = {
 			"assignment_based_on": "Leave Period",
 			"leave_policy": leave_policy.name,
@@ -222,7 +167,7 @@
 			"carry_forward": 1
 		}
 		# carry forwarded leaves = 5, 3 leaves allocated for passed months
-		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
 
 		details = frappe.db.get_value("Leave Allocation", {
 			"leave_policy_assignment": leave_policy_assignments[0]
@@ -236,15 +181,122 @@
 		from erpnext.hr.utils import is_earned_leave_already_allocated
 		frappe.flags.current_date = get_last_day(getdate())
 
-		allocation = frappe.get_doc('Leave Allocation', details.name)
+		allocation = frappe.get_doc("Leave Allocation", details.name)
 		# 1 leave is still pending to be allocated, irrespective of carry forwarded leaves
 		self.assertFalse(is_earned_leave_already_allocated(allocation, leave_policy.leave_policy_details[0].annual_allocation))
 
+	def test_earned_leave_alloc_for_passed_months_based_on_joining_date(self):
+		# tests leave alloc for earned leaves for assignment based on joining date in policy assignment
+		leave_type = create_earned_leave_type("Test Earned Leave")
+		leave_policy = frappe.get_doc({
+			"doctype": "Leave Policy",
+			"title": "Test Leave Policy",
+			"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
+		}).submit()
+
+		# joining date set to 2 months back
+		self.employee.date_of_joining = get_first_day(add_months(getdate(), -2))
+		self.employee.save()
+
+		# assignment created on the last day of the current month
+		frappe.flags.current_date = get_last_day(getdate())
+		data = {
+			"assignment_based_on": "Joining Date",
+			"leave_policy": leave_policy.name
+		}
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
+			"total_leaves_allocated")
+		effective_from = frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "effective_from")
+		self.assertEqual(effective_from, self.employee.date_of_joining)
+		self.assertEqual(leaves_allocated, 3)
+
+		# to ensure leave is not already allocated to avoid duplication
+		from erpnext.hr.utils import allocate_earned_leaves
+		frappe.flags.current_date = get_last_day(getdate())
+		allocate_earned_leaves()
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
+			"total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 3)
+
+	def test_grant_leaves_on_doj_for_earned_leaves_based_on_leave_period(self):
+		# tests leave alloc based on leave period for earned leaves with "based on doj" configuration in leave type
+		leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)), based_on_doj=True)
+
+		# joining date set to 2 months back
+		self.employee.date_of_joining = get_first_day(add_months(getdate(), -2))
+		self.employee.save()
+
+		# assignment created on the same day of the current month, should allocate leaves including the current month
+		frappe.flags.current_date = get_first_day(getdate())
+
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name
+		}
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {
+			"leave_policy_assignment": leave_policy_assignments[0]
+		}, "total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 3)
+
+		# if the daily job is not completed yet, there is another check present
+		# to ensure leave is not already allocated to avoid duplication
+		from erpnext.hr.utils import allocate_earned_leaves
+		frappe.flags.current_date = get_first_day(getdate())
+		allocate_earned_leaves()
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {
+			"leave_policy_assignment": leave_policy_assignments[0]
+		}, "total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 3)
+
+	def test_grant_leaves_on_doj_for_earned_leaves_based_on_joining_date(self):
+		# tests leave alloc based on joining date for earned leaves with "based on doj" configuration in leave type
+		leave_type = create_earned_leave_type("Test Earned Leave", based_on_doj=True)
+		leave_policy = frappe.get_doc({
+			"doctype": "Leave Policy",
+			"title": "Test Leave Policy",
+			"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
+		}).submit()
+
+		# joining date set to 2 months back
+		# leave should be allocated for current month too since this day is same as the joining day
+		self.employee.date_of_joining = get_first_day(add_months(getdate(), -2))
+		self.employee.save()
+
+		# assignment created on the first day of the current month
+		frappe.flags.current_date = get_first_day(getdate())
+		data = {
+			"assignment_based_on": "Joining Date",
+			"leave_policy": leave_policy.name
+		}
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
+			"total_leaves_allocated")
+		effective_from = frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "effective_from")
+		self.assertEqual(effective_from, self.employee.date_of_joining)
+		self.assertEqual(leaves_allocated, 3)
+
+		# to ensure leave is not already allocated to avoid duplication
+		from erpnext.hr.utils import allocate_earned_leaves
+		frappe.flags.current_date = get_first_day(getdate())
+		allocate_earned_leaves()
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
+			"total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 3)
+
 	def tearDown(self):
 		frappe.db.rollback()
+		frappe.db.set_value("Employee", self.employee.name, "date_of_joining", self.original_doj)
+		frappe.flags.current_date = None
 
 
-def create_earned_leave_type(leave_type):
+def create_earned_leave_type(leave_type, based_on_doj=False):
 	frappe.delete_doc_if_exists("Leave Type", leave_type, force=1)
 
 	return frappe.get_doc(dict(
@@ -253,7 +305,8 @@
 		is_earned_leave=1,
 		earned_leave_frequency="Monthly",
 		rounding=0.5,
-		is_carry_forward=1
+		is_carry_forward=1,
+		based_on_date_of_joining=based_on_doj
 	)).insert()
 
 
@@ -269,4 +322,17 @@
 		to_date=add_months(start_date, 12),
 		company="_Test Company",
 		is_active=1
-	)).insert()
\ No newline at end of file
+	)).insert()
+
+
+def setup_leave_period_and_policy(start_date, based_on_doj=False):
+	leave_type = create_earned_leave_type("Test Earned Leave", based_on_doj)
+	leave_period = create_leave_period("Test Earned Leave Period",
+		start_date=start_date)
+	leave_policy = frappe.get_doc({
+		"doctype": "Leave Policy",
+		"title": "Test Leave Policy",
+		"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
+	}).insert()
+
+	return leave_period, leave_policy
\ No newline at end of file
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 7fd3a98..c174047 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -261,10 +261,10 @@
 
 			from_date=allocation.from_date
 
-			if e_leave_type.based_on_date_of_joining_date:
+			if e_leave_type.based_on_date_of_joining:
 				from_date  = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
 
-			if check_effective_date(from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining_date):
+			if check_effective_date(from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining):
 				update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, ignore_duplicates)
 
 def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, ignore_duplicates=False):
@@ -343,7 +343,7 @@
 	allocation.unused_leaves = 0
 	allocation.create_leave_ledger_entry()
 
-def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining_date):
+def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining):
 	import calendar
 
 	from dateutil import relativedelta
@@ -354,7 +354,7 @@
 	#last day of month
 	last_day =  calendar.monthrange(to_date.year, to_date.month)[1]
 
-	if (from_date.day == to_date.day and based_on_date_of_joining_date) or (not based_on_date_of_joining_date and to_date.day == last_day):
+	if (from_date.day == to_date.day and based_on_date_of_joining) or (not based_on_date_of_joining and to_date.day == last_day):
 		if frequency == "Monthly":
 			return True
 		elif frequency == "Quarterly" and rd.months % 3:
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 4290ca3..55054bb 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -341,6 +341,7 @@
 
 	def get_production_items(self):
 		item_dict = {}
+
 		for d in self.po_items:
 			item_details = {
 				"production_item"		: d.item_code,
@@ -357,12 +358,12 @@
 				"production_plan"       : self.name,
 				"production_plan_item"  : d.name,
 				"product_bundle_item"	: d.product_bundle_item,
-				"planned_start_date"    : d.planned_start_date
+				"planned_start_date"    : d.planned_start_date,
+				"project"               : self.project
 			}
 
-			item_details.update({
-				"project": self.project or frappe.db.get_value("Sales Order", d.sales_order, "project")
-			})
+			if not item_details['project'] and d.sales_order:
+				item_details['project'] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
 
 			if self.get_items_from == "Material Request":
 				item_details.update({
@@ -380,39 +381,59 @@
 
 	@frappe.whitelist()
 	def make_work_order(self):
+		from erpnext.manufacturing.doctype.work_order.work_order import get_default_warehouse
+
 		wo_list, po_list = [], []
 		subcontracted_po = {}
+		default_warehouses = get_default_warehouse()
 
-		self.validate_data()
-		self.make_work_order_for_finished_goods(wo_list)
-		self.make_work_order_for_subassembly_items(wo_list, subcontracted_po)
+		self.make_work_order_for_finished_goods(wo_list, default_warehouses)
+		self.make_work_order_for_subassembly_items(wo_list, subcontracted_po, default_warehouses)
 		self.make_subcontracted_purchase_order(subcontracted_po, po_list)
 		self.show_list_created_message('Work Order', wo_list)
 		self.show_list_created_message('Purchase Order', po_list)
 
-	def make_work_order_for_finished_goods(self, wo_list):
+	def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
 		items_data = self.get_production_items()
 
 		for key, item in items_data.items():
 			if self.sub_assembly_items:
 				item['use_multi_level_bom'] = 0
 
+			set_default_warehouses(item, default_warehouses)
 			work_order = self.create_work_order(item)
 			if work_order:
 				wo_list.append(work_order)
 
-	def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po):
+	def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po, default_warehouses):
 		for row in self.sub_assembly_items:
 			if row.type_of_manufacturing == 'Subcontract':
 				subcontracted_po.setdefault(row.supplier, []).append(row)
 				continue
 
-			args = {}
-			self.prepare_args_for_sub_assembly_items(row, args)
-			work_order = self.create_work_order(args)
+			work_order_data = {
+				'wip_warehouse': default_warehouses.get('wip_warehouse'),
+				'fg_warehouse': default_warehouses.get('fg_warehouse')
+			}
+
+			self.prepare_data_for_sub_assembly_items(row, work_order_data)
+			work_order = self.create_work_order(work_order_data)
 			if work_order:
 				wo_list.append(work_order)
 
+	def prepare_data_for_sub_assembly_items(self, row, wo_data):
+		for field in ["production_item", "item_name", "qty", "fg_warehouse",
+			"description", "bom_no", "stock_uom", "bom_level",
+			"production_plan_item", "schedule_date"]:
+			if row.get(field):
+				wo_data[field] = row.get(field)
+
+		wo_data.update({
+			"use_multi_level_bom": 0,
+			"production_plan": self.name,
+			"production_plan_sub_assembly_item": row.name
+		})
+
 	def make_subcontracted_purchase_order(self, subcontracted_po, purchase_orders):
 		if not subcontracted_po:
 			return
@@ -423,7 +444,7 @@
 			po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate()
 			po.is_subcontracted = 'Yes'
 			for row in po_list:
-				args = {
+				po_data = {
 					'item_code': row.production_item,
 					'warehouse': row.fg_warehouse,
 					'production_plan_sub_assembly_item': row.name,
@@ -433,9 +454,9 @@
 
 				for field in ['schedule_date', 'qty', 'uom', 'stock_uom', 'item_name',
 					'description', 'production_plan_item']:
-					args[field] = row.get(field)
+					po_data[field] = row.get(field)
 
-				po.append('items', args)
+				po.append('items', po_data)
 
 			po.set_missing_values()
 			po.flags.ignore_mandatory = True
@@ -452,24 +473,9 @@
 			doc_list = [get_link_to_form(doctype, p) for p in doc_list]
 			msgprint(_("{0} created").format(comma_and(doc_list)))
 
-	def prepare_args_for_sub_assembly_items(self, row, args):
-		for field in ["production_item", "item_name", "qty", "fg_warehouse",
-			"description", "bom_no", "stock_uom", "bom_level",
-			"production_plan_item", "schedule_date"]:
-			args[field] = row.get(field)
-
-		args.update({
-			"use_multi_level_bom": 0,
-			"production_plan": self.name,
-			"production_plan_sub_assembly_item": row.name
-		})
-
 	def create_work_order(self, item):
-		from erpnext.manufacturing.doctype.work_order.work_order import (
-			OverProductionError,
-			get_default_warehouse,
-		)
-		warehouse = get_default_warehouse()
+		from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
+
 		wo = frappe.new_doc("Work Order")
 		wo.update(item)
 		wo.planned_start_date = item.get('planned_start_date') or item.get('schedule_date')
@@ -478,11 +484,11 @@
 			wo.fg_warehouse = item.get("warehouse")
 
 		wo.set_work_order_operations()
+		wo.set_required_items()
 
-		if not wo.fg_warehouse:
-			wo.fg_warehouse = warehouse.get('fg_warehouse')
 		try:
 			wo.flags.ignore_mandatory = True
+			wo.flags.ignore_validate = True
 			wo.insert()
 			return wo.name
 		except OverProductionError:
@@ -1023,3 +1029,8 @@
 
 			if d.value:
 				get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent+1)
+
+def set_default_warehouses(row, default_warehouses):
+	for field in ['wip_warehouse', 'fg_warehouse']:
+		if not row.get(field):
+			row[field] = default_warehouses.get(field)
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 7315249..9dd3fa7 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -76,7 +76,6 @@
 
 		self.set_required_items(reset_only_qty = len(self.get("required_items")))
 
-
 	def validate_sales_order(self):
 		if self.sales_order:
 			self.check_sales_order_on_hold_or_close()
@@ -546,7 +545,7 @@
 				if node.is_bom:
 					operations.extend(_get_operations(node.name, qty=node.exploded_qty))
 
-		bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
+		bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity")
 		operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
 
 		for correct_index, operation in enumerate(operations, start=1):
@@ -627,7 +626,7 @@
 			frappe.delete_doc("Job Card", d.name)
 
 	def validate_production_item(self):
-		if frappe.db.get_value("Item", self.production_item, "has_variants"):
+		if frappe.get_cached_value("Item", self.production_item, "has_variants"):
 			frappe.throw(_("Work Order cannot be raised against a Item Template"), ItemHasVariantError)
 
 		if self.production_item:
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js
index d4f7c9c..3d69c46 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.js
+++ b/erpnext/payroll/doctype/gratuity/gratuity.js
@@ -3,6 +3,14 @@
 
 frappe.ui.form.on('Gratuity', {
 	setup: function (frm) {
+		frm.set_query("salary_component", function () {
+			return {
+				filters: {
+					type: "Earning"
+				}
+			};
+		});
+
 		frm.set_query("expense_account", function () {
 			return {
 				filters: {
@@ -24,7 +32,7 @@
 		});
 	},
 	refresh: function (frm) {
-		if (frm.doc.docstatus == 1 && frm.doc.status == "Unpaid") {
+		if (frm.doc.docstatus == 1 && !frm.doc.pay_via_salary_slip && frm.doc.status == "Unpaid") {
 			frm.add_custom_button(__("Create Payment Entry"), function () {
 				return frappe.call({
 					method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry',
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json
index 1970895..1fd1cec 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.json
+++ b/erpnext/payroll/doctype/gratuity/gratuity.json
@@ -1,7 +1,7 @@
 {
  "actions": [],
  "autoname": "HR-GRA-PAY-.#####",
- "creation": "2020-08-05 20:52:13.024683",
+ "creation": "2022-01-27 16:24:28.200061",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
@@ -16,6 +16,9 @@
   "company",
   "gratuity_rule",
   "section_break_5",
+  "pay_via_salary_slip",
+  "payroll_date",
+  "salary_component",
   "payable_account",
   "expense_account",
   "mode_of_payment",
@@ -78,18 +81,20 @@
    "reqd": 1
   },
   {
+   "depends_on": "eval: !doc.pay_via_salary_slip",
    "fieldname": "expense_account",
    "fieldtype": "Link",
    "label": "Expense Account",
-   "options": "Account",
-   "reqd": 1
+   "mandatory_depends_on": "eval: !doc.pay_via_salary_slip",
+   "options": "Account"
   },
   {
+   "depends_on": "eval: !doc.pay_via_salary_slip",
    "fieldname": "mode_of_payment",
    "fieldtype": "Link",
    "label": "Mode of Payment",
-   "options": "Mode of Payment",
-   "reqd": 1
+   "mandatory_depends_on": "eval: !doc.pay_via_salary_slip",
+   "options": "Mode of Payment"
   },
   {
    "fieldname": "gratuity_rule",
@@ -151,23 +156,45 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval: !doc.pay_via_salary_slip",
    "fieldname": "payable_account",
    "fieldtype": "Link",
    "label": "Payable Account",
-   "options": "Account",
-   "reqd": 1
+   "mandatory_depends_on": "eval: !doc.pay_via_salary_slip",
+   "options": "Account"
   },
   {
    "fieldname": "cost_center",
    "fieldtype": "Link",
    "label": "Cost Center",
    "options": "Cost Center"
+  },
+  {
+   "default": "1",
+   "fieldname": "pay_via_salary_slip",
+   "fieldtype": "Check",
+   "label": "Pay via Salary Slip"
+  },
+  {
+   "depends_on": "pay_via_salary_slip",
+   "fieldname": "payroll_date",
+   "fieldtype": "Date",
+   "label": "Payroll Date",
+   "mandatory_depends_on": "pay_via_salary_slip"
+  },
+  {
+   "depends_on": "pay_via_salary_slip",
+   "fieldname": "salary_component",
+   "fieldtype": "Link",
+   "label": "Salary Component",
+   "mandatory_depends_on": "pay_via_salary_slip",
+   "options": "Salary Component"
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-01-19 12:54:37.306145",
+ "modified": "2022-02-02 14:00:45.536152",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Gratuity",
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py
index 476990a..939634a 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/gratuity.py
@@ -21,7 +21,10 @@
 			self.status = "Unpaid"
 
 	def on_submit(self):
-		self.create_gl_entries()
+		if self.pay_via_salary_slip:
+			self.create_additional_salary()
+		else:
+			self.create_gl_entries()
 
 	def on_cancel(self):
 		self.ignore_linked_doctypes = ['GL Entry']
@@ -64,6 +67,19 @@
 
 		return gl_entry
 
+	def create_additional_salary(self):
+		if self.pay_via_salary_slip:
+			additional_salary = frappe.new_doc('Additional Salary')
+			additional_salary.employee = self.employee
+			additional_salary.salary_component = self.salary_component
+			additional_salary.overwrite_salary_structure_amount = 0
+			additional_salary.amount = self.amount
+			additional_salary.payroll_date = self.payroll_date
+			additional_salary.company = self.company
+			additional_salary.ref_doctype = self.doctype
+			additional_salary.ref_docname = self.name
+			additional_salary.submit()
+
 	def set_total_advance_paid(self):
 		paid_amount = frappe.db.sql("""
 			select ifnull(sum(debit_in_account_currency), 0) as paid_amount
diff --git a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
index aeadba1..771a6fe 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
+++ b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
@@ -10,7 +10,7 @@
 		'transactions': [
 			{
 				'label': _('Payment'),
-				'items': ['Payment Entry']
+				'items': ['Payment Entry', 'Additional Salary']
 			}
 		]
 	}
diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py
index 93cba06..90e8061 100644
--- a/erpnext/payroll/doctype/gratuity/test_gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py
@@ -18,27 +18,25 @@
 
 test_dependencies = ["Salary Component", "Salary Slip", "Account"]
 class TestGratuity(unittest.TestCase):
-	@classmethod
-	def setUpClass(cls):
+	def setUp(self):
+		frappe.db.delete("Gratuity")
+		frappe.db.delete("Additional Salary", {"ref_doctype": "Gratuity"})
+
 		make_earning_salary_component(setup=True, test_tax=True, company_list=['_Test Company'])
 		make_deduction_salary_component(setup=True, test_tax=True, company_list=['_Test Company'])
 
-	def setUp(self):
-		frappe.db.sql("DELETE FROM `tabGratuity`")
-
 	def test_get_last_salary_slip_should_return_none_for_new_employee(self):
 		new_employee = make_employee("new_employee@salary.com", company='_Test Company')
 		salary_slip = get_last_salary_slip(new_employee)
 		assert salary_slip is None
 
-	def test_check_gratuity_amount_based_on_current_slab(self):
+	def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self):
 		employee, sal_slip = create_employee_and_get_last_salary_slip()
 
 		rule = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)")
+		gratuity = create_gratuity(pay_via_salary_slip=1, employee=employee, rule=rule.name)
 
-		gratuity = create_gratuity(employee=employee, rule=rule.name)
-
-		#work experience calculation
+		# work experience calculation
 		date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date'])
 		employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days
 
@@ -64,6 +62,9 @@
 
 		self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2))
 
+		# additional salary creation (Pay via salary slip)
+		self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name}))
+
 	def test_check_gratuity_amount_based_on_all_previous_slabs(self):
 		employee, sal_slip = create_employee_and_get_last_salary_slip()
 		rule = get_gratuity_rule("Rule Under Limited Contract (UAE)")
@@ -117,8 +118,8 @@
 		self.assertEqual(flt(gratuity.paid_amount,2), flt(gratuity.amount, 2))
 
 	def tearDown(self):
-		frappe.db.sql("DELETE FROM `tabGratuity`")
-		frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'")
+		frappe.db.rollback()
+
 
 def get_gratuity_rule(name):
 	rule = frappe.db.exists("Gratuity Rule", name)
@@ -141,9 +142,14 @@
 	gratuity.employee = args.employee
 	gratuity.posting_date = getdate()
 	gratuity.gratuity_rule = args.rule or "Rule Under Limited Contract (UAE)"
-	gratuity.expense_account = args.expense_account or 'Payment Account - _TC'
-	gratuity.payable_account = args.payable_account or get_payable_account("_Test Company")
-	gratuity.mode_of_payment = args.mode_of_payment or 'Cash'
+	gratuity.pay_via_salary_slip = args.pay_via_salary_slip or 0
+	if gratuity.pay_via_salary_slip:
+		gratuity.payroll_date = getdate()
+		gratuity.salary_component = "Performance Bonus"
+	else:
+		gratuity.expense_account = args.expense_account or 'Payment Account - _TC'
+		gratuity.payable_account = args.payable_account or get_payable_account("_Test Company")
+		gratuity.mode_of_payment = args.mode_of_payment or 'Cash'
 
 	gratuity.save()
 	gratuity.submit()
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 8715ef5..d443f9c 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -219,7 +219,6 @@
 
 	if not party_details.place_of_supply: return party_details
 	if not party_details.company_gstin: return party_details
-	if not party_details.supplier_gstin: return party_details
 
 	if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin
 		and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice",
diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js
index 4124e3d..03c729e 100644
--- a/erpnext/regional/report/datev/datev.js
+++ b/erpnext/regional/report/datev/datev.js
@@ -40,7 +40,11 @@
 		});
 
 		query_report.page.add_menu_item(__("Download DATEV File"), () => {
-			const filters = JSON.stringify(query_report.get_values());
+			const filters = encodeURIComponent(
+				JSON.stringify(
+					query_report.get_values()
+				)
+			);
 			window.open(`/api/method/erpnext.regional.report.datev.datev.download_datev_csv?filters=${filters}`);
 		});
 
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index d2bae65..3bc15a8 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -20,18 +20,6 @@
 			+ flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
 			- flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract))
 
-	def get_first_sle(self):
-		sle = frappe.qb.DocType("Stock Ledger Entry")
-		first_sle = (
-				frappe.qb.from_(sle)
-					.select("*")
-					.where((sle.item_code == self.item_code) & (sle.warehouse == self.warehouse))
-					.orderby(sle.posting_date, sle.posting_time, sle.creation)
-					.limit(1)
-				).run(as_dict=True)
-
-		return first_sle and first_sle[0] or None
-
 	def update_reserved_qty_for_production(self):
 		'''Update qty reserved for production from Production Item tables
 			in open work orders'''
@@ -107,13 +95,6 @@
 	frappe.db.add_unique("Bin", ["item_code", "warehouse"], constraint_name="unique_item_warehouse")
 
 
-def update_stock(bin_name, args, allow_negative_stock=False, via_landed_cost_voucher=False):
-	"""WARNING: This function is deprecated. Inline this function instead of using it."""
-	from erpnext.stock.stock_ledger import repost_current_voucher
-
-	repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher)
-	update_qty(bin_name, args)
-
 def get_bin_details(bin_name):
 	return frappe.db.get_value('Bin', bin_name, ['actual_qty', 'ordered_qty',
 	'reserved_qty', 'indented_qty', 'planned_qty', 'reserved_qty_for_production',
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 6663458..62017e4 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -3,10 +3,9 @@
 
 
 import frappe
-from frappe.utils import cstr, flt, nowdate, nowtime
+from frappe.utils import cstr, flt, now, nowdate, nowtime
 
 from erpnext.controllers.stock_controller import create_repost_item_valuation_entry
-from erpnext.stock.utils import update_bin
 
 
 def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False, only_bin=False):
@@ -175,6 +174,7 @@
 			bin.set(field, flt(value))
 			mismatch = True
 
+	bin.modified = now()
 	if mismatch:
 		bin.set_projected_qty()
 		bin.db_update()
@@ -227,8 +227,6 @@
 			"sle_id": sle_doc.name
 		})
 
-		update_bin(args)
-
 		create_repost_item_valuation_entry({
 			"item_code": d[0],
 			"warehouse": d[1],
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index c75c737..7263e39 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -206,16 +206,6 @@
 
 	return bin_obj
 
-def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False):
-	"""WARNING: This function is deprecated. Inline this function instead of using it."""
-	from erpnext.stock.doctype.bin.bin import update_stock
-	is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item')
-	if is_stock_item:
-		bin_name = get_or_make_bin(args.get("item_code"), args.get("warehouse"))
-		update_stock(bin_name, args, allow_negative_stock, via_landed_cost_voucher)
-	else:
-		frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))
-
 @frappe.whitelist()
 def get_incoming_rate(args, raise_error_if_no_rate=True):
 	"""Get Incoming Rate based on valuation method"""
diff --git a/erpnext/utilities/doctype/rename_tool/rename_tool.js b/erpnext/utilities/doctype/rename_tool/rename_tool.js
index 7823055..5553e44 100644
--- a/erpnext/utilities/doctype/rename_tool/rename_tool.js
+++ b/erpnext/utilities/doctype/rename_tool/rename_tool.js
@@ -13,6 +13,12 @@
 	},
 	refresh: function(frm) {
 		frm.disable_save();
+
+		frm.get_field("file_to_rename").df.options = {
+			restrictions: {
+				allowed_file_types: [".csv"],
+			},
+		};
 		if (!frm.doc.file_to_rename) {
 			frm.get_field("rename_log").$wrapper.html("");
 		}