Merge branch 'develop' into fix_asset_sold_status
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index b126d57..6f8b382 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -1553,7 +1553,7 @@
  "icon": "fa fa-file-text",
  "is_submittable": 1,
  "links": [],
- "modified": "2022-03-22 13:00:24.166684",
+ "modified": "2022-09-27 13:00:24.166684",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 6e3a076..fbe0ef3 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -239,14 +239,14 @@
 					frappe.bold(d.warehouse),
 					frappe.bold(d.qty),
 				)
-				if flt(available_stock) <= 0:
+				if is_stock_item and flt(available_stock) <= 0:
 					frappe.throw(
 						_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
 							d.idx, item_code, warehouse
 						),
 						title=_("Item Unavailable"),
 					)
-				elif flt(available_stock) < flt(d.qty):
+				elif is_stock_item and flt(available_stock) < flt(d.qty):
 					frappe.throw(
 						_(
 							"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
@@ -632,11 +632,12 @@
 		pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
 		return bin_qty - pos_sales_qty, is_stock_item
 	else:
-		is_stock_item = False
+		is_stock_item = True
 		if frappe.db.exists("Product Bundle", item_code):
 			return get_bundle_availability(item_code, warehouse), is_stock_item
 		else:
-			# Is a service item
+			is_stock_item = False
+			# Is a service item or non_stock item
 			return 0, is_stock_item
 
 
@@ -650,7 +651,9 @@
 		available_qty = item_bin_qty - item_pos_reserved_qty
 
 		max_available_bundles = available_qty / item.qty
-		if bundle_bin_qty > max_available_bundles:
+		if bundle_bin_qty > max_available_bundles and frappe.get_value(
+			"Item", item.item_code, "is_stock_item"
+		):
 			bundle_bin_qty = max_available_bundles
 
 	pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
@@ -740,3 +743,7 @@
 		]:
 			payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
 			append_payment(payment_mode[0])
+
+
+def on_doctype_update():
+	frappe.db.add_index("POS Invoice", ["return_against"])
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 0a4f25b..f901257 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1543,6 +1543,37 @@
 		pi.save()
 		self.assertEqual(pi.items[0].conversion_factor, 1000)
 
+	def test_batch_expiry_for_purchase_invoice(self):
+		from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
+		item = self.make_item(
+			"_Test Batch Item For Return Check",
+			{
+				"is_purchase_item": 1,
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"create_new_batch": 1,
+				"batch_number_series": "TBIRC.#####",
+			},
+		)
+
+		pi = make_purchase_invoice(
+			qty=1,
+			item_code=item.name,
+			update_stock=True,
+		)
+
+		pi.load_from_db()
+		batch_no = pi.items[0].batch_no
+		self.assertTrue(batch_no)
+
+		frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1))
+
+		return_pi = make_return_doc(pi.doctype, pi.name)
+		return_pi.save().submit()
+
+		self.assertTrue(return_pi.docstatus == 1)
+
 
 def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
 	gl_entries = frappe.db.sql(
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index e51938b..afd5a59 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -22,9 +22,12 @@
 from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
 from erpnext.accounts.utils import get_account_currency
 from erpnext.assets.doctype.asset.depreciation import (
+	depreciate_asset,
 	get_disposal_account_and_cost_center,
 	get_gl_entries_on_asset_disposal,
 	get_gl_entries_on_asset_regain,
+	reset_depreciation_schedule,
+	reverse_depreciation_entry_made_after_disposal,
 )
 from erpnext.controllers.accounts_controller import validate_account_head
 from erpnext.controllers.selling_controller import SellingController
@@ -1081,23 +1084,25 @@
 
 					if self.is_return:
 						fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
-							asset, item.base_net_amount, item.finance_book
+							asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
 						)
 						asset.db_set("disposal_date", None)
 
 						if asset.calculate_depreciation:
-							self.reverse_depreciation_entry_made_after_disposal(asset)
-							self.reset_depreciation_schedule(asset)
+							posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
+							reverse_depreciation_entry_made_after_disposal(asset, posting_date)
+							reset_depreciation_schedule(asset, self.posting_date)
 
 					else:
+						if asset.calculate_depreciation:
+							depreciate_asset(asset, self.posting_date)
+							asset.reload()
+
 						fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
-							asset, item.base_net_amount, item.finance_book
+							asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
 						)
 						asset.db_set("disposal_date", self.posting_date)
 
-						if asset.calculate_depreciation:
-							self.depreciate_asset(asset)
-
 					for gle in fixed_asset_gl_entries:
 						gle["against"] = self.customer
 						gl_entries.append(self.get_gl_dict(gle, item=item))
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index a43a16c..5512d41 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -230,7 +230,7 @@
 				datasets: [{
 					color: 'green',
 					values: asset_values,
-					formatted: asset_values.map(d => d.toFixed(2))
+					formatted: asset_values.map(d => d?.toFixed(2))
 				}]
 			},
 			type: 'line'
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 7438638..9794170 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -4,11 +4,12 @@
 
 import frappe
 from frappe import _
-from frappe.utils import cint, flt, getdate, today
+from frappe.utils import add_months, cint, flt, getdate, nowdate, today
 
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_checks_for_pl_and_bs_accounts,
 )
+from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
 
 
 def post_depreciation_entries(date=None, commit=True):
@@ -196,6 +197,11 @@
 			_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)
 		)
 
+	date = today()
+
+	depreciate_asset(asset, date)
+	asset.reload()
+
 	depreciation_series = frappe.get_cached_value(
 		"Company", asset.company, "series_for_depreciation_entry"
 	)
@@ -203,7 +209,7 @@
 	je = frappe.new_doc("Journal Entry")
 	je.voucher_type = "Journal Entry"
 	je.naming_series = depreciation_series
-	je.posting_date = today()
+	je.posting_date = date
 	je.company = asset.company
 	je.remark = "Scrap Entry for asset {0}".format(asset_name)
 
@@ -214,7 +220,7 @@
 	je.flags.ignore_permissions = True
 	je.submit()
 
-	frappe.db.set_value("Asset", asset_name, "disposal_date", today())
+	frappe.db.set_value("Asset", asset_name, "disposal_date", date)
 	frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
 	asset.set_status("Scrapped")
 
@@ -225,6 +231,9 @@
 def restore_asset(asset_name):
 	asset = frappe.get_doc("Asset", asset_name)
 
+	reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date)
+	reset_depreciation_schedule(asset, asset.disposal_date)
+
 	je = asset.journal_entry_for_scrap
 
 	asset.db_set("disposal_date", None)
@@ -235,7 +244,94 @@
 	asset.set_status()
 
 
-def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
+def depreciate_asset(asset, date):
+	asset.flags.ignore_validate_update_after_submit = True
+	asset.prepare_depreciation_data(date_of_disposal=date)
+	asset.save()
+
+	make_depreciation_entry(asset.name, date)
+
+
+def reset_depreciation_schedule(asset, date):
+	asset.flags.ignore_validate_update_after_submit = True
+
+	# recreate original depreciation schedule of the asset
+	asset.prepare_depreciation_data(date_of_return=date)
+
+	modify_depreciation_schedule_for_asset_repairs(asset)
+	asset.save()
+
+
+def modify_depreciation_schedule_for_asset_repairs(asset):
+	asset_repairs = frappe.get_all(
+		"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
+	)
+
+	for repair in asset_repairs:
+		if repair.increase_in_asset_life:
+			asset_repair = frappe.get_doc("Asset Repair", repair.name)
+			asset_repair.modify_depreciation_schedule()
+			asset.prepare_depreciation_data()
+
+
+def reverse_depreciation_entry_made_after_disposal(asset, date):
+	row = -1
+	finance_book = asset.get("schedules")[0].get("finance_book")
+	for schedule in asset.get("schedules"):
+		if schedule.finance_book != finance_book:
+			row = 0
+			finance_book = schedule.finance_book
+		else:
+			row += 1
+
+		if schedule.schedule_date == date:
+			if not disposal_was_made_on_original_schedule_date(
+				asset, schedule, row, date
+			) or disposal_happens_in_the_future(date):
+
+				reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
+				reverse_journal_entry.posting_date = nowdate()
+				frappe.flags.is_reverse_depr_entry = True
+				reverse_journal_entry.submit()
+
+				frappe.flags.is_reverse_depr_entry = False
+				asset.flags.ignore_validate_update_after_submit = True
+				schedule.journal_entry = None
+				depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry)
+				asset.finance_books[0].value_after_depreciation += depreciation_amount
+				asset.save()
+
+
+def get_depreciation_amount_in_je(journal_entry):
+	if journal_entry.accounts[0].debit_in_account_currency:
+		return journal_entry.accounts[0].debit_in_account_currency
+	else:
+		return journal_entry.accounts[0].credit_in_account_currency
+
+
+# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
+def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal):
+	for finance_book in asset.get("finance_books"):
+		if schedule.finance_book == finance_book.finance_book:
+			orginal_schedule_date = add_months(
+				finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
+			)
+
+			if orginal_schedule_date == posting_date_of_disposal:
+				return True
+	return False
+
+
+def disposal_happens_in_the_future(posting_date_of_disposal):
+	if posting_date_of_disposal > getdate():
+		return True
+
+	return False
+
+
+def get_gl_entries_on_asset_regain(
+	asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
+):
 	(
 		fixed_asset_account,
 		asset,
@@ -247,28 +343,45 @@
 	) = get_asset_details(asset, finance_book)
 
 	gl_entries = [
-		{
-			"account": fixed_asset_account,
-			"debit_in_account_currency": asset.gross_purchase_amount,
-			"debit": asset.gross_purchase_amount,
-			"cost_center": depreciation_cost_center,
-		},
-		{
-			"account": accumulated_depr_account,
-			"credit_in_account_currency": accumulated_depr_amount,
-			"credit": accumulated_depr_amount,
-			"cost_center": depreciation_cost_center,
-		},
+		asset.get_gl_dict(
+			{
+				"account": fixed_asset_account,
+				"debit_in_account_currency": asset.gross_purchase_amount,
+				"debit": asset.gross_purchase_amount,
+				"cost_center": depreciation_cost_center,
+				"posting_date": getdate(),
+			},
+			item=asset,
+		),
+		asset.get_gl_dict(
+			{
+				"account": accumulated_depr_account,
+				"credit_in_account_currency": accumulated_depr_amount,
+				"credit": accumulated_depr_amount,
+				"cost_center": depreciation_cost_center,
+				"posting_date": getdate(),
+			},
+			item=asset,
+		),
 	]
 
 	profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
 	if profit_amount:
-		get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
+		get_profit_gl_entries(
+			asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
+		)
+
+	if voucher_type and voucher_no:
+		for entry in gl_entries:
+			entry["voucher_type"] = voucher_type
+			entry["voucher_no"] = voucher_no
 
 	return gl_entries
 
 
-def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
+def get_gl_entries_on_asset_disposal(
+	asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
+):
 	(
 		fixed_asset_account,
 		asset,
@@ -280,23 +393,38 @@
 	) = get_asset_details(asset, finance_book)
 
 	gl_entries = [
-		{
-			"account": fixed_asset_account,
-			"credit_in_account_currency": asset.gross_purchase_amount,
-			"credit": asset.gross_purchase_amount,
-			"cost_center": depreciation_cost_center,
-		},
-		{
-			"account": accumulated_depr_account,
-			"debit_in_account_currency": accumulated_depr_amount,
-			"debit": accumulated_depr_amount,
-			"cost_center": depreciation_cost_center,
-		},
+		asset.get_gl_dict(
+			{
+				"account": fixed_asset_account,
+				"credit_in_account_currency": asset.gross_purchase_amount,
+				"credit": asset.gross_purchase_amount,
+				"cost_center": depreciation_cost_center,
+				"posting_date": getdate(),
+			},
+			item=asset,
+		),
+		asset.get_gl_dict(
+			{
+				"account": accumulated_depr_account,
+				"debit_in_account_currency": accumulated_depr_amount,
+				"debit": accumulated_depr_amount,
+				"cost_center": depreciation_cost_center,
+				"posting_date": getdate(),
+			},
+			item=asset,
+		),
 	]
 
 	profit_amount = flt(selling_amount) - flt(value_after_depreciation)
 	if profit_amount:
-		get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
+		get_profit_gl_entries(
+			asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
+		)
+
+	if voucher_type and voucher_no:
+		for entry in gl_entries:
+			entry["voucher_type"] = voucher_type
+			entry["voucher_no"] = voucher_no
 
 	return gl_entries
 
@@ -333,15 +461,21 @@
 	)
 
 
-def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
+def get_profit_gl_entries(
+	asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
+):
 	debit_or_credit = "debit" if profit_amount < 0 else "credit"
 	gl_entries.append(
-		{
-			"account": disposal_account,
-			"cost_center": depreciation_cost_center,
-			debit_or_credit: abs(profit_amount),
-			debit_or_credit + "_in_account_currency": abs(profit_amount),
-		}
+		asset.get_gl_dict(
+			{
+				"account": disposal_account,
+				"cost_center": depreciation_cost_center,
+				debit_or_credit: abs(profit_amount),
+				debit_or_credit + "_in_account_currency": abs(profit_amount),
+				"posting_date": getdate(),
+			},
+			item=asset,
+		)
 	)
 
 
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 6838885..ccd19ad 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -4,7 +4,16 @@
 import unittest
 
 import frappe
-from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
+from frappe.utils import (
+	add_days,
+	add_months,
+	cstr,
+	flt,
+	get_first_day,
+	get_last_day,
+	getdate,
+	nowdate,
+)
 
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.assets.doctype.asset.asset import (
@@ -182,28 +191,48 @@
 		self.assertEqual(doc.items[0].is_fixed_asset, 1)
 
 	def test_scrap_asset(self):
+		date = nowdate()
+		purchase_date = add_months(get_first_day(date), -2)
+
 		asset = create_asset(
 			calculate_depreciation=1,
-			available_for_use_date="2020-01-01",
-			purchase_date="2020-01-01",
+			available_for_use_date=purchase_date,
+			purchase_date=purchase_date,
 			expected_value_after_useful_life=10000,
 			total_number_of_depreciations=10,
 			frequency_of_depreciation=1,
 			submit=1,
 		)
 
-		post_depreciation_entries(date=add_months("2020-01-01", 4))
+		post_depreciation_entries(date=add_months(purchase_date, 2))
+		asset.load_from_db()
+
+		accumulated_depr_amount = flt(
+			asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
+			asset.precision("gross_purchase_amount"),
+		)
+		self.assertEquals(accumulated_depr_amount, 18000.0)
 
 		scrap_asset(asset.name)
-
 		asset.load_from_db()
+
+		accumulated_depr_amount = flt(
+			asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
+			asset.precision("gross_purchase_amount"),
+		)
+		pro_rata_amount, _, _ = asset.get_pro_rata_amt(
+			asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
+		)
+		pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
+		self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
+
 		self.assertEqual(asset.status, "Scrapped")
 		self.assertTrue(asset.journal_entry_for_scrap)
 
 		expected_gle = (
-			("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
+			("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
 			("_Test Fixed Asset - _TC", 0.0, 100000.0),
-			("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0),
+			("_Test Gain/Loss on Asset Disposal - _TC", 82000.0 - pro_rata_amount, 0.0),
 		)
 
 		gle = frappe.db.sql(
@@ -220,19 +249,27 @@
 		self.assertFalse(asset.journal_entry_for_scrap)
 		self.assertEqual(asset.status, "Partially Depreciated")
 
+		accumulated_depr_amount = flt(
+			asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
+			asset.precision("gross_purchase_amount"),
+		)
+		self.assertEquals(accumulated_depr_amount, 18000.0)
+
 	def test_gle_made_by_asset_sale(self):
+		date = nowdate()
+		purchase_date = add_months(get_first_day(date), -2)
+
 		asset = create_asset(
 			calculate_depreciation=1,
-			available_for_use_date="2020-06-06",
-			purchase_date="2020-01-01",
+			available_for_use_date=purchase_date,
+			purchase_date=purchase_date,
 			expected_value_after_useful_life=10000,
-			total_number_of_depreciations=3,
-			frequency_of_depreciation=10,
-			depreciation_start_date="2020-12-31",
+			total_number_of_depreciations=10,
+			frequency_of_depreciation=1,
 			submit=1,
 		)
 
-		post_depreciation_entries(date="2021-01-01")
+		post_depreciation_entries(date=add_months(purchase_date, 2))
 
 		si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
 		si.customer = "_Test Customer"
@@ -243,10 +280,15 @@
 
 		self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
 
+		pro_rata_amount, _, _ = asset.get_pro_rata_amt(
+			asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
+		)
+		pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
+
 		expected_gle = (
-			("_Test Accumulated Depreciations - _TC", 20490.2, 0.0),
+			("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
 			("_Test Fixed Asset - _TC", 0.0, 100000.0),
-			("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0),
+			("_Test Gain/Loss on Asset Disposal - _TC", 57000.0 - pro_rata_amount, 0.0),
 			("Debtors - _TC", 25000.0, 0.0),
 		)
 
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index 2e6f0ad..08355f0 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -12,8 +12,11 @@
 
 import erpnext
 from erpnext.assets.doctype.asset.depreciation import (
+	depreciate_asset,
 	get_gl_entries_on_asset_disposal,
 	get_value_after_depreciation_on_disposal_date,
+	reset_depreciation_schedule,
+	reverse_depreciation_entry_made_after_disposal,
 )
 from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
 from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
@@ -424,11 +427,15 @@
 			asset = self.get_asset(item)
 
 			if asset.calculate_depreciation:
-				self.depreciate_asset(asset)
+				depreciate_asset(asset, self.posting_date)
 				asset.reload()
 
 			fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
-				asset, item.asset_value, item.get("finance_book") or self.get("finance_book")
+				asset,
+				item.asset_value,
+				item.get("finance_book") or self.get("finance_book"),
+				self.get("doctype"),
+				self.get("name"),
 			)
 
 			asset.db_set("disposal_date", self.posting_date)
@@ -516,8 +523,8 @@
 				self.set_consumed_asset_status(asset)
 
 				if asset.calculate_depreciation:
-					self.reverse_depreciation_entry_made_after_disposal(asset)
-					self.reset_depreciation_schedule(asset)
+					reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
+					reset_depreciation_schedule(asset, self.posting_date)
 
 	def get_asset(self, item):
 		asset = frappe.get_doc("Asset", item.asset)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 8686cb5..22291a3 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -38,7 +38,6 @@
 	validate_party_frozen_disabled,
 )
 from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year
-from erpnext.assets.doctype.asset.depreciation import make_depreciation_entry
 from erpnext.buying.utils import update_last_purchase_rate
 from erpnext.controllers.print_settings import (
 	set_print_templates_for_item_table,
@@ -1891,88 +1890,6 @@
 				_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
 			)
 
-	def depreciate_asset(self, asset):
-		asset.flags.ignore_validate_update_after_submit = True
-		asset.prepare_depreciation_data(date_of_disposal=self.posting_date)
-		asset.save()
-
-		make_depreciation_entry(asset.name, self.posting_date)
-
-	def reset_depreciation_schedule(self, asset):
-		asset.flags.ignore_validate_update_after_submit = True
-
-		# recreate original depreciation schedule of the asset
-		asset.prepare_depreciation_data(date_of_return=self.posting_date)
-
-		self.modify_depreciation_schedule_for_asset_repairs(asset)
-		asset.save()
-
-	def modify_depreciation_schedule_for_asset_repairs(self, asset):
-		asset_repairs = frappe.get_all(
-			"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
-		)
-
-		for repair in asset_repairs:
-			if repair.increase_in_asset_life:
-				asset_repair = frappe.get_doc("Asset Repair", repair.name)
-				asset_repair.modify_depreciation_schedule()
-				asset.prepare_depreciation_data()
-
-	def reverse_depreciation_entry_made_after_disposal(self, asset):
-		from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
-
-		posting_date_of_original_disposal = self.get_posting_date_of_disposal_entry()
-
-		row = -1
-		finance_book = asset.get("schedules")[0].get("finance_book")
-		for schedule in asset.get("schedules"):
-			if schedule.finance_book != finance_book:
-				row = 0
-				finance_book = schedule.finance_book
-			else:
-				row += 1
-
-			if schedule.schedule_date == posting_date_of_original_disposal:
-				if not self.disposal_was_made_on_original_schedule_date(
-					asset, schedule, row, posting_date_of_original_disposal
-				) or self.disposal_happens_in_the_future(posting_date_of_original_disposal):
-
-					reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
-					reverse_journal_entry.posting_date = nowdate()
-					frappe.flags.is_reverse_depr_entry = True
-					reverse_journal_entry.submit()
-
-					frappe.flags.is_reverse_depr_entry = False
-					asset.flags.ignore_validate_update_after_submit = True
-					schedule.journal_entry = None
-					asset.save()
-
-	def get_posting_date_of_disposal_entry(self):
-		if self.doctype == "Sales Invoice" and self.return_against:
-			return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
-		else:
-			return self.posting_date
-
-	# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
-	def disposal_was_made_on_original_schedule_date(
-		self, asset, schedule, row, posting_date_of_disposal
-	):
-		for finance_book in asset.get("finance_books"):
-			if schedule.finance_book == finance_book.finance_book:
-				orginal_schedule_date = add_months(
-					finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
-				)
-
-				if orginal_schedule_date == posting_date_of_disposal:
-					return True
-		return False
-
-	def disposal_happens_in_the_future(self, posting_date_of_disposal):
-		if posting_date_of_disposal > getdate():
-			return True
-
-		return False
-
 
 @frappe.whitelist()
 def get_tax_rate(account_head):
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index a08feb4..6ef77f3 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -391,12 +391,12 @@
 		"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts",
 	],
 	"hourly": [
-		"erpnext.accounts.doctype.subscription.subscription.process_all",
 		"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
 		"erpnext.projects.doctype.project.project.hourly_reminder",
 		"erpnext.projects.doctype.project.project.collect_project_status",
 	],
 	"hourly_long": [
+		"erpnext.accounts.doctype.subscription.subscription.process_all",
 		"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
 		"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
 	],
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 20f1503..f3640b9 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -557,37 +557,52 @@
 
 			if(!frm.doc.skip_transfer){
 				// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
-				if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))
-				&& frm.doc.status != 'Stopped') {
-					frm.has_finish_btn = true;
+				if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') {
+					if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) {
+						frm.has_finish_btn = true;
 
-					if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
-						// Only show "Material Consumption" when required_qty > consumed_qty
-						var counter = 0;
-						var tbl = frm.doc.required_items || [];
-						var tbl_lenght = tbl.length;
-						for (var i = 0, len = tbl_lenght; i < len; i++) {
-							let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
-							if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
-								counter += 1;
+						if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
+							// Only show "Material Consumption" when required_qty > consumed_qty
+							var counter = 0;
+							var tbl = frm.doc.required_items || [];
+							var tbl_lenght = tbl.length;
+							for (var i = 0, len = tbl_lenght; i < len; i++) {
+								let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
+								if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
+									counter += 1;
+								}
+							}
+							if (counter > 0) {
+								var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
+									const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
+									erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
+								});
+								consumption_btn.addClass('btn-primary');
 							}
 						}
-						if (counter > 0) {
-							var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
-								const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
-								erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
-							});
-							consumption_btn.addClass('btn-primary');
+
+						var finish_btn = frm.add_custom_button(__('Finish'), function() {
+							erpnext.work_order.make_se(frm, 'Manufacture');
+						});
+
+						if(doc.material_transferred_for_manufacturing>=doc.qty) {
+							// all materials transferred for manufacturing, make this primary
+							finish_btn.addClass('btn-primary');
 						}
-					}
+					} else {
+						frappe.db.get_doc("Manufacturing Settings").then((doc) => {
+							let allowance_percentage = doc.overproduction_percentage_for_work_order;
 
-					var finish_btn = frm.add_custom_button(__('Finish'), function() {
-						erpnext.work_order.make_se(frm, 'Manufacture');
-					});
+							if (allowance_percentage > 0) {
+								let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
 
-					if(doc.material_transferred_for_manufacturing>=doc.qty) {
-						// all materials transferred for manufacturing, make this primary
-						finish_btn.addClass('btn-primary');
+								if ((flt(doc.produced_qty) < allowed_qty)) {
+									frm.add_custom_button(__('Finish'), function() {
+										erpnext.work_order.make_se(frm, 'Manufacture');
+									});
+								}
+							}
+						});
 					}
 				}
 			} else {
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index da7576e..24375d8 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -660,7 +660,7 @@
 			} else {
 				return;
 			}
-		} else if (available_qty < qty_needed) {
+		} else if (is_stock_item && available_qty < qty_needed) {
 			frappe.throw({
 				message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
 				indicator: 'orange'
@@ -694,7 +694,7 @@
 			callback(res) {
 				if (!me.item_stock_map[item_code])
 					me.item_stock_map[item_code] = {};
-				me.item_stock_map[item_code][warehouse] = res.message[0];
+				me.item_stock_map[item_code][warehouse] = res.message;
 			}
 		});
 	}
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index b75ffb2..f9b5bb2 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -242,13 +242,14 @@
 				if (this.value) {
 					me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => {
 						me.item_stock_map = me.events.get_item_stock_map();
-						const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value];
+						const available_qty = me.item_stock_map[me.item_row.item_code][this.value][0];
+						const is_stock_item = Boolean(me.item_stock_map[me.item_row.item_code][this.value][1]);
 						if (available_qty === undefined) {
 							me.events.get_available_stock(me.item_row.item_code, this.value).then(() => {
 								// item stock map is updated now reset warehouse
 								me.warehouse_control.set_value(this.value);
 							})
-						} else if (available_qty === 0) {
+						} else if (available_qty === 0 && is_stock_item) {
 							me.warehouse_control.set_value('');
 							const bold_item_code = me.item_row.item_code.bold();
 							const bold_warehouse = this.value.bold();
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index b77c3a5..6269724 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -1241,6 +1241,37 @@
 
 		self.assertEqual(query[0].value, 0)
 
+	def test_batch_expiry_for_purchase_receipt(self):
+		from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
+		item = make_item(
+			"_Test Batch Item For Return Check",
+			{
+				"is_purchase_item": 1,
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"create_new_batch": 1,
+				"batch_number_series": "TBIRC.#####",
+			},
+		)
+
+		pi = make_purchase_receipt(
+			qty=1,
+			item_code=item.name,
+			update_stock=True,
+		)
+
+		pi.load_from_db()
+		batch_no = pi.items[0].batch_no
+		self.assertTrue(batch_no)
+
+		frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1))
+
+		return_pi = make_return_doc(pi.doctype, pi.name)
+		return_pi.save().submit()
+
+		self.assertTrue(return_pi.docstatus == 1)
+
 
 def prepare_data_for_internal_transfer():
 	from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 738ac33..8bcd772 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1073,8 +1073,8 @@
 			# No work order could mean independent Manufacture entry, if so skip validation
 			if self.work_order and self.fg_completed_qty > allowed_qty:
 				frappe.throw(
-					_("For quantity {0} should not be greater than work order quantity {1}").format(
-						flt(self.fg_completed_qty), wo_qty
+					_("For quantity {0} should not be greater than allowed quantity {1}").format(
+						flt(self.fg_completed_qty), allowed_qty
 					)
 				)
 
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 329cd7d..f7f8cbe 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -153,6 +153,9 @@
 
 	def validate_batch(self):
 		if self.batch_no and self.voucher_type != "Stock Entry":
+			if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and self.actual_qty < 0:
+				return
+
 			expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date")
 			if expiry_date:
 				if getdate(self.posting_date) > getdate(expiry_date):