Merge branch 'develop' into fix_flaky_test_in_payment_terms_report
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
index 75f8f06..9e67c4c 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
@@ -29,6 +29,7 @@
 					"root_type",
 					"is_group",
 					"tax_rate",
+					"account_currency",
 				]:
 
 					account_number = cstr(child.get("account_number")).strip()
@@ -95,7 +96,17 @@
 		is_group = child.get("is_group")
 	elif len(
 		set(child.keys())
-		- set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])
+		- set(
+			[
+				"account_name",
+				"account_type",
+				"root_type",
+				"is_group",
+				"tax_rate",
+				"account_number",
+				"account_currency",
+			]
+		)
 	):
 		is_group = 1
 	else:
@@ -185,6 +196,7 @@
 			"root_type",
 			"tax_rate",
 			"account_number",
+			"account_currency",
 		],
 		order_by="lft, rgt",
 	)
@@ -267,6 +279,7 @@
 				"root_type",
 				"is_group",
 				"tax_rate",
+				"account_currency",
 			]:
 				continue
 
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index 220b747..cb7da17 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -36,7 +36,7 @@
 
 	no_of_columns = max([len(d) for d in data])
 
-	if no_of_columns > 7:
+	if no_of_columns > 8:
 		frappe.throw(
 			_("More columns found than expected. Please compare the uploaded file with standard template"),
 			title=(_("Wrong Template")),
@@ -233,6 +233,7 @@
 			is_group,
 			account_type,
 			root_type,
+			account_currency,
 		) = i
 
 		if not account_name:
@@ -253,6 +254,8 @@
 			charts_map[account_name]["account_type"] = account_type
 		if root_type:
 			charts_map[account_name]["root_type"] = root_type
+		if account_currency:
+			charts_map[account_name]["account_currency"] = account_currency
 		path = return_parent(data, account_name)[::-1]
 		paths.append(path)  # List of path is created
 		line_no += 1
@@ -315,6 +318,7 @@
 		"Is Group",
 		"Account Type",
 		"Root Type",
+		"Account Currency",
 	]
 	writer = UnicodeWriter()
 	writer.writerow(fields)
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index d67d59b..a4f6a74 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -211,8 +211,7 @@
 			# Handle Accounts with '0' balance in Account/Base Currency
 			for d in [x for x in account_details if x.zero_balance]:
 
-				# TODO: Set new balance in Base/Account currency
-				if d.balance > 0:
+				if d.balance != 0:
 					current_exchange_rate = new_exchange_rate = 0
 
 					new_balance_in_account_currency = 0  # this will be '0'
@@ -399,6 +398,9 @@
 
 		journal_entry_accounts = []
 		for d in accounts:
+			if not flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")):
+				continue
+
 			dr_or_cr = (
 				"debit_in_account_currency"
 				if d.get("balance_in_account_currency") > 0
@@ -448,7 +450,13 @@
 				}
 			)
 
-		journal_entry_accounts.append(
+		journal_entry.set("accounts", journal_entry_accounts)
+		journal_entry.set_amounts_in_company_currency()
+		journal_entry.set_total_debit_credit()
+
+		self.gain_loss_unbooked += journal_entry.difference - self.gain_loss_unbooked
+		journal_entry.append(
+			"accounts",
 			{
 				"account": unrealized_exchange_gain_loss_account,
 				"balance": get_balance_on(unrealized_exchange_gain_loss_account),
@@ -460,10 +468,9 @@
 				"exchange_rate": 1,
 				"reference_type": "Exchange Rate Revaluation",
 				"reference_name": self.name,
-			}
+			},
 		)
 
-		journal_entry.set("accounts", journal_entry_accounts)
 		journal_entry.set_amounts_in_company_currency()
 		journal_entry.set_total_debit_credit()
 		journal_entry.save()
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 498fc7c..80e7222 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -137,7 +137,8 @@
    "fieldname": "finance_book",
    "fieldtype": "Link",
    "label": "Finance Book",
-   "options": "Finance Book"
+   "options": "Finance Book",
+   "read_only": 1
   },
   {
    "fieldname": "2_add_edit_gl_entries",
@@ -538,7 +539,7 @@
  "idx": 176,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-17 12:53:53.280620",
+ "modified": "2023-03-01 14:58:59.286591",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 2f43914..7005c17 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -495,26 +495,22 @@
 	"""get amount based on doctype"""
 	dt = ref_doc.doctype
 	if dt in ["Sales Order", "Purchase Order"]:
-		grand_total = flt(ref_doc.rounded_total) - flt(ref_doc.advance_paid)
-
+		grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
 	elif dt in ["Sales Invoice", "Purchase Invoice"]:
 		if ref_doc.party_account_currency == ref_doc.currency:
 			grand_total = flt(ref_doc.outstanding_amount)
 		else:
 			grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
-
 	elif dt == "POS Invoice":
 		for pay in ref_doc.payments:
 			if pay.type == "Phone" and pay.account == payment_account:
 				grand_total = pay.amount
 				break
-
 	elif dt == "Fees":
 		grand_total = ref_doc.outstanding_amount
 
 	if grand_total > 0:
 		return grand_total
-
 	else:
 		frappe.throw(_("Payment Entry is already created"))
 
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index 477c726..4279aa4 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -45,7 +45,10 @@
 				frappe.get_doc(method).insert(ignore_permissions=True)
 
 	def test_payment_request_linkings(self):
-		so_inr = make_sales_order(currency="INR")
+		so_inr = make_sales_order(currency="INR", do_not_save=True)
+		so_inr.disable_rounded_total = 1
+		so_inr.save()
+
 		pr = make_payment_request(
 			dt="Sales Order",
 			dn=so_inr.name,
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index a1239d6..b40649b 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -161,7 +161,7 @@
 
 		bold_item_name = frappe.bold(item.item_name)
 		bold_extra_batch_qty_needed = frappe.bold(
-			abs(available_batch_qty - reserved_batch_qty - item.qty)
+			abs(available_batch_qty - reserved_batch_qty - item.stock_qty)
 		)
 		bold_invalid_batch_no = frappe.bold(item.batch_no)
 
@@ -172,7 +172,7 @@
 				).format(item.idx, bold_invalid_batch_no, bold_item_name),
 				title=_("Item Unavailable"),
 			)
-		elif (available_batch_qty - reserved_batch_qty - item.qty) < 0:
+		elif (available_batch_qty - reserved_batch_qty - item.stock_qty) < 0:
 			frappe.throw(
 				_(
 					"Row #{}: Batch No. {} of item {} has less than required stock available, {} more required"
@@ -246,7 +246,7 @@
 						),
 						title=_("Item Unavailable"),
 					)
-				elif is_stock_item and flt(available_stock) < flt(d.qty):
+				elif is_stock_item and flt(available_stock) < flt(d.stock_qty):
 					frappe.throw(
 						_(
 							"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
@@ -651,7 +651,7 @@
 		item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
 		available_qty = item_bin_qty - item_pos_reserved_qty
 
-		max_available_bundles = available_qty / item.qty
+		max_available_bundles = available_qty / item.stock_qty
 		if bundle_bin_qty > max_available_bundles and frappe.get_value(
 			"Item", item.item_code, "is_stock_item"
 		):
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 21addab..b79af71 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1485,11 +1485,17 @@
 		if po_details:
 			updated_pr += update_billed_amount_based_on_po(po_details, update_modified)
 
+		adjust_incoming_rate = frappe.db.get_single_value(
+			"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
+		)
+
 		for pr in set(updated_pr):
 			from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
 
 			pr_doc = frappe.get_doc("Purchase Receipt", pr)
-			update_billing_percentage(pr_doc, update_modified=update_modified)
+			update_billing_percentage(
+				pr_doc, update_modified=update_modified, adjust_incoming_rate=adjust_incoming_rate
+			)
 
 	def get_pr_details_billed_amt(self):
 		# Get billed amount based on purchase receipt item reference (pr_detail) in purchase invoice
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index f901257..a6d7df6 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1523,6 +1523,94 @@
 		company.enable_provisional_accounting_for_non_stock_items = 0
 		company.save()
 
+	def test_adjust_incoming_rate(self):
+		frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
+
+		frappe.db.set_single_value(
+			"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1
+		)
+
+		# Increase the cost of the item
+
+		pr = make_purchase_receipt(qty=1, rate=100)
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 100)
+
+		pi = create_purchase_invoice_from_receipt(pr.name)
+		for row in pi.items:
+			row.rate = 150
+
+		pi.save()
+		pi.submit()
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 150)
+
+		# Reduce the cost of the item
+
+		pr = make_purchase_receipt(qty=1, rate=100)
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 100)
+
+		pi = create_purchase_invoice_from_receipt(pr.name)
+		for row in pi.items:
+			row.rate = 50
+
+		pi.save()
+		pi.submit()
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 50)
+
+		frappe.db.set_single_value(
+			"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0
+		)
+
+		# Don't adjust incoming rate
+
+		pr = make_purchase_receipt(qty=1, rate=100)
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 100)
+
+		pi = create_purchase_invoice_from_receipt(pr.name)
+		for row in pi.items:
+			row.rate = 50
+
+		pi.save()
+		pi.submit()
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 100)
+
+		frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
+
 	def test_item_less_defaults(self):
 
 		pi = frappe.new_doc("Purchase Invoice")
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html
index 475be92..2d5ca49 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.html
+++ b/erpnext/accounts/report/general_ledger/general_ledger.html
@@ -38,8 +38,11 @@
 			{% if(data[i].posting_date) { %}
 				<td>{%= frappe.datetime.str_to_user(data[i].posting_date) %}</td>
 				<td>{%= data[i].voucher_type %}
-					<br>{%= data[i].voucher_no %}</td>
-				<td>
+					<br>{%= data[i].voucher_no %}
+				</td>
+				{% var longest_word = cstr(data[i].remarks).split(" ").reduce((longest, word) => word.length > longest.length ? word : longest, ""); %}
+				<td {% if longest_word.length > 45 %} class="overflow-wrap-anywhere" {% endif %}>
+					<span>
 					{% if(!(filters.party || filters.account)) { %}
 						{%= data[i].party || data[i].account %}
 						<br>
@@ -49,11 +52,14 @@
 					{% if(data[i].bill_no) { %}
 						<br>{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}
 					{% } %}
-					</td>
-					<td style="text-align: right">
-						{%= format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %}</td>
-					<td style="text-align: right">
-						{%= format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %}</td>
+					</span>
+				</td>
+				<td style="text-align: right">
+					{%= format_currency(data[i].debit, filters.presentation_currency) %}
+				</td>
+				<td style="text-align: right">
+					{%= format_currency(data[i].credit, filters.presentation_currency) %}
+				</td>
 			{% } else { %}
 				<td></td>
 				<td></td>
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
index c28b2b3..3d2dff1 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
@@ -43,9 +43,9 @@
 	if(frm.doc.depreciation_method != "Manual") return;
 
 	var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
-	$.each(frm.doc.schedules || [], function(i, row) {
+
+	$.each(frm.doc.depreciation_schedule || [], function(i, row) {
 		accumulated_depreciation  += flt(row.depreciation_amount);
-		frappe.model.set_value(row.doctype, row.name,
-			"accumulated_depreciation_amount", accumulated_depreciation);
+		frappe.model.set_value(row.doctype, row.name, "accumulated_depreciation_amount", accumulated_depreciation);
 	})
 };
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
index 898c482..d38508d 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
@@ -10,7 +10,9 @@
   "asset",
   "naming_series",
   "column_break_2",
+  "gross_purchase_amount",
   "opening_accumulated_depreciation",
+  "number_of_depreciations_booked",
   "finance_book",
   "finance_book_id",
   "depreciation_details_section",
@@ -148,18 +150,36 @@
    "read_only": 1
   },
   {
-   "depends_on": "opening_accumulated_depreciation",
    "fieldname": "opening_accumulated_depreciation",
    "fieldtype": "Currency",
+   "hidden": 1,
    "label": "Opening Accumulated Depreciation",
    "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "gross_purchase_amount",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Gross Purchase Amount",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "number_of_depreciations_booked",
+   "fieldtype": "Int",
+   "hidden": 1,
+   "label": "Number of Depreciations Booked",
+   "print_hide": 1,
    "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-16 21:08:21.421260",
+ "modified": "2023-02-26 16:37:23.734806",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset Depreciation Schedule",
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 6f02662..b75fbcb 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -4,7 +4,15 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import add_days, add_months, cint, flt, get_last_day, is_last_day_of_the_month
+from frappe.utils import (
+	add_days,
+	add_months,
+	cint,
+	flt,
+	get_last_day,
+	getdate,
+	is_last_day_of_the_month,
+)
 
 
 class AssetDepreciationSchedule(Document):
@@ -83,15 +91,58 @@
 		date_of_return=None,
 		update_asset_finance_book_row=True,
 	):
+		have_asset_details_been_modified = self.have_asset_details_been_modified(asset_doc)
+		not_manual_depr_or_have_manual_depr_details_been_modified = (
+			self.not_manual_depr_or_have_manual_depr_details_been_modified(row)
+		)
+
 		self.set_draft_asset_depr_schedule_details(asset_doc, row)
-		self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
-		self.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
+
+		if self.should_prepare_depreciation_schedule(
+			have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified
+		):
+			self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
+			self.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
+
+	def have_asset_details_been_modified(self, asset_doc):
+		return (
+			asset_doc.gross_purchase_amount != self.gross_purchase_amount
+			or asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation
+			or asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked
+		)
+
+	def not_manual_depr_or_have_manual_depr_details_been_modified(self, row):
+		return (
+			self.depreciation_method != "Manual"
+			or row.total_number_of_depreciations != self.total_number_of_depreciations
+			or row.frequency_of_depreciation != self.frequency_of_depreciation
+			or getdate(row.depreciation_start_date) != self.get("depreciation_schedule")[0].schedule_date
+			or row.expected_value_after_useful_life != self.expected_value_after_useful_life
+		)
+
+	def should_prepare_depreciation_schedule(
+		self, have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified
+	):
+		if not self.get("depreciation_schedule"):
+			return True
+
+		old_asset_depr_schedule_doc = self.get_doc_before_save()
+
+		if self.docstatus != 0 and not old_asset_depr_schedule_doc:
+			return True
+
+		if have_asset_details_been_modified or not_manual_depr_or_have_manual_depr_details_been_modified:
+			return True
+
+		return False
 
 	def set_draft_asset_depr_schedule_details(self, asset_doc, row):
 		self.asset = asset_doc.name
 		self.finance_book = row.finance_book
 		self.finance_book_id = row.idx
 		self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation
+		self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked
+		self.gross_purchase_amount = asset_doc.gross_purchase_amount
 		self.depreciation_method = row.depreciation_method
 		self.total_number_of_depreciations = row.total_number_of_depreciations
 		self.frequency_of_depreciation = row.frequency_of_depreciation
@@ -102,7 +153,7 @@
 	def make_depr_schedule(
 		self, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True
 	):
-		if row.depreciation_method != "Manual" and not self.get("depreciation_schedule"):
+		if not self.get("depreciation_schedule"):
 			self.depreciation_schedule = []
 
 		if not asset_doc.available_for_use_date:
@@ -293,7 +344,9 @@
 		ignore_booked_entry=False,
 	):
 		straight_line_idx = [
-			d.idx for d in self.get("depreciation_schedule") if d.depreciation_method == "Straight Line"
+			d.idx
+			for d in self.get("depreciation_schedule")
+			if d.depreciation_method == "Straight Line" or d.depreciation_method == "Manual"
 		]
 
 		accumulated_depreciation = flt(self.opening_accumulated_depreciation)
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 652dcf0..95857e4 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -18,6 +18,7 @@
   "pr_required",
   "column_break_12",
   "maintain_same_rate",
+  "set_landed_cost_based_on_purchase_invoice_rate",
   "allow_multiple_items",
   "bill_for_rejected_quantity_in_purchase_invoice",
   "disable_last_purchase_rate",
@@ -147,6 +148,14 @@
    "fieldname": "show_pay_button",
    "fieldtype": "Check",
    "label": "Show Pay Button in Purchase Order Portal"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: !doc.maintain_same_rate",
+   "description": "Users can enable the checkbox If they want to adjust the incoming rate (set using purchase receipt) based on the purchase invoice rate.",
+   "fieldname": "set_landed_cost_based_on_purchase_invoice_rate",
+   "fieldtype": "Check",
+   "label": "Set Landed Cost Based on Purchase Invoice Rate"
   }
  ],
  "icon": "fa fa-cog",
@@ -154,7 +163,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2023-02-15 14:42:10.200679",
+ "modified": "2023-02-28 15:41:32.686805",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py
index be1ebde..4680a88 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.py
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.py
@@ -21,3 +21,10 @@
 			self.get("supp_master_name") == "Naming Series",
 			hide_name_field=False,
 		)
+
+	def before_save(self):
+		self.check_maintain_same_rate()
+
+	def check_maintain_same_rate(self):
+		if self.maintain_same_rate:
+			self.set_landed_cost_based_on_purchase_invoice_rate = 0
diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js
index 6304a09..9db769d 100644
--- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js
+++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js
@@ -22,14 +22,14 @@
 			fieldname:"from_date",
 			label: __("From Date"),
 			fieldtype: "Date",
-			default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
+			default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
 			reqd: 1
 		},
 		{
 			fieldname:"to_date",
 			label: __("To Date"),
 			fieldtype: "Date",
-			default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
+			default: frappe.datetime.get_today(),
 			reqd: 1
 		},
 	]
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js
index b6739fe..7e5338f 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js
@@ -22,14 +22,14 @@
 			fieldname:"from_date",
 			label: __("From Date"),
 			fieldtype: "Date",
-			default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
+			default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
 			reqd: 1
 		},
 		{
 			fieldname:"to_date",
 			label: __("To Date"),
 			fieldtype: "Date",
-			default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
+			default: frappe.datetime.get_today(),
 			reqd: 1
 		},
 	]
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 4f7d9ad..e15b612 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -265,7 +265,10 @@
 					) / qty_in_stock_uom
 				else:
 					item.valuation_rate = (
-						item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
+						item.base_net_amount
+						+ item.item_tax_amount
+						+ flt(item.landed_cost_voucher_amount)
+						+ flt(item.get("rate_difference_with_purchase_invoice"))
 					) / qty_in_stock_uom
 			else:
 				item.valuation_rate = 0.0
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index fc6793a..15c270e 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -131,7 +131,7 @@
 					)
 
 				elif ref.serial_no:
-					if not d.serial_no:
+					if d.qty and not d.serial_no:
 						frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
 					else:
 						serial_nos = get_serial_nos(d.serial_no)
@@ -252,7 +252,6 @@
 			child.parent = par.name and par.docstatus = 1
 			and par.is_return = 1 and par.return_against = %s
 		group by item_code
-		for update
 	""".format(
 			column, doc.doctype, doc.doctype
 		),
@@ -401,6 +400,16 @@
 			if serial_nos:
 				target_doc.serial_no = "\n".join(serial_nos)
 
+		if source_doc.get("rejected_serial_no"):
+			returned_serial_nos = get_returned_serial_nos(
+				source_doc, source_parent, serial_no_field="rejected_serial_no"
+			)
+			rejected_serial_nos = list(
+				set(get_serial_nos(source_doc.rejected_serial_no)) - set(returned_serial_nos)
+			)
+			if rejected_serial_nos:
+				target_doc.rejected_serial_no = "\n".join(rejected_serial_nos)
+
 		if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
 			returned_qty_map = get_returned_qty_map_for_row(
 				source_parent.name, source_parent.supplier, source_doc.name, doctype
@@ -611,7 +620,7 @@
 	return filters
 
 
-def get_returned_serial_nos(child_doc, parent_doc):
+def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
 	return_ref_field = frappe.scrub(child_doc.doctype)
@@ -620,7 +629,7 @@
 
 	serial_nos = []
 
-	fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
+	fields = [f"`{'tab' + child_doc.doctype}`.`{serial_no_field}`"]
 
 	filters = [
 		[parent_doc.doctype, "return_against", "=", parent_doc.name],
@@ -630,6 +639,6 @@
 	]
 
 	for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
-		serial_nos.extend(get_serial_nos(row.serial_no))
+		serial_nos.extend(get_serial_nos(row.get(serial_no_field)))
 
 	return serial_nos
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 8b4d28b..fc16a91 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -136,7 +136,7 @@
 			self.in_words = money_in_words(amount, self.currency)
 
 	def calculate_commission(self):
-		if not self.meta.get_field("commission_rate"):
+		if not self.meta.get_field("commission_rate") or self.docstatus.is_submitted():
 			return
 
 		self.round_floats_in(self, ("amount_eligible_for_commission", "commission_rate"))
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 8c403aa..1edd7bf 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -24,11 +24,19 @@
 	def __init__(self, doc: Document):
 		self.doc = doc
 		frappe.flags.round_off_applicable_accounts = []
+
+		self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
+
 		get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
 		self.calculate()
 
+	def filter_rows(self):
+		"""Exclude rows, that do not fulfill the filter criteria, from totals computation."""
+		items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items")))
+		return items
+
 	def calculate(self):
-		if not len(self.doc.get("items")):
+		if not len(self._items):
 			return
 
 		self.discount_amount_applied = False
@@ -70,7 +78,7 @@
 		if hasattr(self.doc, "tax_withholding_net_total"):
 			sum_net_amount = 0
 			sum_base_net_amount = 0
-			for item in self.doc.get("items"):
+			for item in self._items:
 				if hasattr(item, "apply_tds") and item.apply_tds:
 					sum_net_amount += item.net_amount
 					sum_base_net_amount += item.base_net_amount
@@ -79,7 +87,7 @@
 			self.doc.base_tax_withholding_net_total = sum_base_net_amount
 
 	def validate_item_tax_template(self):
-		for item in self.doc.get("items"):
+		for item in self._items:
 			if item.item_code and item.get("item_tax_template"):
 				item_doc = frappe.get_cached_doc("Item", item.item_code)
 				args = {
@@ -137,7 +145,7 @@
 			return
 
 		if not self.discount_amount_applied:
-			for item in self.doc.get("items"):
+			for item in self._items:
 				self.doc.round_floats_in(item)
 
 				if item.discount_percentage == 100:
@@ -236,7 +244,7 @@
 		if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")):
 			return
 
-		for item in self.doc.get("items"):
+		for item in self._items:
 			item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
 			cumulated_tax_fraction = 0
 			total_inclusive_tax_amount_per_qty = 0
@@ -317,7 +325,7 @@
 			self.doc.total
 		) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
 
-		for item in self.doc.get("items"):
+		for item in self._items:
 			self.doc.total += item.amount
 			self.doc.total_qty += item.qty
 			self.doc.base_total += item.base_amount
@@ -354,7 +362,7 @@
 			]
 		)
 
-		for n, item in enumerate(self.doc.get("items")):
+		for n, item in enumerate(self._items):
 			item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
 			for i, tax in enumerate(self.doc.get("taxes")):
 				# tax_amount represents the amount of tax for the current step
@@ -363,7 +371,7 @@
 				# Adjust divisional loss to the last item
 				if tax.charge_type == "Actual":
 					actual_tax_dict[tax.idx] -= current_tax_amount
-					if n == len(self.doc.get("items")) - 1:
+					if n == len(self._items) - 1:
 						current_tax_amount += actual_tax_dict[tax.idx]
 
 				# accumulate tax amount into tax.tax_amount
@@ -391,7 +399,7 @@
 					)
 
 				# set precision in the last item iteration
-				if n == len(self.doc.get("items")) - 1:
+				if n == len(self._items) - 1:
 					self.round_off_totals(tax)
 					self._set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"])
 
@@ -570,7 +578,7 @@
 	def calculate_total_net_weight(self):
 		if self.doc.meta.get_field("total_net_weight"):
 			self.doc.total_net_weight = 0.0
-			for d in self.doc.items:
+			for d in self._items:
 				if d.total_weight:
 					self.doc.total_net_weight += d.total_weight
 
@@ -630,7 +638,7 @@
 
 			if total_for_discount_amount:
 				# calculate item amount after Discount Amount
-				for i, item in enumerate(self.doc.get("items")):
+				for i, item in enumerate(self._items):
 					distributed_amount = (
 						flt(self.doc.discount_amount) * item.net_amount / total_for_discount_amount
 					)
@@ -643,7 +651,7 @@
 						self.doc.apply_discount_on == "Net Total"
 						or not taxes
 						or total_for_discount_amount == self.doc.net_total
-					) and i == len(self.doc.get("items")) - 1:
+					) and i == len(self._items) - 1:
 						discount_amount_loss = flt(
 							self.doc.net_total - net_total - self.doc.discount_amount, self.doc.precision("net_total")
 						)
diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py
index 4673230..7c3c387 100644
--- a/erpnext/controllers/website_list_for_contact.py
+++ b/erpnext/controllers/website_list_for_contact.py
@@ -76,12 +76,9 @@
 	ignore_permissions = False
 
 	if not filters:
-		filters = []
+		filters = {}
 
-	if doctype in ["Supplier Quotation", "Purchase Invoice"]:
-		filters.append((doctype, "docstatus", "<", 2))
-	else:
-		filters.append((doctype, "docstatus", "=", 1))
+	filters["docstatus"] = ["<", "2"] if doctype in ["Supplier Quotation", "Purchase Invoice"] else 1
 
 	if (user != "Guest" and is_website_user()) or doctype == "Request for Quotation":
 		parties_doctype = (
@@ -92,12 +89,12 @@
 
 		if customers:
 			if doctype == "Quotation":
-				filters.append(("quotation_to", "=", "Customer"))
-				filters.append(("party_name", "in", customers))
+				filters["quotation_to"] = "Customer"
+				filters["party_name"] = ["in", customers]
 			else:
-				filters.append(("customer", "in", customers))
+				filters["customer"] = ["in", customers]
 		elif suppliers:
-			filters.append(("supplier", "in", suppliers))
+			filters["supplier"] = ["in", suppliers]
 		elif not custom:
 			return []
 
@@ -110,7 +107,7 @@
 
 		if not customers and not suppliers and custom:
 			ignore_permissions = False
-			filters = []
+			filters = {}
 
 	transactions = get_list_for_transactions(
 		doctype,
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 1f76a1a..b261795 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -19,10 +19,6 @@
 				}
 			}
 		});
-
-		if (frm.doc.opportunity_from && frm.doc.party_name){
-			frm.trigger('set_contact_link');
-		}
 	},
 
 	validate: function(frm) {
@@ -130,6 +126,10 @@
 		} else {
 			frappe.contacts.clear_address_and_contact(frm);
 		}
+
+		if (frm.doc.opportunity_from && frm.doc.party_name) {
+			frm.trigger('set_contact_link');
+		}
 	},
 
 	set_contact_link: function(frm) {
@@ -137,6 +137,8 @@
 			frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Customer'}
 		} else if(frm.doc.opportunity_from == "Lead" && frm.doc.party_name) {
 			frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Lead'}
+		} else if (frm.doc.opportunity_from == "Prospect" && frm.doc.party_name) {
+			frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Prospect'}
 		}
 	},
 
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index fba886c..dbfbcc9 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -356,7 +356,7 @@
 
 scheduler_events = {
 	"cron": {
-		"0/5 * * * *": [
+		"0/15 * * * *": [
 			"erpnext.manufacturing.doctype.bom_update_log.bom_update_log.resume_bom_cost_update_jobs",
 		],
 		"0/30 * * * *": [
diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
index 158f143..ba05355 100644
--- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
+++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
@@ -64,8 +64,6 @@
    "fieldtype": "Section Break"
   },
   {
-   "fetch_from": "prevdoc_detail_docname.sales_person",
-   "fetch_if_empty": 1,
    "fieldname": "service_person",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -110,13 +108,15 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-05-27 17:47:21.474282",
+ "modified": "2023-02-27 11:09:33.114458",
  "modified_by": "Administrator",
  "module": "Maintenance",
  "name": "Maintenance Visit Purpose",
+ "naming_rule": "Random",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
index c3f52d4..51f7b24 100644
--- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
+++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
@@ -212,7 +212,7 @@
 			["name", "boms_updated", "status"],
 		)
 		incomplete_level = any(row.get("status") == "Pending" for row in bom_batches)
-		if not bom_batches or incomplete_level:
+		if not bom_batches or not incomplete_level:
 			continue
 
 		# Prep parent BOMs & updated processed BOMs for next level
@@ -252,6 +252,9 @@
 	current_boms = []
 
 	for row in bom_batches:
+		if not row.boms_updated:
+			continue
+
 		boms_updated = json.loads(row.boms_updated)
 		current_boms.extend(boms_updated)
 		boms_updated_dict = {bom: True for bom in boms_updated}
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 3133628..e82f379 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -561,7 +561,34 @@
 		)
 
 	def set_transferred_qty_in_job_card_item(self, ste_doc):
-		from frappe.query_builder.functions import Sum
+		def _get_job_card_items_transferred_qty(ste_doc):
+			from frappe.query_builder.functions import Sum
+
+			job_card_items_transferred_qty = {}
+			job_card_items = [
+				x.get("job_card_item") for x in ste_doc.get("items") if x.get("job_card_item")
+			]
+
+			if job_card_items:
+				se = frappe.qb.DocType("Stock Entry")
+				sed = frappe.qb.DocType("Stock Entry Detail")
+
+				query = (
+					frappe.qb.from_(sed)
+					.join(se)
+					.on(sed.parent == se.name)
+					.select(sed.job_card_item, Sum(sed.qty))
+					.where(
+						(sed.job_card_item.isin(job_card_items))
+						& (se.docstatus == 1)
+						& (se.purpose == "Material Transfer for Manufacture")
+					)
+					.groupby(sed.job_card_item)
+				)
+
+				job_card_items_transferred_qty = frappe._dict(query.run(as_list=True))
+
+			return job_card_items_transferred_qty
 
 		def _validate_over_transfer(row, transferred_qty):
 			"Block over transfer of items if not allowed in settings."
@@ -578,29 +605,23 @@
 					exc=JobCardOverTransferError,
 				)
 
-		for row in ste_doc.items:
-			if not row.job_card_item:
-				continue
+		job_card_items_transferred_qty = _get_job_card_items_transferred_qty(ste_doc)
 
-			sed = frappe.qb.DocType("Stock Entry Detail")
-			se = frappe.qb.DocType("Stock Entry")
-			transferred_qty = (
-				frappe.qb.from_(sed)
-				.join(se)
-				.on(sed.parent == se.name)
-				.select(Sum(sed.qty))
-				.where(
-					(sed.job_card_item == row.job_card_item)
-					& (se.docstatus == 1)
-					& (se.purpose == "Material Transfer for Manufacture")
-				)
-			).run()[0][0]
-
+		if job_card_items_transferred_qty:
 			allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
-			if not allow_excess:
-				_validate_over_transfer(row, transferred_qty)
 
-			frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty))
+			for row in ste_doc.items:
+				if not row.job_card_item:
+					continue
+
+				transferred_qty = flt(job_card_items_transferred_qty.get(row.job_card_item))
+
+				if not allow_excess:
+					_validate_over_transfer(row, transferred_qty)
+
+				frappe.db.set_value(
+					"Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty)
+				)
 
 	def set_transferred_qty(self, update_status=False):
 		"Set total FG Qty in Job Card for which RM was transferred."
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 4aff42c..97480b2 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -506,7 +506,7 @@
 				callback: function(r) {
 					if (r.message) {
 						frappe.model.set_value(cdt, cdn, {
-							"required_qty": 1,
+							"required_qty": row.required_qty || 1,
 							"item_name": r.message.item_name,
 							"description": r.message.description,
 							"source_warehouse": r.message.default_warehouse,
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
index cdf1541..3573a3a 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
@@ -4,7 +4,8 @@
 
 import frappe
 from frappe import _
-from frappe.query_builder.functions import Sum
+from frappe.query_builder.functions import Floor, Sum
+from frappe.utils import cint
 from pypika.terms import ExistsCriterion
 
 
@@ -34,57 +35,55 @@
 
 
 def get_bom_stock(filters):
-	qty_to_produce = filters.get("qty_to_produce") or 1
-	if int(qty_to_produce) < 0:
-		frappe.throw(_("Quantity to Produce can not be less than Zero"))
+	qty_to_produce = filters.get("qty_to_produce")
+	if cint(qty_to_produce) <= 0:
+		frappe.throw(_("Quantity to Produce should be greater than zero."))
 
 	if filters.get("show_exploded_view"):
 		bom_item_table = "BOM Explosion Item"
 	else:
 		bom_item_table = "BOM Item"
 
-	bin = frappe.qb.DocType("Bin")
-	bom = frappe.qb.DocType("BOM")
-	bom_item = frappe.qb.DocType(bom_item_table)
-
-	query = (
-		frappe.qb.from_(bom)
-		.inner_join(bom_item)
-		.on(bom.name == bom_item.parent)
-		.left_join(bin)
-		.on(bom_item.item_code == bin.item_code)
-		.select(
-			bom_item.item_code,
-			bom_item.description,
-			bom_item.stock_qty,
-			bom_item.stock_uom,
-			(bom_item.stock_qty / bom.quantity) * qty_to_produce,
-			Sum(bin.actual_qty),
-			Sum(bin.actual_qty) / (bom_item.stock_qty / bom.quantity),
-		)
-		.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
-		.groupby(bom_item.item_code)
+	warehouse_details = frappe.db.get_value(
+		"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
 	)
 
-	if filters.get("warehouse"):
-		warehouse_details = frappe.db.get_value(
-			"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
-		)
+	BOM = frappe.qb.DocType("BOM")
+	BOM_ITEM = frappe.qb.DocType(bom_item_table)
+	BIN = frappe.qb.DocType("Bin")
+	WH = frappe.qb.DocType("Warehouse")
+	CONDITIONS = ()
 
-		if warehouse_details:
-			wh = frappe.qb.DocType("Warehouse")
-			query = query.where(
-				ExistsCriterion(
-					frappe.qb.from_(wh)
-					.select(wh.name)
-					.where(
-						(wh.lft >= warehouse_details.lft)
-						& (wh.rgt <= warehouse_details.rgt)
-						& (bin.warehouse == wh.name)
-					)
-				)
+	if warehouse_details:
+		CONDITIONS = ExistsCriterion(
+			frappe.qb.from_(WH)
+			.select(WH.name)
+			.where(
+				(WH.lft >= warehouse_details.lft)
+				& (WH.rgt <= warehouse_details.rgt)
+				& (BIN.warehouse == WH.name)
 			)
-		else:
-			query = query.where(bin.warehouse == filters.get("warehouse"))
+		)
+	else:
+		CONDITIONS = BIN.warehouse == filters.get("warehouse")
 
-	return query.run()
+	QUERY = (
+		frappe.qb.from_(BOM)
+		.inner_join(BOM_ITEM)
+		.on(BOM.name == BOM_ITEM.parent)
+		.left_join(BIN)
+		.on((BOM_ITEM.item_code == BIN.item_code) & (CONDITIONS))
+		.select(
+			BOM_ITEM.item_code,
+			BOM_ITEM.description,
+			BOM_ITEM.stock_qty,
+			BOM_ITEM.stock_uom,
+			BOM_ITEM.stock_qty * qty_to_produce / BOM.quantity,
+			Sum(BIN.actual_qty).as_("actual_qty"),
+			Sum(Floor(BIN.actual_qty / (BOM_ITEM.stock_qty * qty_to_produce / BOM.quantity))),
+		)
+		.where((BOM_ITEM.parent == filters.get("bom")) & (BOM_ITEM.parenttype == "BOM"))
+		.groupby(BOM_ITEM.item_code)
+	)
+
+	return QUERY.run()
diff --git a/erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py
new file mode 100644
index 0000000..1c56ebe
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py
@@ -0,0 +1,108 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+
+import frappe
+from frappe.exceptions import ValidationError
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import floor
+
+from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+from erpnext.manufacturing.report.bom_stock_report.bom_stock_report import (
+	get_bom_stock as bom_stock_report,
+)
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+
+class TestBomStockReport(FrappeTestCase):
+	def setUp(self):
+		self.warehouse = "_Test Warehouse - _TC"
+		self.fg_item, self.rm_items = create_items()
+		make_stock_entry(target=self.warehouse, item_code=self.rm_items[0], qty=20, basic_rate=100)
+		make_stock_entry(target=self.warehouse, item_code=self.rm_items[1], qty=40, basic_rate=200)
+		self.bom = make_bom(item=self.fg_item, quantity=1, raw_materials=self.rm_items, rm_qty=10)
+
+	def test_bom_stock_report(self):
+		# Test 1: When `qty_to_produce` is 0.
+		filters = frappe._dict(
+			{
+				"bom": self.bom.name,
+				"warehouse": "Stores - _TC",
+				"qty_to_produce": 0,
+			}
+		)
+		self.assertRaises(ValidationError, bom_stock_report, filters)
+
+		# Test 2: When stock is not available.
+		data = bom_stock_report(
+			frappe._dict(
+				{
+					"bom": self.bom.name,
+					"warehouse": "Stores - _TC",
+					"qty_to_produce": 1,
+				}
+			)
+		)
+		expected_data = get_expected_data(self.bom, "Stores - _TC", 1)
+		self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
+
+		# Test 3: When stock is available.
+		data = bom_stock_report(
+			frappe._dict(
+				{
+					"bom": self.bom.name,
+					"warehouse": self.warehouse,
+					"qty_to_produce": 1,
+				}
+			)
+		)
+		expected_data = get_expected_data(self.bom, self.warehouse, 1)
+		self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
+
+
+def create_items():
+	fg_item = make_item(properties={"is_stock_item": 1}).name
+	rm_item1 = make_item(
+		properties={
+			"is_stock_item": 1,
+			"standard_rate": 100,
+			"opening_stock": 100,
+			"last_purchase_rate": 100,
+		}
+	).name
+	rm_item2 = make_item(
+		properties={
+			"is_stock_item": 1,
+			"standard_rate": 200,
+			"opening_stock": 200,
+			"last_purchase_rate": 200,
+		}
+	).name
+
+	return fg_item, [rm_item1, rm_item2]
+
+
+def get_expected_data(bom, warehouse, qty_to_produce, show_exploded_view=False):
+	expected_data = []
+
+	for item in bom.get("exploded_items") if show_exploded_view else bom.get("items"):
+		in_stock_qty = frappe.get_cached_value(
+			"Bin", {"item_code": item.item_code, "warehouse": warehouse}, "actual_qty"
+		)
+
+		expected_data.append(
+			[
+				item.item_code,
+				item.description,
+				item.stock_qty,
+				item.stock_uom,
+				item.stock_qty * qty_to_produce / bom.quantity,
+				in_stock_qty,
+				floor(in_stock_qty / (item.stock_qty * qty_to_produce / bom.quantity))
+				if in_stock_qty
+				else None,
+			]
+		)
+
+	return expected_data
diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
index 371ecbc..5c46bf3 100644
--- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
+++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
@@ -27,7 +27,13 @@
 
 	records = (
 		frappe.qb.from_(asset)
-		.select(asset.name, asset.opening_accumulated_depreciation, asset.docstatus)
+		.select(
+			asset.name,
+			asset.opening_accumulated_depreciation,
+			asset.gross_purchase_amount,
+			asset.number_of_depreciations_booked,
+			asset.docstatus,
+		)
 		.where(asset.calculate_depreciation == 1)
 		.where(asset.docstatus < 2)
 	).run(as_dict=True)
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index a87c3ec..d1a55e6 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -91,6 +91,9 @@
 	}
 
 	_calculate_taxes_and_totals() {
+		const is_quotation = this.frm.doc.doctype == "Quotation";
+		this.frm.doc._items = is_quotation ? this.filtered_items() : this.frm.doc.items;
+
 		this.validate_conversion_rate();
 		this.calculate_item_values();
 		this.initialize_taxes();
@@ -122,7 +125,7 @@
 	calculate_item_values() {
 		var me = this;
 		if (!this.discount_amount_applied) {
-			for (const item of this.frm.doc.items || []) {
+			for (const item of this.frm.doc._items || []) {
 				frappe.model.round_floats_in(item);
 				item.net_rate = item.rate;
 				item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
@@ -131,8 +134,8 @@
 					item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
 				}
 				else {
-					let qty = item.qty || 1;
-					qty = me.frm.doc.is_return ? -1 * qty : qty;
+					// allow for '0' qty on Credit/Debit notes
+					let qty = item.qty || -1
 					item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item));
 				}
 
@@ -206,7 +209,7 @@
 		});
 		if(has_inclusive_tax==false) return;
 
-		$.each(me.frm.doc["items"] || [], function(n, item) {
+		$.each(me.frm.doc._items || [], function(n, item) {
 			var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
 			var cumulated_tax_fraction = 0.0;
 			var total_inclusive_tax_amount_per_qty = 0;
@@ -277,7 +280,7 @@
 		var me = this;
 		this.frm.doc.total_qty = this.frm.doc.total = this.frm.doc.base_total = this.frm.doc.net_total = this.frm.doc.base_net_total = 0.0;
 
-		$.each(this.frm.doc["items"] || [], function(i, item) {
+		$.each(this.frm.doc._items || [], function(i, item) {
 			me.frm.doc.total += item.amount;
 			me.frm.doc.total_qty += item.qty;
 			me.frm.doc.base_total += item.base_amount;
@@ -330,7 +333,7 @@
 			}
 		});
 
-		$.each(this.frm.doc["items"] || [], function(n, item) {
+		$.each(this.frm.doc._items || [], function(n, item) {
 			var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
 			$.each(me.frm.doc["taxes"] || [], function(i, tax) {
 				// tax_amount represents the amount of tax for the current step
@@ -339,7 +342,7 @@
 				// Adjust divisional loss to the last item
 				if (tax.charge_type == "Actual") {
 					actual_tax_dict[tax.idx] -= current_tax_amount;
-					if (n == me.frm.doc["items"].length - 1) {
+					if (n == me.frm.doc._items.length - 1) {
 						current_tax_amount += actual_tax_dict[tax.idx];
 					}
 				}
@@ -376,7 +379,7 @@
 				}
 
 				// set precision in the last item iteration
-				if (n == me.frm.doc["items"].length - 1) {
+				if (n == me.frm.doc._items.length - 1) {
 					me.round_off_totals(tax);
 					me.set_in_company_currency(tax,
 						["tax_amount", "tax_amount_after_discount_amount"]);
@@ -599,10 +602,11 @@
 
 	_cleanup() {
 		this.frm.doc.base_in_words = this.frm.doc.in_words = "";
+		let items = this.frm.doc._items;
 
-		if(this.frm.doc["items"] && this.frm.doc["items"].length) {
-			if(!frappe.meta.get_docfield(this.frm.doc["items"][0].doctype, "item_tax_amount", this.frm.doctype)) {
-				$.each(this.frm.doc["items"] || [], function(i, item) {
+		if(items && items.length) {
+			if(!frappe.meta.get_docfield(items[0].doctype, "item_tax_amount", this.frm.doctype)) {
+				$.each(items || [], function(i, item) {
 					delete item["item_tax_amount"];
 				});
 			}
@@ -655,7 +659,7 @@
 			var net_total = 0;
 			// calculate item amount after Discount Amount
 			if (total_for_discount_amount) {
-				$.each(this.frm.doc["items"] || [], function(i, item) {
+				$.each(this.frm.doc._items || [], function(i, item) {
 					distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
 					item.net_amount = flt(item.net_amount - distributed_amount,
 						precision("base_amount", item));
@@ -663,7 +667,7 @@
 
 					// discount amount rounding loss adjustment if no taxes
 					if ((!(me.frm.doc.taxes || []).length || total_for_discount_amount==me.frm.doc.net_total || (me.frm.doc.apply_discount_on == "Net Total"))
-							&& i == (me.frm.doc.items || []).length - 1) {
+							&& i == (me.frm.doc._items || []).length - 1) {
 						var discount_amount_loss = flt(me.frm.doc.net_total - net_total
 							- me.frm.doc.discount_amount, precision("net_total"));
 						item.net_amount = flt(item.net_amount + discount_amount_loss,
@@ -892,4 +896,8 @@
 		}
 
 	}
+
+	filtered_items() {
+		return this.frm.doc.items.filter(item => !item["is_alternative"]);
+	}
 };
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 09f2c5d..8d69ea0 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -488,7 +488,7 @@
 								() => {
 									var d = locals[cdt][cdn];
 									me.add_taxes_from_item_tax_template(d.item_tax_rate);
-									if (d.free_item_data) {
+									if (d.free_item_data && d.free_item_data.length > 0) {
 										me.apply_product_discount(d);
 									}
 								},
@@ -1884,11 +1884,13 @@
 
 	get_advances() {
 		if(!this.frm.is_return) {
+			var me = this;
 			return this.frm.call({
 				method: "set_advances",
 				doc: this.frm.doc,
 				callback: function(r, rt) {
 					refresh_field("advances");
+					me.frm.dirty();
 				}
 			})
 		}
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index b348bd3..81ef44d 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -90,7 +90,7 @@
 				|| frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
 					this.frm.add_custom_button(
 						__("Sales Order"),
-						this.frm.cscript["Make Sales Order"],
+						() => this.make_sales_order(),
 						__("Create")
 					);
 				}
@@ -145,6 +145,20 @@
 
 	}
 
+	make_sales_order() {
+		var me = this;
+
+		let has_alternative_item = this.frm.doc.items.some((item) => item.is_alternative);
+		if (has_alternative_item) {
+			this.show_alternative_items_dialog();
+		} else {
+			frappe.model.open_mapped_doc({
+				method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
+				frm: me.frm
+			});
+		}
+	}
+
 	set_dynamic_field_label(){
 		if (this.frm.doc.quotation_to == "Customer")
 		{
@@ -220,17 +234,111 @@
 			}
 		})
 	}
+
+	show_alternative_items_dialog() {
+		let me = this;
+
+		const table_fields = [
+		{
+			fieldtype:"Data",
+			fieldname:"name",
+			label: __("Name"),
+			read_only: 1,
+		},
+		{
+			fieldtype:"Link",
+			fieldname:"item_code",
+			options: "Item",
+			label: __("Item Code"),
+			read_only: 1,
+			in_list_view: 1,
+			columns: 2,
+			formatter: (value, df, options, doc) => {
+				return doc.is_alternative ? `<span class="indicator yellow">${value}</span>` : value;
+			}
+		},
+		{
+			fieldtype:"Data",
+			fieldname:"description",
+			label: __("Description"),
+			in_list_view: 1,
+			read_only: 1,
+		},
+		{
+			fieldtype:"Currency",
+			fieldname:"amount",
+			label: __("Amount"),
+			options: "currency",
+			in_list_view: 1,
+			read_only: 1,
+		},
+		{
+			fieldtype:"Check",
+			fieldname:"is_alternative",
+			label: __("Is Alternative"),
+			read_only: 1,
+		}];
+
+
+		this.data = this.frm.doc.items.filter(
+			(item) => item.is_alternative || item.has_alternative_item
+		).map((item) => {
+			return {
+				"name": item.name,
+				"item_code": item.item_code,
+				"description": item.description,
+				"amount": item.amount,
+				"is_alternative": item.is_alternative,
+			}
+		});
+
+		const dialog = new frappe.ui.Dialog({
+			title: __("Select Alternative Items for Sales Order"),
+			fields: [
+				{
+					fieldname: "info",
+					fieldtype: "HTML",
+					read_only: 1
+				},
+				{
+					fieldname: "alternative_items",
+					fieldtype: "Table",
+					cannot_add_rows: true,
+					in_place_edit: true,
+					reqd: 1,
+					data: this.data,
+					description: __("Select an item from each set to be used in the Sales Order."),
+					get_data: () => {
+						return this.data;
+					},
+					fields: table_fields
+				},
+			],
+			primary_action: function() {
+				frappe.model.open_mapped_doc({
+					method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
+					frm: me.frm,
+					args: {
+						selected_items: dialog.fields_dict.alternative_items.grid.get_selected_children()
+					}
+				});
+				dialog.hide();
+			},
+			primary_action_label: __('Continue')
+		});
+
+		dialog.fields_dict.info.$wrapper.html(
+			`<p class="small text-muted">
+				<span class="indicator yellow"></span>
+				Alternative Items
+			</p>`
+		)
+		dialog.show();
+	}
 };
 
 cur_frm.script_manager.make(erpnext.selling.QuotationController);
 
-cur_frm.cscript['Make Sales Order'] = function() {
-	frappe.model.open_mapped_doc({
-		method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
-		frm: cur_frm
-	})
-}
-
 frappe.ui.form.on("Quotation Item", "items_on_form_rendered", "packed_items_on_form_rendered", function(frm, cdt, cdn) {
 	// enable tax_amount field if Actual
 })
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 063813b..fc66db2 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -35,6 +35,9 @@
 
 		make_packing_list(self)
 
+	def before_submit(self):
+		self.set_has_alternative_item()
+
 	def validate_valid_till(self):
 		if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
 			frappe.throw(_("Valid till date cannot be before transaction date"))
@@ -59,7 +62,18 @@
 					title=_("Unpublished Item"),
 				)
 
+	def set_has_alternative_item(self):
+		"""Mark 'Has Alternative Item' for rows."""
+		if not any(row.is_alternative for row in self.get("items")):
+			return
+
+		items_with_alternatives = self.get_rows_with_alternatives()
+		for row in self.get("items"):
+			if not row.is_alternative and row.name in items_with_alternatives:
+				row.has_alternative_item = 1
+
 	def get_ordered_status(self):
+		status = "Open"
 		ordered_items = frappe._dict(
 			frappe.db.get_all(
 				"Sales Order Item",
@@ -70,16 +84,40 @@
 			)
 		)
 
-		status = "Open"
-		if ordered_items:
+		if not ordered_items:
+			return status
+
+		has_alternatives = any(row.is_alternative for row in self.get("items"))
+		self._items = self.get_valid_items() if has_alternatives else self.get("items")
+
+		if any(row.qty > ordered_items.get(row.item_code, 0.0) for row in self._items):
+			status = "Partially Ordered"
+		else:
 			status = "Ordered"
 
-			for item in self.get("items"):
-				if item.qty > ordered_items.get(item.item_code, 0.0):
-					status = "Partially Ordered"
-
 		return status
 
+	def get_valid_items(self):
+		"""
+		Filters out items in an alternatives set that were not ordered.
+		"""
+
+		def is_in_sales_order(row):
+			in_sales_order = bool(
+				frappe.db.exists(
+					"Sales Order Item", {"quotation_item": row.name, "item_code": row.item_code, "docstatus": 1}
+				)
+			)
+			return in_sales_order
+
+		def can_map(row) -> bool:
+			if row.is_alternative or row.has_alternative_item:
+				return is_in_sales_order(row)
+
+			return True
+
+		return list(filter(can_map, self.get("items")))
+
 	def is_fully_ordered(self):
 		return self.get_ordered_status() == "Ordered"
 
@@ -176,6 +214,22 @@
 	def on_recurring(self, reference_doc, auto_repeat_doc):
 		self.valid_till = None
 
+	def get_rows_with_alternatives(self):
+		rows_with_alternatives = []
+		table_length = len(self.get("items"))
+
+		for idx, row in enumerate(self.get("items")):
+			if row.is_alternative:
+				continue
+
+			if idx == (table_length - 1):
+				break
+
+			if self.get("items")[idx + 1].is_alternative:
+				rows_with_alternatives.append(row.name)
+
+		return rows_with_alternatives
+
 
 def get_list_context(context=None):
 	from erpnext.controllers.website_list_for_contact import get_list_context
@@ -221,6 +275,8 @@
 		)
 	)
 
+	selected_rows = [x.get("name") for x in frappe.flags.get("args", {}).get("selected_items", [])]
+
 	def set_missing_values(source, target):
 		if customer:
 			target.customer = customer.name
@@ -244,6 +300,24 @@
 			target.blanket_order = obj.blanket_order
 			target.blanket_order_rate = obj.blanket_order_rate
 
+	def can_map_row(item) -> bool:
+		"""
+		Row mapping from Quotation to Sales order:
+		1. If no selections, map all non-alternative rows (that sum up to the grand total)
+		2. If selections: Is Alternative Item/Has Alternative Item: Map if selected and adequate qty
+		3. If selections: Simple row: Map if adequate qty
+		"""
+		has_qty = item.qty > 0
+
+		if not selected_rows:
+			return not item.is_alternative
+
+		if selected_rows and (item.is_alternative or item.has_alternative_item):
+			return (item.name in selected_rows) and has_qty
+
+		# Simple row
+		return has_qty
+
 	doclist = get_mapped_doc(
 		"Quotation",
 		source_name,
@@ -253,7 +327,7 @@
 				"doctype": "Sales Order Item",
 				"field_map": {"parent": "prevdoc_docname", "name": "quotation_item"},
 				"postprocess": update_item,
-				"condition": lambda doc: doc.qty > 0,
+				"condition": can_map_row,
 			},
 			"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
 			"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
@@ -322,7 +396,11 @@
 		source_name,
 		{
 			"Quotation": {"doctype": "Sales Invoice", "validation": {"docstatus": ["=", 1]}},
-			"Quotation Item": {"doctype": "Sales Invoice Item", "postprocess": update_item},
+			"Quotation Item": {
+				"doctype": "Sales Invoice Item",
+				"postprocess": update_item,
+				"condition": lambda row: not row.is_alternative,
+			},
 			"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
 			"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
 		},
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index cdf5f5d..67f6518 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -457,6 +457,139 @@
 			expected_index = id + 1
 			self.assertEqual(item.idx, expected_index)
 
+	def test_alternative_items_with_stock_items(self):
+		"""
+		Check if taxes & totals considers only non-alternative items with:
+		- One set of non-alternative & alternative items [first 3 rows]
+		- One simple stock item
+		"""
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		item_list = []
+		stock_items = {
+			"_Test Simple Item 1": 100,
+			"_Test Alt 1": 120,
+			"_Test Alt 2": 110,
+			"_Test Simple Item 2": 200,
+		}
+
+		for item, rate in stock_items.items():
+			make_item(item, {"is_stock_item": 1})
+			item_list.append(
+				{
+					"item_code": item,
+					"qty": 1,
+					"rate": rate,
+					"is_alternative": bool("Alt" in item),
+				}
+			)
+
+		quotation = make_quotation(item_list=item_list, do_not_submit=1)
+		quotation.append(
+			"taxes",
+			{
+				"account_head": "_Test Account VAT - _TC",
+				"charge_type": "On Net Total",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "VAT",
+				"doctype": "Sales Taxes and Charges",
+				"rate": 10,
+			},
+		)
+		quotation.submit()
+
+		self.assertEqual(quotation.net_total, 300)
+		self.assertEqual(quotation.grand_total, 330)
+
+	def test_alternative_items_with_service_items(self):
+		"""
+		Check if taxes & totals considers only non-alternative items with:
+		- One set of non-alternative & alternative service items [first 3 rows]
+		- One simple non-alternative service item
+		All having the same item code and unique item name/description due to
+		dynamic services
+		"""
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		item_list = []
+		service_items = {
+			"Tiling with Standard Tiles": 100,
+			"Alt Tiling with Durable Tiles": 150,
+			"Alt Tiling with Premium Tiles": 180,
+			"False Ceiling with Material #234": 190,
+		}
+
+		make_item("_Test Dynamic Service Item", {"is_stock_item": 0})
+
+		for name, rate in service_items.items():
+			item_list.append(
+				{
+					"item_code": "_Test Dynamic Service Item",
+					"item_name": name,
+					"description": name,
+					"qty": 1,
+					"rate": rate,
+					"is_alternative": bool("Alt" in name),
+				}
+			)
+
+		quotation = make_quotation(item_list=item_list, do_not_submit=1)
+		quotation.append(
+			"taxes",
+			{
+				"account_head": "_Test Account VAT - _TC",
+				"charge_type": "On Net Total",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "VAT",
+				"doctype": "Sales Taxes and Charges",
+				"rate": 10,
+			},
+		)
+		quotation.submit()
+
+		self.assertEqual(quotation.net_total, 290)
+		self.assertEqual(quotation.grand_total, 319)
+
+	def test_alternative_items_sales_order_mapping_with_stock_items(self):
+		from erpnext.selling.doctype.quotation.quotation import make_sales_order
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		frappe.flags.args = frappe._dict()
+		item_list = []
+		stock_items = {
+			"_Test Simple Item 1": 100,
+			"_Test Alt 1": 120,
+			"_Test Alt 2": 110,
+			"_Test Simple Item 2": 200,
+		}
+
+		for item, rate in stock_items.items():
+			make_item(item, {"is_stock_item": 1})
+			item_list.append(
+				{
+					"item_code": item,
+					"qty": 1,
+					"rate": rate,
+					"is_alternative": bool("Alt" in item),
+					"warehouse": "_Test Warehouse - _TC",
+				}
+			)
+
+		quotation = make_quotation(item_list=item_list)
+
+		frappe.flags.args.selected_items = [quotation.items[2]]
+		sales_order = make_sales_order(quotation.name)
+		sales_order.delivery_date = add_days(sales_order.transaction_date, 10)
+		sales_order.save()
+
+		self.assertEqual(sales_order.items[0].item_code, "_Test Alt 2")
+		self.assertEqual(sales_order.items[1].item_code, "_Test Simple Item 2")
+		self.assertEqual(sales_order.net_total, 310)
+
+		sales_order.submit()
+		quotation.reload()
+		self.assertEqual(quotation.status, "Ordered")
+
 
 test_records = frappe.get_test_records("Quotation")
 
diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json
index ca7dfd2..f2aabc5 100644
--- a/erpnext/selling/doctype/quotation_item/quotation_item.json
+++ b/erpnext/selling/doctype/quotation_item/quotation_item.json
@@ -49,6 +49,8 @@
   "pricing_rules",
   "stock_uom_rate",
   "is_free_item",
+  "is_alternative",
+  "has_alternative_item",
   "section_break_43",
   "valuation_rate",
   "column_break_45",
@@ -643,12 +645,28 @@
    "no_copy": 1,
    "options": "currency",
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "is_alternative",
+   "fieldtype": "Check",
+   "label": "Is Alternative",
+   "print_hide": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "has_alternative_item",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Has Alternative Item",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-12-25 02:49:53.926625",
+ "modified": "2023-02-06 11:00:07.042364",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Quotation Item",
@@ -656,5 +674,6 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index fb64772..449d461 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -275,7 +275,7 @@
 		if (this.frm.doc.docstatus===0) {
 			this.frm.add_custom_button(__('Quotation'),
 				function() {
-					erpnext.utils.map_current_doc({
+					let d = erpnext.utils.map_current_doc({
 						method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
 						source_doctype: "Quotation",
 						target: me.frm,
@@ -293,7 +293,16 @@
 							docstatus: 1,
 							status: ["!=", "Lost"]
 						}
-					})
+					});
+
+					setTimeout(() => {
+						d.$parent.append(`
+							<span class='small text-muted'>
+								${__("Note: Please create Sales Orders from individual Quotations to select from among Alternative Items.")}
+							</span>
+					`);
+					}, 200);
+
 				}, __("Get Items From"));
 		}
 
@@ -309,9 +318,12 @@
 
 	make_work_order() {
 		var me = this;
-		this.frm.call({
-			doc: this.frm.doc,
-			method: 'get_work_order_items',
+		me.frm.call({
+			method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items",
+			args: {
+				sales_order: this.frm.docname,
+			},
+			freeze: true,
 			callback: function(r) {
 				if(!r.message) {
 					frappe.msgprint({
@@ -321,14 +333,7 @@
 					});
 					return;
 				}
-				else if(!r.message) {
-					frappe.msgprint({
-						title: __('Work Order not created'),
-						message: __('Work Order already created for all items with BOM'),
-						indicator: 'orange'
-					});
-					return;
-				} else {
+				else {
 					const fields = [{
 						label: 'Items',
 						fieldtype: 'Table',
@@ -429,9 +434,9 @@
 	make_raw_material_request() {
 		var me = this;
 		this.frm.call({
-			doc: this.frm.doc,
-			method: 'get_work_order_items',
+			method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items",
 			args: {
+				sales_order: this.frm.docname,
 				for_raw_material_request: 1
 			},
 			callback: function(r) {
@@ -450,6 +455,7 @@
 	}
 
 	make_raw_material_request_dialog(r) {
+		var me = this;
 		var fields = [
 			{fieldtype:'Check', fieldname:'include_exploded_items',
 				label: __('Include Exploded Items')},
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index ca6a51a..385d0f3 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -6,11 +6,12 @@
 
 import frappe
 import frappe.utils
-from frappe import _
+from frappe import _, qb
 from frappe.contacts.doctype.address.address import get_company_address
 from frappe.desk.notifications import clear_doctype_notifications
 from frappe.model.mapper import get_mapped_doc
 from frappe.model.utils import get_fetch_values
+from frappe.query_builder.functions import Sum
 from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html
 
 from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
@@ -414,51 +415,6 @@
 			self.indicator_color = "green"
 			self.indicator_title = _("Paid")
 
-	@frappe.whitelist()
-	def get_work_order_items(self, for_raw_material_request=0):
-		"""Returns items with BOM that already do not have a linked work order"""
-		items = []
-		item_codes = [i.item_code for i in self.items]
-		product_bundle_parents = [
-			pb.new_item_code
-			for pb in frappe.get_all(
-				"Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
-			)
-		]
-
-		for table in [self.items, self.packed_items]:
-			for i in table:
-				bom = get_default_bom(i.item_code)
-				stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
-
-				if not for_raw_material_request:
-					total_work_order_qty = flt(
-						frappe.db.sql(
-							"""select sum(qty) from `tabWork Order`
-						where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2""",
-							(i.item_code, self.name, i.name),
-						)[0][0]
-					)
-					pending_qty = stock_qty - total_work_order_qty
-				else:
-					pending_qty = stock_qty
-
-				if pending_qty and i.item_code not in product_bundle_parents:
-					items.append(
-						dict(
-							name=i.name,
-							item_code=i.item_code,
-							description=i.description,
-							bom=bom or "",
-							warehouse=i.warehouse,
-							pending_qty=pending_qty,
-							required_qty=pending_qty if for_raw_material_request else 0,
-							sales_order_item=i.name,
-						)
-					)
-
-		return items
-
 	def on_recurring(self, reference_doc, auto_repeat_doc):
 		def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
 			delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
@@ -1350,3 +1306,57 @@
 		return
 
 	frappe.db.set_value("Sales Order Item", sales_order_item, "produced_qty", total_produced_qty)
+
+
+@frappe.whitelist()
+def get_work_order_items(sales_order, for_raw_material_request=0):
+	"""Returns items with BOM that already do not have a linked work order"""
+	if sales_order:
+		so = frappe.get_doc("Sales Order", sales_order)
+
+		wo = qb.DocType("Work Order")
+
+		items = []
+		item_codes = [i.item_code for i in so.items]
+		product_bundle_parents = [
+			pb.new_item_code
+			for pb in frappe.get_all(
+				"Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
+			)
+		]
+
+		for table in [so.items, so.packed_items]:
+			for i in table:
+				bom = get_default_bom(i.item_code)
+				stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
+
+				if not for_raw_material_request:
+					total_work_order_qty = flt(
+						qb.from_(wo)
+						.select(Sum(wo.qty))
+						.where(
+							(wo.production_item == i.item_code)
+							& (wo.sales_order == so.name) * (wo.sales_order_item == i.name)
+							& (wo.docstatus.lte(2))
+						)
+						.run()[0][0]
+					)
+					pending_qty = stock_qty - total_work_order_qty
+				else:
+					pending_qty = stock_qty
+
+				if pending_qty and i.item_code not in product_bundle_parents:
+					items.append(
+						dict(
+							name=i.name,
+							item_code=i.item_code,
+							description=i.description,
+							bom=bom or "",
+							warehouse=i.warehouse,
+							pending_qty=pending_qty,
+							required_qty=pending_qty if for_raw_material_request else 0,
+							sales_order_item=i.name,
+						)
+					)
+
+		return items
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index d4d7c58..627914f 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1217,6 +1217,8 @@
 		self.assertTrue(si.get("payment_schedule"))
 
 	def test_make_work_order(self):
+		from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
+
 		# Make a new Sales Order
 		so = make_sales_order(
 			**{
@@ -1230,7 +1232,7 @@
 		# Raise Work Orders
 		po_items = []
 		so_item_name = {}
-		for item in so.get_work_order_items():
+		for item in get_work_order_items(so.name):
 			po_items.append(
 				{
 					"warehouse": item.get("warehouse"),
@@ -1448,6 +1450,7 @@
 
 		from erpnext.controllers.item_variant import create_variant
 		from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+		from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
 
 		make_item(  # template item
 			"Test-WO-Tshirt",
@@ -1487,7 +1490,7 @@
 				]
 			}
 		)
-		wo_items = so.get_work_order_items()
+		wo_items = get_work_order_items(so.name)
 
 		self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R")
 		self.assertEqual(wo_items[0].get("bom"), red_var_bom.name)
@@ -1497,6 +1500,8 @@
 		self.assertEqual(wo_items[1].get("bom"), template_bom.name)
 
 	def test_request_for_raw_materials(self):
+		from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
+
 		item = make_item(
 			"_Test Finished Item",
 			{
@@ -1529,7 +1534,7 @@
 		so = make_sales_order(**{"item_list": [{"item_code": item.item_code, "qty": 1, "rate": 1000}]})
 		so.submit()
 		mr_dict = frappe._dict()
-		items = so.get_work_order_items(1)
+		items = get_work_order_items(so.name, 1)
 		mr_dict["items"] = items
 		mr_dict["include_exploded_items"] = 0
 		mr_dict["ignore_existing_ordered_qty"] = 1
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index c442774..46320e5 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -522,7 +522,7 @@
 
 			const from_selector = field === 'qty' && value === "+1";
 			if (from_selector)
-				value = flt(item_row.qty) + flt(value);
+				value = flt(item_row.stock_qty) + flt(value);
 
 			if (item_row_exists) {
 				if (field === 'qty')
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 5ce6e9c..f1df3a1 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -253,7 +253,7 @@
 	}
 
 	calculate_commission() {
-		if(!this.frm.fields_dict.commission_rate) return;
+		if(!this.frm.fields_dict.commission_rate || this.frm.doc.docstatus === 1) return;
 
 		if(this.frm.doc.commission_rate > 100) {
 			this.frm.set_value("commission_rate", 100);
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 5bcb05a..9a9ddf4 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -33,6 +33,9 @@
 			'Material Request': () => {
 				open_form(frm, "Material Request", "Material Request Item", "items");
 			},
+			'Stock Entry': () => {
+				open_form(frm, "Stock Entry", "Stock Entry Detail", "items");
+			},
 		};
 
 	},
@@ -893,6 +896,9 @@
 		new_child_doc.item_name = frm.doc.item_name;
 		new_child_doc.uom = frm.doc.stock_uom;
 		new_child_doc.description = frm.doc.description;
+		if (!new_child_doc.qty) {
+			new_child_doc.qty = 1.0;
+		}
 
 		frappe.run_serially([
 			() => frappe.ui.form.make_quick_entry(doctype, null, null, new_doc),
diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py
index fb1a28d..0c24d3c 100644
--- a/erpnext/stock/doctype/item_alternative/item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/item_alternative.py
@@ -54,7 +54,7 @@
 		if not item_data.allow_alternative_item:
 			frappe.throw(alternate_item_check_msg.format(self.item_code))
 		if self.two_way and not alternative_item_data.allow_alternative_item:
-			frappe.throw(alternate_item_check_msg.format(self.item_code))
+			frappe.throw(alternate_item_check_msg.format(self.alternative_item_code))
 
 	def validate_duplicate(self):
 		if frappe.db.get_value(
diff --git a/erpnext/stock/doctype/item_price/item_price.js b/erpnext/stock/doctype/item_price/item_price.js
index 12cf6cf..ce489ff 100644
--- a/erpnext/stock/doctype/item_price/item_price.js
+++ b/erpnext/stock/doctype/item_price/item_price.js
@@ -2,7 +2,18 @@
 // License: GNU General Public License v3. See license.txt
 
 frappe.ui.form.on("Item Price", {
-	onload: function (frm) {
+	setup(frm) {
+		frm.set_query("item_code", function() {
+			return {
+				filters: {
+					"disabled": 0,
+					"has_variants": 0
+				}
+			};
+		});
+	},
+
+	onload(frm) {
 		// Fetch price list details
 		frm.add_fetch("price_list", "buying", "buying");
 		frm.add_fetch("price_list", "selling", "selling");
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index bcd31ad..54d1ae6 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -3,7 +3,7 @@
 
 
 import frappe
-from frappe import _
+from frappe import _, bold
 from frappe.model.document import Document
 from frappe.query_builder import Criterion
 from frappe.query_builder.functions import Cast_
@@ -21,6 +21,7 @@
 		self.update_price_list_details()
 		self.update_item_details()
 		self.check_duplicates()
+		self.validate_item_template()
 
 	def validate_item(self):
 		if not frappe.db.exists("Item", self.item_code):
@@ -49,6 +50,12 @@
 				"Item", self.item_code, ["item_name", "description"]
 			)
 
+	def validate_item_template(self):
+		if frappe.get_cached_value("Item", self.item_code, "has_variants"):
+			msg = f"Item Price cannot be created for the template item {bold(self.item_code)}"
+
+			frappe.throw(_(msg))
+
 	def check_duplicates(self):
 
 		item_price = frappe.qb.DocType("Item Price")
diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py
index 30d933e..8fd4938 100644
--- a/erpnext/stock/doctype/item_price/test_item_price.py
+++ b/erpnext/stock/doctype/item_price/test_item_price.py
@@ -16,6 +16,28 @@
 		frappe.db.sql("delete from `tabItem Price`")
 		make_test_records_for_doctype("Item Price", force=True)
 
+	def test_template_item_price(self):
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		item = make_item(
+			"Test Template Item 1",
+			{
+				"has_variants": 1,
+				"variant_based_on": "Manufacturer",
+			},
+		)
+
+		doc = frappe.get_doc(
+			{
+				"doctype": "Item Price",
+				"price_list": "_Test Price List",
+				"item_code": item.name,
+				"price_list_rate": 100,
+			}
+		)
+
+		self.assertRaises(frappe.ValidationError, doc.save)
+
 	def test_duplicate_item(self):
 		doc = frappe.copy_doc(test_records[0])
 		self.assertRaises(ItemPriceDuplicateItem, doc.save)
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 6426fe8..8aeb751 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -10,6 +10,7 @@
 import frappe
 from frappe import _, msgprint
 from frappe.model.mapper import get_mapped_doc
+from frappe.query_builder.functions import Sum
 from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate
 
 from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
@@ -180,6 +181,34 @@
 		self.update_requested_qty()
 		self.update_requested_qty_in_production_plan()
 
+	def get_mr_items_ordered_qty(self, mr_items):
+		mr_items_ordered_qty = {}
+		mr_items = [d.name for d in self.get("items") if d.name in mr_items]
+
+		doctype = qty_field = None
+		if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
+			doctype = frappe.qb.DocType("Stock Entry Detail")
+			qty_field = doctype.transfer_qty
+		elif self.material_request_type == "Manufacture":
+			doctype = frappe.qb.DocType("Work Order")
+			qty_field = doctype.qty
+
+		if doctype and qty_field:
+			query = (
+				frappe.qb.from_(doctype)
+				.select(doctype.material_request_item, Sum(qty_field))
+				.where(
+					(doctype.material_request == self.name)
+					& (doctype.material_request_item.isin(mr_items))
+					& (doctype.docstatus == 1)
+				)
+				.groupby(doctype.material_request_item)
+			)
+
+			mr_items_ordered_qty = frappe._dict(query.run())
+
+		return mr_items_ordered_qty
+
 	def update_completed_qty(self, mr_items=None, update_modified=True):
 		if self.material_request_type == "Purchase":
 			return
@@ -187,18 +216,13 @@
 		if not mr_items:
 			mr_items = [d.name for d in self.get("items")]
 
+		mr_items_ordered_qty = self.get_mr_items_ordered_qty(mr_items)
+		mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance")
+
 		for d in self.get("items"):
 			if d.name in mr_items:
 				if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
-					d.ordered_qty = flt(
-						frappe.db.sql(
-							"""select sum(transfer_qty)
-						from `tabStock Entry Detail` where material_request = %s
-						and material_request_item = %s and docstatus = 1""",
-							(self.name, d.name),
-						)[0][0]
-					)
-					mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance")
+					d.ordered_qty = flt(mr_items_ordered_qty.get(d.name))
 
 					if mr_qty_allowance:
 						allowed_qty = d.qty + (d.qty * (mr_qty_allowance / 100))
@@ -217,14 +241,7 @@
 						)
 
 				elif self.material_request_type == "Manufacture":
-					d.ordered_qty = flt(
-						frappe.db.sql(
-							"""select sum(qty)
-						from `tabWork Order` where material_request = %s
-						and material_request_item = %s and docstatus = 1""",
-							(self.name, d.name),
-						)[0][0]
-					)
+					d.ordered_qty = flt(mr_items_ordered_qty.get(d.name))
 
 				frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty)
 
@@ -587,6 +604,9 @@
 
 	def set_missing_values(source, target):
 		target.purpose = source.material_request_type
+		target.from_warehouse = source.set_from_warehouse
+		target.to_warehouse = source.set_warehouse
+
 		if source.job_card:
 			target.purpose = "Material Transfer for Manufacture"
 
@@ -722,6 +742,7 @@
 def make_in_transit_stock_entry(source_name, in_transit_warehouse):
 	ste_doc = make_stock_entry(source_name)
 	ste_doc.add_to_transit = 1
+	ste_doc.to_warehouse = in_transit_warehouse
 
 	for row in ste_doc.items:
 		row.t_warehouse = in_transit_warehouse
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index bb318f7..c1abd31 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -293,6 +293,7 @@
 			get_purchase_document_details,
 		)
 
+		stock_rbnb = None
 		if erpnext.is_perpetual_inventory_enabled(self.company):
 			stock_rbnb = self.get_company_default("stock_received_but_not_billed")
 			landed_cost_entries = get_item_account_wise_additional_cost(self.name)
@@ -450,6 +451,21 @@
 								item=d,
 							)
 
+					if d.rate_difference_with_purchase_invoice and stock_rbnb:
+						account_currency = get_account_currency(stock_rbnb)
+						self.add_gl_entry(
+							gl_entries=gl_entries,
+							account=stock_rbnb,
+							cost_center=d.cost_center,
+							debit=0.0,
+							credit=flt(d.rate_difference_with_purchase_invoice),
+							remarks=_("Adjustment based on Purchase Invoice rate"),
+							against_account=warehouse_account_name,
+							account_currency=account_currency,
+							project=d.project,
+							item=d,
+						)
+
 					# sub-contracting warehouse
 					if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
 						self.add_gl_entry(
@@ -470,10 +486,11 @@
 						+ flt(d.landed_cost_voucher_amount)
 						+ flt(d.rm_supp_cost)
 						+ flt(d.item_tax_amount)
+						+ flt(d.rate_difference_with_purchase_invoice)
 					)
 
 					divisional_loss = flt(
-						valuation_amount_as_per_doc - stock_value_diff, d.precision("base_net_amount")
+						valuation_amount_as_per_doc - flt(stock_value_diff), d.precision("base_net_amount")
 					)
 
 					if divisional_loss:
@@ -765,7 +782,7 @@
 			updated_pr += update_billed_amount_based_on_po(po_details, update_modified)
 
 		for pr in set(updated_pr):
-			pr_doc = self if (pr == self.name) else frappe.get_cached_doc("Purchase Receipt", pr)
+			pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr)
 			update_billing_percentage(pr_doc, update_modified=update_modified)
 
 		self.load_from_db()
@@ -881,7 +898,7 @@
 	return {d.po_detail: flt(d.billed_amt) for d in query}
 
 
-def update_billing_percentage(pr_doc, update_modified=True):
+def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate=False):
 	# Reload as billed amount was set in db directly
 	pr_doc.load_from_db()
 
@@ -897,6 +914,12 @@
 
 		total_amount += total_billable_amount
 		total_billed_amount += flt(item.billed_amt)
+		if adjust_incoming_rate:
+			adjusted_amt = 0.0
+			if item.billed_amt and item.amount:
+				adjusted_amt = flt(item.billed_amt) - flt(item.amount)
+
+			item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
 
 	percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
 	pr_doc.db_set("per_billed", percent_billed)
@@ -906,6 +929,26 @@
 		pr_doc.set_status(update=True)
 		pr_doc.notify_update()
 
+	if adjust_incoming_rate:
+		adjust_incoming_rate_for_pr(pr_doc)
+
+
+def adjust_incoming_rate_for_pr(doc):
+	doc.update_valuation_rate(reset_outgoing_rate=False)
+
+	for item in doc.get("items"):
+		item.db_update()
+
+	doc.docstatus = 2
+	doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
+	doc.make_gl_entries_on_cancel()
+
+	# update stock & gl entries for submit state of PR
+	doc.docstatus = 1
+	doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
+	doc.make_gl_entries()
+	doc.repost_future_sle_and_gle()
+
 
 def get_item_wise_returned_qty(pr_doc):
 	items = [d.name for d in pr_doc.items]
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 7a350b9..cd320fd 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -69,6 +69,7 @@
   "item_tax_amount",
   "rm_supp_cost",
   "landed_cost_voucher_amount",
+  "rate_difference_with_purchase_invoice",
   "billed_amt",
   "warehouse_and_reference",
   "warehouse",
@@ -1007,12 +1008,20 @@
    "fieldtype": "Check",
    "label": "Has Item Scanned",
    "read_only": 1
+  },
+  {
+   "fieldname": "rate_difference_with_purchase_invoice",
+   "fieldtype": "Currency",
+   "label": "Rate Difference with Purchase Invoice",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-01-18 15:48:58.114923",
+ "modified": "2023-02-28 15:43:04.470104",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 2a9f091..9673c81 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -6,7 +6,7 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.model.mapper import get_mapped_doc
-from frappe.utils import cint, cstr, flt
+from frappe.utils import cint, cstr, flt, get_number_format_info
 
 from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template import (
 	get_template_details,
@@ -156,7 +156,9 @@
 		for i in range(1, 11):
 			reading_value = reading.get("reading_" + str(i))
 			if reading_value is not None and reading_value.strip():
-				result = flt(reading.get("min_value")) <= flt(reading_value) <= flt(reading.get("max_value"))
+				result = (
+					flt(reading.get("min_value")) <= parse_float(reading_value) <= flt(reading.get("max_value"))
+				)
 				if not result:
 					return False
 		return True
@@ -196,7 +198,7 @@
 			# numeric readings
 			for i in range(1, 11):
 				field = "reading_" + str(i)
-				data[field] = flt(reading.get(field))
+				data[field] = parse_float(reading.get(field))
 			data["mean"] = self.calculate_mean(reading)
 
 		return data
@@ -210,7 +212,7 @@
 		for i in range(1, 11):
 			reading_value = reading.get("reading_" + str(i))
 			if reading_value is not None and reading_value.strip():
-				readings_list.append(flt(reading_value))
+				readings_list.append(parse_float(reading_value))
 
 		actual_mean = mean(readings_list) if readings_list else 0
 		return actual_mean
@@ -324,3 +326,19 @@
 	)
 
 	return doc
+
+
+def parse_float(num: str) -> float:
+	"""Since reading_# fields are `Data` field they might contain number which
+	is representation in user's prefered number format instead of machine
+	readable format. This function converts them to machine readable format."""
+
+	number_format = frappe.db.get_default("number_format") or "#,###.##"
+	decimal_str, comma_str, _number_format_precision = get_number_format_info(number_format)
+
+	if decimal_str == "," and comma_str == ".":
+		num = num.replace(",", "#$")
+		num = num.replace(".", ",")
+		num = num.replace("#$", ".")
+
+	return flt(num)
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
index 4f19643..9d2e139 100644
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
@@ -2,7 +2,7 @@
 # See license.txt
 
 import frappe
-from frappe.tests.utils import FrappeTestCase
+from frappe.tests.utils import FrappeTestCase, change_settings
 from frappe.utils import nowdate
 
 from erpnext.controllers.stock_controller import (
@@ -216,6 +216,40 @@
 		qa.save()
 		self.assertEqual(qa.status, "Accepted")
 
+	@change_settings("System Settings", {"number_format": "#.###,##"})
+	def test_diff_number_format(self):
+		self.assertEqual(frappe.db.get_default("number_format"), "#.###,##")  # sanity check
+
+		# Test QI based on acceptance values (Non formula)
+		dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
+		readings = [
+			{
+				"specification": "Iron Content",  # numeric reading
+				"min_value": 60,
+				"max_value": 100,
+				"reading_1": "70,000",
+			},
+			{
+				"specification": "Iron Content",  # numeric reading
+				"min_value": 60,
+				"max_value": 100,
+				"reading_1": "1.100,00",
+			},
+		]
+
+		qa = create_quality_inspection(
+			reference_type="Delivery Note", reference_name=dn.name, readings=readings, do_not_save=True
+		)
+
+		qa.save()
+
+		# status must be auto set as per formula
+		self.assertEqual(qa.readings[0].status, "Accepted")
+		self.assertEqual(qa.readings[1].status, "Rejected")
+
+		qa.delete()
+		dn.delete()
+
 
 def create_quality_inspection(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 398b3c9..3f6a2c8 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -397,6 +397,7 @@
 				"voucher_type": self.doctype,
 				"voucher_no": self.name,
 				"voucher_detail_no": row.name,
+				"actual_qty": 0,
 				"company": self.company,
 				"stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
 				"is_cancelled": 1 if self.docstatus == 2 else 0,
@@ -423,6 +424,8 @@
 				data.valuation_rate = flt(row.valuation_rate)
 				data.stock_value_difference = -1 * flt(row.amount_difference)
 
+		self.update_inventory_dimensions(row, data)
+
 		return data
 
 	def make_sle_on_cancel(self):
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index b53f429..489ec6e 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -8,6 +8,7 @@
 from frappe import _, throw
 from frappe.model import child_table_fields, default_fields
 from frappe.model.meta import get_field_precision
+from frappe.query_builder.functions import CombineDatetime, IfNull, Sum
 from frappe.utils import add_days, add_months, cint, cstr, flt, getdate
 
 from erpnext import get_company_currency
@@ -526,12 +527,8 @@
 
 	itemwise_barcode = {}
 	for item in items_list:
-		barcodes = frappe.db.sql(
-			"""
-			select barcode from `tabItem Barcode` where parent = %s
-		""",
-			item.item_code,
-			as_dict=1,
+		barcodes = frappe.db.get_all(
+			"Item Barcode", filters={"parent": item.item_code}, fields="barcode"
 		)
 
 		for barcode in barcodes:
@@ -891,34 +888,36 @@
 	:param item_code: str, Item Doctype field item_code
 	"""
 
-	args["item_code"] = item_code
-
-	conditions = """where item_code=%(item_code)s
-		and price_list=%(price_list)s
-		and ifnull(uom, '') in ('', %(uom)s)"""
-
-	conditions += "and ifnull(batch_no, '') in ('', %(batch_no)s)"
+	ip = frappe.qb.DocType("Item Price")
+	query = (
+		frappe.qb.from_(ip)
+		.select(ip.name, ip.price_list_rate, ip.uom)
+		.where(
+			(ip.item_code == item_code)
+			& (ip.price_list == args.get("price_list"))
+			& (IfNull(ip.uom, "").isin(["", args.get("uom")]))
+			& (IfNull(ip.batch_no, "").isin(["", args.get("batch_no")]))
+		)
+		.orderby(ip.valid_from, order=frappe.qb.desc)
+		.orderby(IfNull(ip.batch_no, ""), order=frappe.qb.desc)
+		.orderby(ip.uom, order=frappe.qb.desc)
+	)
 
 	if not ignore_party:
 		if args.get("customer"):
-			conditions += " and customer=%(customer)s"
+			query = query.where(ip.customer == args.get("customer"))
 		elif args.get("supplier"):
-			conditions += " and supplier=%(supplier)s"
+			query = query.where(ip.supplier == args.get("supplier"))
 		else:
-			conditions += "and (customer is null or customer = '') and (supplier is null or supplier = '')"
+			query = query.where((IfNull(ip.customer, "") == "") & (IfNull(ip.supplier, "") == ""))
 
 	if args.get("transaction_date"):
-		conditions += """ and %(transaction_date)s between
-			ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
+		query = query.where(
+			(IfNull(ip.valid_from, "2000-01-01") <= args["transaction_date"])
+			& (IfNull(ip.valid_upto, "2500-12-31") >= args["transaction_date"])
+		)
 
-	return frappe.db.sql(
-		""" select name, price_list_rate, uom
-		from `tabItem Price` {conditions}
-		order by valid_from desc, ifnull(batch_no, '') desc, uom desc """.format(
-			conditions=conditions
-		),
-		args,
-	)
+	return query.run()
 
 
 def get_price_list_rate_for(args, item_code):
@@ -1091,91 +1090,68 @@
 	if not user:
 		user = frappe.session["user"]
 
-	condition = "pfu.user = %(user)s AND pfu.default=1"
-	if user and company:
-		condition = "pfu.user = %(user)s AND pf.company = %(company)s AND pfu.default=1"
+	pf = frappe.qb.DocType("POS Profile")
+	pfu = frappe.qb.DocType("POS Profile User")
 
-	pos_profile = frappe.db.sql(
-		"""SELECT pf.*
-		FROM
-			`tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu
-		ON
-				pf.name = pfu.parent
-		WHERE
-			{cond} AND pf.disabled = 0
-	""".format(
-			cond=condition
-		),
-		{"user": user, "company": company},
-		as_dict=1,
+	query = (
+		frappe.qb.from_(pf)
+		.left_join(pfu)
+		.on(pf.name == pfu.parent)
+		.select(pf.star)
+		.where((pfu.user == user) & (pfu.default == 1))
 	)
 
+	if company:
+		query = query.where(pf.company == company)
+
+	pos_profile = query.run(as_dict=True)
+
 	if not pos_profile and company:
-		pos_profile = frappe.db.sql(
-			"""SELECT pf.*
-			FROM
-				`tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu
-			ON
-					pf.name = pfu.parent
-			WHERE
-				pf.company = %(company)s AND pf.disabled = 0
-		""",
-			{"company": company},
-			as_dict=1,
-		)
+		pos_profile = (
+			frappe.qb.from_(pf)
+			.left_join(pfu)
+			.on(pf.name == pfu.parent)
+			.select(pf.star)
+			.where((pf.company == company) & (pf.disabled == 0))
+		).run(as_dict=True)
 
 	return pos_profile and pos_profile[0] or None
 
 
 def get_serial_nos_by_fifo(args, sales_order=None):
 	if frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo"):
-		return "\n".join(
-			frappe.db.sql_list(
-				"""select name from `tabSerial No`
-			where item_code=%(item_code)s and warehouse=%(warehouse)s and
-			sales_order=IF(%(sales_order)s IS NULL, sales_order, %(sales_order)s)
-			order by timestamp(purchase_date, purchase_time)
-			asc limit %(qty)s""",
-				{
-					"item_code": args.item_code,
-					"warehouse": args.warehouse,
-					"qty": abs(cint(args.stock_qty)),
-					"sales_order": sales_order,
-				},
-			)
+		sn = frappe.qb.DocType("Serial No")
+		query = (
+			frappe.qb.from_(sn)
+			.select(sn.name)
+			.where((sn.item_code == args.item_code) & (sn.warehouse == args.warehouse))
+			.orderby(CombineDatetime(sn.purchase_date, sn.purchase_time))
+			.limit(abs(cint(args.stock_qty)))
 		)
 
+		if sales_order:
+			query = query.where(sn.sales_order == sales_order)
+		if args.batch_no:
+			query = query.where(sn.batch_no == args.batch_no)
 
-def get_serial_no_batchwise(args, sales_order=None):
-	if frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo"):
-		return "\n".join(
-			frappe.db.sql_list(
-				"""select name from `tabSerial No`
-			where item_code=%(item_code)s and warehouse=%(warehouse)s and
-			sales_order=IF(%(sales_order)s IS NULL, sales_order, %(sales_order)s)
-			and batch_no=IF(%(batch_no)s IS NULL, batch_no, %(batch_no)s) order
-			by timestamp(purchase_date, purchase_time) asc limit %(qty)s""",
-				{
-					"item_code": args.item_code,
-					"warehouse": args.warehouse,
-					"batch_no": args.batch_no,
-					"qty": abs(cint(args.stock_qty)),
-					"sales_order": sales_order,
-				},
-			)
-		)
+		serial_nos = query.run(as_list=True)
+		serial_nos = [s[0] for s in serial_nos]
+
+		return "\n".join(serial_nos)
 
 
 @frappe.whitelist()
 def get_conversion_factor(item_code, uom):
 	variant_of = frappe.db.get_value("Item", item_code, "variant_of", cache=True)
 	filters = {"parent": item_code, "uom": uom}
+
 	if variant_of:
 		filters["parent"] = ("in", (item_code, variant_of))
 	conversion_factor = frappe.db.get_value("UOM Conversion Detail", filters, "conversion_factor")
 	if not conversion_factor:
 		stock_uom = frappe.db.get_value("Item", item_code, "stock_uom")
 		conversion_factor = get_uom_conv_factor(uom, stock_uom)
+
 	return {"conversion_factor": conversion_factor or 1.0}
 
 
@@ -1217,12 +1193,16 @@
 
 
 def get_company_total_stock(item_code, company):
-	return frappe.db.sql(
-		"""SELECT sum(actual_qty) from
-		(`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name)
-		WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""",
-		(company, item_code),
-	)[0][0]
+	bin = frappe.qb.DocType("Bin")
+	wh = frappe.qb.DocType("Warehouse")
+
+	return (
+		frappe.qb.from_(bin)
+		.inner_join(wh)
+		.on(bin.warehouse == wh.name)
+		.select(Sum(bin.actual_qty))
+		.where((wh.company == company) & (bin.item_code == item_code))
+	).run()[0][0]
 
 
 @frappe.whitelist()
@@ -1231,6 +1211,7 @@
 		{"item_code": item_code, "warehouse": warehouse, "stock_qty": stock_qty, "serial_no": serial_no}
 	)
 	serial_no = get_serial_no(args)
+
 	return {"serial_no": serial_no}
 
 
@@ -1250,6 +1231,7 @@
 		bin_details_and_serial_nos.update(
 			get_serial_no_details(item_code, warehouse, stock_qty, serial_no)
 		)
+
 	return bin_details_and_serial_nos
 
 
@@ -1264,6 +1246,7 @@
 		)
 		serial_no = get_serial_no(args)
 		batch_qty_and_serial_no.update({"serial_no": serial_no})
+
 	return batch_qty_and_serial_no
 
 
@@ -1336,7 +1319,6 @@
 def apply_price_list_on_item(args):
 	item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1)
 	item_details = get_price_list_rate(args, item_doc)
-
 	item_details.update(get_pricing_rule_for_item(args))
 
 	return item_details
@@ -1420,12 +1402,12 @@
 		) or {"valuation_rate": 0}
 
 	elif not item.get("is_stock_item"):
-		valuation_rate = frappe.db.sql(
-			"""select sum(base_net_amount) / sum(qty*conversion_factor)
-			from `tabPurchase Invoice Item`
-			where item_code = %s and docstatus=1""",
-			item_code,
-		)
+		pi_item = frappe.qb.DocType("Purchase Invoice Item")
+		valuation_rate = (
+			frappe.qb.from_(pi_item)
+			.select((Sum(pi_item.base_net_amount) / Sum(pi_item.qty * pi_item.conversion_factor)))
+			.where((pi_item.docstatus == 1) & (pi_item.item_code == item_code))
+		).run()
 
 		if valuation_rate:
 			return {"valuation_rate": valuation_rate[0][0] or 0.0}
@@ -1451,7 +1433,7 @@
 	if args.get("warehouse") and args.get("stock_qty") and args.get("item_code"):
 		has_serial_no = frappe.get_value("Item", {"item_code": args.item_code}, "has_serial_no")
 		if args.get("batch_no") and has_serial_no == 1:
-			return get_serial_no_batchwise(args, sales_order)
+			return get_serial_nos_by_fifo(args, sales_order)
 		elif has_serial_no == 1:
 			args = json.dumps(
 				{
@@ -1483,31 +1465,35 @@
 		args = frappe._dict(json.loads(args))
 
 	blanket_order_details = None
-	condition = ""
-	if args.item_code:
-		if args.customer and args.doctype == "Sales Order":
-			condition = " and bo.customer=%(customer)s"
-		elif args.supplier and args.doctype == "Purchase Order":
-			condition = " and bo.supplier=%(supplier)s"
-		if args.blanket_order:
-			condition += " and bo.name =%(blanket_order)s"
-		if args.transaction_date:
-			condition += " and bo.to_date>=%(transaction_date)s"
 
-		blanket_order_details = frappe.db.sql(
-			"""
-				select boi.rate as blanket_order_rate, bo.name as blanket_order
-				from `tabBlanket Order` bo, `tabBlanket Order Item` boi
-				where bo.company=%(company)s and boi.item_code=%(item_code)s
-					and bo.docstatus=1 and bo.name = boi.parent {0}
-			""".format(
-				condition
-			),
-			args,
-			as_dict=True,
+	if args.item_code:
+		bo = frappe.qb.DocType("Blanket Order")
+		bo_item = frappe.qb.DocType("Blanket Order Item")
+
+		query = (
+			frappe.qb.from_(bo)
+			.from_(bo_item)
+			.select(bo_item.rate.as_("blanket_order_rate"), bo.name.as_("blanket_order"))
+			.where(
+				(bo.company == args.company)
+				& (bo_item.item_code == args.item_code)
+				& (bo.docstatus == 1)
+				& (bo.name == bo_item.parent)
+			)
 		)
 
+		if args.customer and args.doctype == "Sales Order":
+			query = query.where(bo.customer == args.customer)
+		elif args.supplier and args.doctype == "Purchase Order":
+			query = query.where(bo.supplier == args.supplier)
+		if args.blanket_order:
+			query = query.where(bo.name == args.blanket_order)
+		if args.transaction_date:
+			query = query.where(bo.to_date >= args.transaction_date)
+
+		blanket_order_details = query.run(as_dict=True)
 		blanket_order_details = blanket_order_details[0] if blanket_order_details else ""
+
 	return blanket_order_details
 
 
@@ -1517,10 +1503,10 @@
 		if get_reserved_qty_for_so(args.get("against_sales_order"), args.get("item_code")):
 			reserved_so = args.get("against_sales_order")
 	elif args.get("against_sales_invoice"):
-		sales_order = frappe.db.sql(
-			"""select sales_order from `tabSales Invoice Item` where
-		parent=%s and item_code=%s""",
-			(args.get("against_sales_invoice"), args.get("item_code")),
+		sales_order = frappe.db.get_all(
+			"Sales Invoice Item",
+			filters={"parent": args.get("against_sales_invoice"), "item_code": args.get("item_code")},
+			fields="sales_order",
 		)
 		if sales_order and sales_order[0]:
 			if get_reserved_qty_for_so(sales_order[0][0], args.get("item_code")):
@@ -1532,13 +1518,14 @@
 
 
 def get_reserved_qty_for_so(sales_order, item_code):
-	reserved_qty = frappe.db.sql(
-		"""select sum(qty) from `tabSales Order Item`
-	where parent=%s and item_code=%s and ensure_delivery_based_on_produced_serial_no=1
-	""",
-		(sales_order, item_code),
+	reserved_qty = frappe.db.get_value(
+		"Sales Order Item",
+		filters={
+			"parent": sales_order,
+			"item_code": item_code,
+			"ensure_delivery_based_on_produced_serial_no": 1,
+		},
+		fieldname="sum(qty)",
 	)
-	if reserved_qty and reserved_qty[0][0]:
-		return reserved_qty[0][0]
-	else:
-		return 0
+
+	return reserved_qty or 0
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index f4fd4de..95dbc83 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -191,14 +191,17 @@
 
 	def validate_available_qty_for_consumption(self):
 		for item in self.get("supplied_items"):
+			precision = item.precision("consumed_qty")
 			if (
-				item.available_qty_for_consumption and item.available_qty_for_consumption < item.consumed_qty
+				item.available_qty_for_consumption
+				and flt(item.available_qty_for_consumption, precision) - flt(item.consumed_qty, precision) < 0
 			):
-				frappe.throw(
-					_(
-						"Row {0}: Consumed Qty must be less than or equal to Available Qty For Consumption in Consumed Items Table."
-					).format(item.idx)
-				)
+				msg = f"""Row {item.idx}: Consumed Qty {flt(item.consumed_qty, precision)}
+					must be less than or equal to Available Qty For Consumption
+					{flt(item.available_qty_for_consumption, precision)}
+					in Consumed Items Table."""
+
+				frappe.throw(_(msg))
 
 	def validate_items_qty(self):
 		for item in self.items:
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index 7f4e9ef..2a078c4 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -13,8 +13,8 @@
 	get_datetime,
 	get_datetime_str,
 	get_link_to_form,
+	get_system_timezone,
 	get_time,
-	get_time_zone,
 	get_weekdays,
 	getdate,
 	nowdate,
@@ -981,7 +981,7 @@
 
 
 def get_tz(user):
-	return frappe.db.get_value("User", user, "time_zone") or get_time_zone()
+	return frappe.db.get_value("User", user, "time_zone") or get_system_timezone()
 
 
 @frappe.whitelist()
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 5a0a863..bec3ce2 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -9916,3 +9916,5 @@
 Delivered at Place,Geliefert benannter Ort,
 Delivered at Place Unloaded,Geliefert benannter Ort entladen,
 Delivered Duty Paid,Geliefert verzollt,
+Discount Validity,Frist für den Rabatt,
+Discount Validity Based On,Frist für den Rabatt berechnet sich nach,
diff --git a/erpnext/utilities/doctype/video/video.py b/erpnext/utilities/doctype/video/video.py
index 13b7877..62033a5 100644
--- a/erpnext/utilities/doctype/video/video.py
+++ b/erpnext/utilities/doctype/video/video.py
@@ -10,6 +10,7 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils import cint
+from frappe.utils.data import get_system_timezone
 from pyyoutube import Api
 
 
@@ -64,7 +65,7 @@
 
 	frequency = get_frequency(frequency)
 	time = datetime.now()
-	timezone = pytz.timezone(frappe.utils.get_time_zone())
+	timezone = pytz.timezone(get_system_timezone())
 	site_time = time.astimezone(timezone)
 
 	if frequency == 30:
diff --git a/erpnext/www/book_appointment/index.py b/erpnext/www/book_appointment/index.py
index dfca946..f50c207 100644
--- a/erpnext/www/book_appointment/index.py
+++ b/erpnext/www/book_appointment/index.py
@@ -4,6 +4,7 @@
 import frappe
 import pytz
 from frappe import _
+from frappe.utils.data import get_system_timezone
 
 WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
 
@@ -125,7 +126,7 @@
 
 def convert_to_guest_timezone(guest_tz, datetimeobject):
 	guest_tz = pytz.timezone(guest_tz)
-	local_timezone = pytz.timezone(frappe.utils.get_time_zone())
+	local_timezone = pytz.timezone(get_system_timezone())
 	datetimeobject = local_timezone.localize(datetimeobject)
 	datetimeobject = datetimeobject.astimezone(guest_tz)
 	return datetimeobject
@@ -134,7 +135,7 @@
 def convert_to_system_timezone(guest_tz, datetimeobject):
 	guest_tz = pytz.timezone(guest_tz)
 	datetimeobject = guest_tz.localize(datetimeobject)
-	system_tz = pytz.timezone(frappe.utils.get_time_zone())
+	system_tz = pytz.timezone(get_system_timezone())
 	datetimeobject = datetimeobject.astimezone(system_tz)
 	return datetimeobject
 
diff --git a/pyproject.toml b/pyproject.toml
index 1c93eed..0718e5b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -28,9 +28,6 @@
 requires = ["flit_core >=3.4,<4"]
 build-backend = "flit_core.buildapi"
 
-[tool.bench.dev-dependencies]
-hypothesis = "~=6.31.0"
-
 [tool.black]
 line-length = 99