Merge branch 'develop' into fix-minor-custom-cash-flow
diff --git a/.flake8 b/.flake8
index 5735456..4ff8840 100644
--- a/.flake8
+++ b/.flake8
@@ -29,6 +29,8 @@
     B950,
     W191,
     E124, # closing bracket, irritating while writing QB code
+    E131, # continuation line unaligned for hanging indent
+    E123, # closing bracket does not match indentation of opening bracket's line
 
 max-line-length = 200
 exclude=.github/helper/semgrep_rules
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
index 88aa7ef..2bdaa10 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
@@ -134,7 +134,8 @@
   {
    "fieldname": "allocated_amount",
    "fieldtype": "Currency",
-   "label": "Allocated Amount"
+   "label": "Allocated Amount",
+   "options": "currency"
   },
   {
    "fieldname": "amended_from",
@@ -152,7 +153,8 @@
   {
    "fieldname": "unallocated_amount",
    "fieldtype": "Currency",
-   "label": "Unallocated Amount"
+   "label": "Unallocated Amount",
+   "options": "currency"
   },
   {
    "fieldname": "party_section",
@@ -192,10 +194,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-14 17:31:58.963529",
+ "modified": "2022-03-21 19:05:04.208222",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Bank Transaction",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -242,6 +245,7 @@
  ],
  "sort_field": "date",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "bank_account",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json
index 5c19091..ed8ff7c 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json
@@ -110,13 +110,12 @@
    "description": "Reference number of the invoice from the previous system",
    "fieldname": "invoice_number",
    "fieldtype": "Data",
-   "in_list_view": 1,
    "label": "Invoice Number"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2021-12-17 19:25:06.053187",
+ "modified": "2022-03-21 19:31:45.382656",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Opening Invoice Creation Tool Item",
@@ -125,5 +124,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index 0c6e7ed..b850027 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -264,7 +264,6 @@
    "print_hide": 1
   },
   {
-   "allow_on_submit": 1,
    "default": "0",
    "fieldname": "is_return",
    "fieldtype": "Check",
@@ -1573,7 +1572,7 @@
  "icon": "fa fa-file-text",
  "is_submittable": 1,
  "links": [],
- "modified": "2021-10-05 12:11:53.871828",
+ "modified": "2022-03-22 13:00:24.166684",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Invoice",
@@ -1623,6 +1622,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "timeline_field": "customer",
  "title_field": "title",
  "track_changes": 1,
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 91c07ad..275eeb3 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -16,7 +16,11 @@
 )
 from erpnext.accounts.party import get_due_date, get_party_account
 from erpnext.stock.doctype.batch.batch import get_batch_qty, get_pos_reserved_batch_qty
-from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos, get_serial_nos
+from erpnext.stock.doctype.serial_no.serial_no import (
+	get_delivered_serial_nos,
+	get_pos_reserved_serial_nos,
+	get_serial_nos,
+)
 
 
 class POSInvoice(SalesInvoice):
@@ -145,12 +149,7 @@
 						.format(item.idx, bold_invalid_batch_no, bold_item_name, bold_extra_batch_qty_needed), title=_("Item Unavailable"))
 
 	def validate_delivered_serial_nos(self, item):
-		serial_nos = get_serial_nos(item.serial_no)
-		delivered_serial_nos = frappe.db.get_list('Serial No', {
-			'item_code': item.item_code,
-			'name': ['in', serial_nos],
-			'sales_invoice': ['is', 'set']
-		}, pluck='name')
+		delivered_serial_nos = get_delivered_serial_nos(item.serial_no)
 
 		if delivered_serial_nos:
 			bold_delivered_serial_nos = frappe.bold(', '.join(delivered_serial_nos))
@@ -172,10 +171,14 @@
 			frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
 
 	def validate_stock_availablility(self):
+		if self.is_return:
+			return
+
+		if self.docstatus.is_draft() and not frappe.db.get_value('POS Profile', self.pos_profile, 'validate_stock_on_save'):
+			return
+
 		from erpnext.stock.stock_ledger import is_negative_stock_allowed
 
-		if self.is_return or self.docstatus != 1:
-			return
 		for d in self.get('items'):
 			is_service_item = not (frappe.db.get_value('Item', d.get('item_code'), 'is_stock_item'))
 			if is_service_item:
@@ -485,16 +488,15 @@
 			"payment_account": pay.account,
 		}, ["name"])
 
-		args = {
-			'doctype': 'Payment Request',
+		filters = {
 			'reference_doctype': 'POS Invoice',
 			'reference_name': self.name,
 			'payment_gateway_account': payment_gateway_account,
 			'email_to': self.contact_mobile
 		}
-		pr = frappe.db.exists(args)
+		pr = frappe.db.get_value('Payment Request', filters=filters)
 		if pr:
-			return frappe.get_doc('Payment Request', pr[0][0])
+			return frappe.get_doc('Payment Request', pr)
 
 @frappe.whitelist()
 def get_stock_availability(item_code, warehouse):
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index cf8affd..e06f7aa 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -340,6 +340,7 @@
 			item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
 
 		si.get("items")[0].serial_no = serial_nos[0]
+		si.update_stock = 1
 		si.insert()
 		si.submit()
 
@@ -610,6 +611,78 @@
 			pos_inv.delete()
 			pr.delete()
 
+	def test_delivered_serial_no_case(self):
+		from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import (
+			init_user_and_profile,
+		)
+		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+		from erpnext.stock.doctype.serial_no.test_serial_no import get_serial_nos
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+
+		frappe.db.savepoint('before_test_delivered_serial_no_case')
+		try:
+			se = make_serialized_item()
+			serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
+
+			dn = create_delivery_note(
+					item_code="_Test Serialized Item With Series", serial_no=serial_no
+			)
+
+			delivery_document_no = frappe.db.get_value("Serial No", serial_no, "delivery_document_no")
+			self.assertEquals(delivery_document_no, dn.name)
+
+			init_user_and_profile()
+
+			pos_inv = create_pos_invoice(
+					item_code="_Test Serialized Item With Series",
+					serial_no=serial_no,
+					qty=1,
+					rate=100,
+					do_not_submit=True
+			)
+
+			self.assertRaises(frappe.ValidationError, pos_inv.submit)
+
+		finally:
+			frappe.db.rollback(save_point='before_test_delivered_serial_no_case')
+			frappe.set_user("Administrator")
+
+	def test_returned_serial_no_case(self):
+		from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import (
+			init_user_and_profile,
+		)
+		from erpnext.stock.doctype.serial_no.serial_no import get_pos_reserved_serial_nos
+		from erpnext.stock.doctype.serial_no.test_serial_no import get_serial_nos
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+
+		frappe.db.savepoint('before_test_returned_serial_no_case')
+		try:
+			se = make_serialized_item()
+			serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
+
+			init_user_and_profile()
+
+			pos_inv = create_pos_invoice(
+					item_code="_Test Serialized Item With Series",
+					serial_no=serial_no,
+					qty=1,
+					rate=100,
+			)
+
+			pos_return = make_sales_return(pos_inv.name)
+			pos_return.flags.ignore_validate = True
+			pos_return.insert()
+			pos_return.submit()
+
+			pos_reserved_serial_nos = get_pos_reserved_serial_nos({
+				'item_code': '_Test Serialized Item With Series',
+				'warehouse': '_Test Warehouse - _TC'
+			})
+			self.assertTrue(serial_no not in pos_reserved_serial_nos)
+
+		finally:
+			frappe.db.rollback(save_point='before_test_returned_serial_no_case')
+			frappe.set_user("Administrator")
 
 def create_pos_invoice(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index d4513c6..b3d9c15 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -53,7 +53,7 @@
 					frappe.throw(msg)
 
 	def on_submit(self):
-		pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
+		pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
 
 		returns = [d for d in pos_invoice_docs if d.get('is_return') == 1]
 		sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
@@ -70,7 +70,7 @@
 		self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
 
 	def on_cancel(self):
-		pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
+		pos_invoice_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
 
 		self.update_pos_invoices(pos_invoice_docs)
 		self.cancel_linked_invoices()
@@ -254,7 +254,7 @@
 		'docstatus': 1
 	}
 	pos_invoices = frappe.db.get_all('POS Invoice', filters=filters,
-		fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer'])
+		fields=["name as pos_invoice", 'posting_date', 'grand_total', 'customer', 'is_return', 'return_against'])
 
 	return pos_invoices
 
@@ -294,17 +294,62 @@
 	else:
 		cancel_merge_logs(merge_logs, closing_entry)
 
+def split_invoices(invoices):
+	'''
+	Splits invoices into multiple groups
+	Use-case:
+	If a serial no is sold and later it is returned
+	then split the invoices such that the selling entry is merged first and then the return entry
+	'''
+	# Input
+	# invoices = [
+	# 	{'pos_invoice': 'Invoice with SR#1 & SR#2', 'is_return': 0},
+	# 	{'pos_invoice': 'Invoice with SR#1', 'is_return': 1},
+	# 	{'pos_invoice': 'Invoice with SR#2', 'is_return': 0}
+	# ]
+	# Output
+	# _invoices = [
+	# 	[{'pos_invoice': 'Invoice with SR#1 & SR#2', 'is_return': 0}],
+	# 	[{'pos_invoice': 'Invoice with SR#1', 'is_return': 1}, {'pos_invoice': 'Invoice with SR#2', 'is_return': 0}],
+	# ]
+
+	_invoices = []
+	special_invoices = []
+	pos_return_docs = [frappe.get_cached_doc("POS Invoice", d.pos_invoice) for d in invoices if d.is_return and d.return_against]
+	for pos_invoice in pos_return_docs:
+		for item in pos_invoice.items:
+			if not item.serial_no:
+				continue
+
+			return_against_is_added = any(d for d in _invoices if d.pos_invoice == pos_invoice.return_against)
+			if return_against_is_added:
+				break
+
+			return_against_is_consolidated = frappe.db.get_value('POS Invoice', pos_invoice.return_against, 'status', cache=True) == 'Consolidated'
+			if return_against_is_consolidated:
+				break
+
+			pos_invoice_row = [d for d in invoices if d.pos_invoice == pos_invoice.return_against]
+			_invoices.append(pos_invoice_row)
+			special_invoices.append(pos_invoice.return_against)
+			break
+
+	_invoices.append([d for d in invoices if d.pos_invoice not in special_invoices])
+
+	return _invoices
+
 def create_merge_logs(invoice_by_customer, closing_entry=None):
 	try:
 		for customer, invoices in invoice_by_customer.items():
-			merge_log = frappe.new_doc('POS Invoice Merge Log')
-			merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
-			merge_log.customer = customer
-			merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None
+			for _invoices in split_invoices(invoices):
+				merge_log = frappe.new_doc('POS Invoice Merge Log')
+				merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
+				merge_log.customer = customer
+				merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None
 
-			merge_log.set('pos_invoices', invoices)
-			merge_log.save(ignore_permissions=True)
-			merge_log.submit()
+				merge_log.set('pos_invoices', _invoices)
+				merge_log.save(ignore_permissions=True)
+				merge_log.submit()
 
 		if closing_entry:
 			closing_entry.set_status(update=True, status='Submitted')
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
index 8909da9..fe57ce2 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -385,4 +385,66 @@
 		finally:
 			frappe.set_user("Administrator")
 			frappe.db.sql("delete from `tabPOS Profile`")
+			frappe.db.sql("delete from `tabPOS Invoice`")
+
+	def test_serial_no_case_1(self):
+		'''
+		Create a POS Invoice with serial no
+		Create a Return Invoice with serial no
+		Create a POS Invoice with serial no again
+		Consolidate the invoices
+
+		The first POS Invoice should be consolidated with a separate single Merge Log
+		The second and third POS Invoice should be consolidated with a single Merge Log
+		'''
+
+		from erpnext.stock.doctype.serial_no.test_serial_no import get_serial_nos
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+
+		frappe.db.sql("delete from `tabPOS Invoice`")
+
+		try:
+			se = make_serialized_item()
+			serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
+
+			init_user_and_profile()
+
+			pos_inv = create_pos_invoice(
+					item_code="_Test Serialized Item With Series",
+					serial_no=serial_no,
+					qty=1,
+					rate=100,
+					do_not_submit=1
+			)
+			pos_inv.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 100
+			})
+			pos_inv.submit()
+
+			pos_inv_cn = make_sales_return(pos_inv.name)
+			pos_inv_cn.paid_amount = -100
+			pos_inv_cn.submit()
+
+			pos_inv2 = create_pos_invoice(
+					item_code="_Test Serialized Item With Series",
+					serial_no=serial_no,
+					qty=1,
+					rate=100,
+					do_not_submit=1
+			)
+			pos_inv2.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 100
+			})
+			pos_inv2.submit()
+
+			consolidate_pos_invoices()
+
+			pos_inv.load_from_db()
+			pos_inv2.load_from_db()
+
+			self.assertNotEqual(pos_inv.consolidated_invoice, pos_inv2.consolidated_invoice)
+
+		finally:
+			frappe.set_user("Administrator")
+			frappe.db.sql("delete from `tabPOS Profile`")
 			frappe.db.sql("delete from `tabPOS Invoice`")
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json b/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json
index 205c4ed..387c4b0 100644
--- a/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json
+++ b/erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json
@@ -9,7 +9,9 @@
   "posting_date",
   "column_break_3",
   "customer",
-  "grand_total"
+  "grand_total",
+  "is_return",
+  "return_against"
  ],
  "fields": [
   {
@@ -48,11 +50,27 @@
    "in_list_view": 1,
    "label": "Amount",
    "reqd": 1
+  },
+  {
+   "default": "0",
+   "fetch_from": "pos_invoice.is_return",
+   "fieldname": "is_return",
+   "fieldtype": "Check",
+   "label": "Is Return",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "pos_invoice.return_against",
+   "fieldname": "return_against",
+   "fieldtype": "Link",
+   "label": "Return Against",
+   "options": "POS Invoice",
+   "read_only": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-05-29 15:08:42.194979",
+ "modified": "2022-03-24 13:32:02.366257",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Invoice Reference",
@@ -61,5 +79,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json
index 9c9f37b..11646a6 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.json
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json
@@ -22,6 +22,7 @@
   "hide_images",
   "hide_unavailable_items",
   "auto_add_item_to_cart",
+  "validate_stock_on_save",
   "column_break_16",
   "update_stock",
   "ignore_pricing_rule",
@@ -351,6 +352,12 @@
   {
    "fieldname": "column_break_25",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "validate_stock_on_save",
+   "fieldtype": "Check",
+   "label": "Validate Stock on Save"
   }
  ],
  "icon": "icon-cog",
@@ -378,10 +385,11 @@
    "link_fieldname": "pos_profile"
   }
  ],
- "modified": "2021-10-14 14:17:00.469298",
+ "modified": "2022-03-21 13:29:28.480533",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Profile",
+ "naming_rule": "Set by user",
  "owner": "Administrator",
  "permissions": [
   {
@@ -404,5 +412,6 @@
   }
  ],
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index e107912..9433489 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1711,6 +1711,7 @@
 		}
 	}, target_doc, set_missing_values)
 
+	doclist.set_onload('ignore_price_list', True)
 	return doclist
 
 @frappe.whitelist()
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 2c66542..f93f9fe 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -442,6 +442,8 @@
 		}
 	}, target_doc, set_missing_values)
 
+	doc.set_onload('ignore_price_list', True)
+
 	return doc
 
 @frappe.whitelist()
@@ -509,6 +511,7 @@
 
 	doc = get_mapped_doc("Purchase Order", source_name,	fields,
 		target_doc, postprocess, ignore_permissions=ignore_permissions)
+	doc.set_onload('ignore_price_list', True)
 
 	return doc
 
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index 81fc324..d578863 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -139,6 +139,7 @@
 		},
 	}, target_doc, set_missing_values)
 
+	doclist.set_onload('ignore_price_list', True)
 	return doclist
 
 @frappe.whitelist()
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index dd9b45c..d870823 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -168,7 +168,7 @@
 				{account_type_condition}
 				AND is_group = 0
 				AND company = %(company)s
-				AND account_currency = %(currency)s
+				AND (account_currency = %(currency)s or ifnull(account_currency, '') = '')
 				AND `{searchfield}` LIKE %(txt)s
 				{mcond}
 			ORDER BY idx DESC, name
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 2166633..d6296eb 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -399,6 +399,8 @@
 		}
 	}, target_doc, set_missing_values)
 
+	doclist.set_onload('ignore_price_list', True)
+
 	return doclist
 
 def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None,
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 19acc10..29da5f1 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -114,20 +114,16 @@
 			for item in self.doc.get("items"):
 				self.doc.round_floats_in(item)
 
-				if not item.rate:
-					item.rate = item.price_list_rate
-
 				if item.discount_percentage == 100:
 					item.rate = 0.0
 				elif item.price_list_rate:
-					if item.pricing_rules or abs(item.discount_percentage) > 0:
+					if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
 						item.rate = flt(item.price_list_rate *
 							(1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
 
-						if abs(item.discount_percentage) > 0:
-							item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
+						item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
 
-					elif item.discount_amount or item.pricing_rules:
+					elif item.discount_amount and item.pricing_rules:
 						item.rate =  item.price_list_rate - item.discount_amount
 
 				if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item',
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 20fb987..88a9c10 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -225,9 +225,7 @@
 
 
 def _get_employee_from_user(user):
-	employee_docname = frappe.db.exists(
-		{'doctype': 'Employee', 'user_id': user})
+	employee_docname = frappe.db.get_value('Employee', {'user_id': user})
 	if employee_docname:
-		# frappe.db.exists returns a tuple of a tuple
-		return frappe.get_doc('Employee', employee_docname[0][0])
+		return frappe.get_doc('Employee', employee_docname)
 	return None
diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py
index f4086dc..776e604 100644
--- a/erpnext/crm/doctype/appointment/test_appointment.py
+++ b/erpnext/crm/doctype/appointment/test_appointment.py
@@ -8,50 +8,44 @@
 
 
 def create_test_lead():
-    test_lead = frappe.db.exists({'doctype': 'Lead', 'email_id':'test@example.com'})
-    if test_lead:
-        return frappe.get_doc('Lead', test_lead[0][0])
-    test_lead = frappe.get_doc({
-        'doctype': 'Lead',
-        'lead_name': 'Test Lead',
-        'email_id': 'test@example.com'
-    })
-    test_lead.insert(ignore_permissions=True)
-    return test_lead
+	test_lead = frappe.db.get_value("Lead", {"email_id": "test@example.com"})
+	if test_lead:
+		return frappe.get_doc("Lead", test_lead)
+	test_lead = frappe.get_doc(
+		{"doctype": "Lead", "lead_name": "Test Lead", "email_id": "test@example.com"}
+	)
+	test_lead.insert(ignore_permissions=True)
+	return test_lead
 
 
 def create_test_appointments():
-    test_appointment = frappe.db.exists(
-        {'doctype': 'Appointment', 'scheduled_time':datetime.datetime.now(),'email':'test@example.com'})
-    if test_appointment:
-        return frappe.get_doc('Appointment', test_appointment[0][0])
-    test_appointment = frappe.get_doc({
-        'doctype': 'Appointment',
-        'email': 'test@example.com',
-        'status': 'Open',
-        'customer_name': 'Test Lead',
-        'customer_phone_number': '666',
-        'customer_skype': 'test',
-        'customer_email': 'test@example.com',
-        'scheduled_time': datetime.datetime.now()
-    })
-    test_appointment.insert()
-    return test_appointment
+	test_appointment = frappe.get_doc(
+		{
+			"doctype": "Appointment",
+			"email": "test@example.com",
+			"status": "Open",
+			"customer_name": "Test Lead",
+			"customer_phone_number": "666",
+			"customer_skype": "test",
+			"customer_email": "test@example.com",
+			"scheduled_time": datetime.datetime.now(),
+		}
+	)
+	test_appointment.insert()
+	return test_appointment
 
 
 class TestAppointment(unittest.TestCase):
-    test_appointment = test_lead = None
+	test_appointment = test_lead = None
 
-    def setUp(self):
-        self.test_lead = create_test_lead()
-        self.test_appointment = create_test_appointments()
+	def setUp(self):
+		self.test_lead = create_test_lead()
+		self.test_appointment = create_test_appointments()
 
-    def test_calendar_event_created(self):
-        cal_event = frappe.get_doc(
-            'Event', self.test_appointment.calendar_event)
-        self.assertEqual(cal_event.starts_on,
-                         self.test_appointment.scheduled_time)
+	def test_calendar_event_created(self):
+		cal_event = frappe.get_doc("Event", self.test_appointment.calendar_event)
+		self.assertEqual(cal_event.starts_on, self.test_appointment.scheduled_time)
 
-    def test_lead_linked(self):
-        lead = frappe.get_doc('Lead', self.test_lead.name)
-        self.assertIsNotNone(lead)
+	def test_lead_linked(self):
+		lead = frappe.get_doc("Lead", self.test_lead.name)
+		self.assertIsNotNone(lead)
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js
index 741e78f..7108cab 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.js
+++ b/erpnext/e_commerce/doctype/website_item/website_item.js
@@ -5,6 +5,12 @@
 	onload: function(frm) {
 		// should never check Private
 		frm.fields_dict["website_image"].df.is_private = 0;
+
+		frm.set_query("website_warehouse", () => {
+			return {
+				filters: {"is_group": 0}
+			};
+		});
 	},
 
 	image: function() {
diff --git a/erpnext/e_commerce/product_ui/views.js b/erpnext/e_commerce/product_ui/views.js
index 6dce79d..fb63b21 100644
--- a/erpnext/e_commerce/product_ui/views.js
+++ b/erpnext/e_commerce/product_ui/views.js
@@ -418,6 +418,22 @@
 
 			me.change_route_with_filters();
 		});
+
+		// bind filter lookup input box
+		$('.filter-lookup-input').on('keydown', frappe.utils.debounce((e) => {
+			const $input = $(e.target);
+			const keyword = ($input.val() || '').toLowerCase();
+			const $filter_options = $input.next('.filter-options');
+
+			$filter_options.find('.filter-lookup-wrapper').show();
+			$filter_options.find('.filter-lookup-wrapper').each((i, el) => {
+				const $el = $(el);
+				const value = $el.data('value').toLowerCase();
+				if (!value.includes(keyword)) {
+					$el.hide();
+				}
+			});
+		}, 300));
 	}
 
 	change_route_with_filters() {
diff --git a/erpnext/e_commerce/variant_selector/test_variant_selector.py b/erpnext/e_commerce/variant_selector/test_variant_selector.py
index ee098e1..3eb75cb 100644
--- a/erpnext/e_commerce/variant_selector/test_variant_selector.py
+++ b/erpnext/e_commerce/variant_selector/test_variant_selector.py
@@ -15,6 +15,7 @@
 
 	@classmethod
 	def setUpClass(cls):
+		super().setUpClass()
 		template_item = make_item("Test-Tshirt-Temp", {
 			"has_variant": 1,
 			"variant_based_on": "Item Attribute",
diff --git a/erpnext/education/setup.py b/erpnext/education/setup.py
index b716926..663f1ca 100644
--- a/erpnext/education/setup.py
+++ b/erpnext/education/setup.py
@@ -3,7 +3,6 @@
 
 
 import frappe
-from erpnext.setup.utils import insert_record
 
 
 def setup_education():
@@ -13,6 +12,21 @@
 		return
 	create_academic_sessions()
 
+
+def insert_record(records):
+	for r in records:
+		doc = frappe.new_doc(r.get("doctype"))
+		doc.update(r)
+		try:
+			doc.insert(ignore_permissions=True)
+		except frappe.DuplicateEntryError as e:
+			# pass DuplicateEntryError and continue
+			if e.args and e.args[0]==doc.doctype and e.args[1]==doc.name:
+				# make sure DuplicateEntryError is for the exact same doc and not a related doc
+				pass
+			else:
+				raise
+
 def create_academic_sessions():
 	data = [
 		{"doctype": "Academic Year", "academic_year_name": "2015-16"},
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 7d32fd8..3a30990 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -25,6 +25,7 @@
 	LeaveDayBlockedError,
 	NotAnOptionalHoliday,
 	OverlapError,
+	get_leave_allocation_records,
 	get_leave_balance_on,
 	get_leave_details,
 )
@@ -882,6 +883,27 @@
 		self.assertEqual(leave_allocation['leaves_pending_approval'], 1)
 		self.assertEqual(leave_allocation['remaining_leaves'], 26)
 
+	@set_holiday_list('Salary Slip Test Holiday List', '_Test Company')
+	def test_get_leave_allocation_records(self):
+		employee = get_employee()
+		leave_type = create_leave_type(
+			leave_type_name="_Test_CF_leave_expiry",
+			is_carry_forward=1,
+			expire_carry_forwarded_leaves_after_days=90)
+		leave_type.insert()
+
+		leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
+		details = get_leave_allocation_records(employee.name, getdate(), leave_type.name)
+		expected_data = {
+			"from_date": getdate(leave_alloc.from_date),
+			"to_date": getdate(leave_alloc.to_date),
+			"total_leaves_allocated": 30.0,
+			"unused_leaves": 15.0,
+			"new_leaves_allocated": 15.0,
+			"leave_type": leave_type.name
+		}
+		self.assertEqual(details.get(leave_type.name), expected_data)
+
 
 def create_carry_forwarded_allocation(employee, leave_type):
 		# initial leave allocation
@@ -903,6 +925,8 @@
 			carry_forward=1)
 		leave_allocation.submit()
 
+		return leave_allocation
+
 def make_allocation_record(employee=None, leave_type=None, from_date=None, to_date=None, carry_forward=False, leaves=None):
 	allocation = frappe.get_doc({
 		"doctype": "Leave Allocation",
@@ -931,12 +955,9 @@
 	dept_doc.save(ignore_permissions=True)
 
 def get_leave_period():
-	leave_period_name = frappe.db.exists({
-		"doctype": "Leave Period",
-		"company": "_Test Company"
-	})
+	leave_period_name = frappe.db.get_value("Leave Period", {"company": "_Test Company"})
 	if leave_period_name:
-		return frappe.get_doc("Leave Period", leave_period_name[0][0])
+		return frappe.get_doc("Leave Period", leave_period_name)
 	else:
 		return frappe.get_doc(dict(
 				name = 'Test Leave Period',
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 5f492d7..960d0e5 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -500,6 +500,9 @@
 			2: "Cancelled"
 		}[self.docstatus or 0]
 
+		if self.for_quantity <= self.transferred_qty:
+			self.status = 'Material Transferred'
+
 		if self.time_logs:
 			self.status = 'Work In Progress'
 
@@ -507,10 +510,6 @@
 			(self.for_quantity <= self.total_completed_qty or not self.items)):
 			self.status = 'Completed'
 
-		if self.status != 'Completed':
-			if self.for_quantity <= self.transferred_qty:
-				self.status = 'Material Transferred'
-
 		if update_status:
 			self.db_set('status', self.status)
 
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index 33425d2..c5841c1 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -169,6 +169,7 @@
 
 		job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name})
 		job_card = frappe.get_doc("Job Card", job_card_name)
+		self.assertEqual(job_card.status, "Open")
 
 		# fully transfer both RMs
 		transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index e8759f5..59ddf1f 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -2,6 +2,13 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Production Plan', {
+
+	before_save: function(frm) {
+		// preserve temporary names on production plan item to re-link sub-assembly items
+		frm.doc.po_items.forEach(item => {
+			item.temporary_name = item.name;
+		});
+	},
 	setup: function(frm) {
 		frm.custom_make_buttons = {
 			'Work Order': 'Work Order / Subcontract PO',
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 2b6e696..349f40e 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -32,6 +32,7 @@
 		self.set_pending_qty_in_row_without_reference()
 		self.calculate_total_planned_qty()
 		self.set_status()
+		self._rename_temporary_references()
 
 	def set_pending_qty_in_row_without_reference(self):
 		"Set Pending Qty in independent rows (not from SO or MR)."
@@ -57,6 +58,18 @@
 			if not flt(d.planned_qty):
 				frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx))
 
+	def _rename_temporary_references(self):
+		""" po_items and sub_assembly_items items are both constructed client side without saving.
+
+			Attempt to fix linkages by using temporary names to map final row names.
+		"""
+		new_name_map = {d.temporary_name: d.name for d in self.po_items if d.temporary_name}
+		actual_names = {d.name for d in self.po_items}
+
+		for sub_assy in self.sub_assembly_items:
+			if sub_assy.production_plan_item not in actual_names:
+				sub_assy.production_plan_item = new_name_map.get(sub_assy.production_plan_item)
+
 	@frappe.whitelist()
 	def get_open_sales_orders(self):
 		""" Pull sales orders  which are pending to deliver based on criteria selected"""
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 6425374..ec49703 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -667,6 +667,39 @@
 		wo_doc.submit()
 		self.assertEqual(wo_doc.qty, 0.55)
 
+	def test_temporary_name_relinking(self):
+
+		pp = frappe.new_doc("Production Plan")
+
+		# this can not be unittested so mocking data that would be expected
+		# from client side.
+		for _ in range(10):
+			po_item = pp.append("po_items", {
+				"name": frappe.generate_hash(length=10),
+				"temporary_name": frappe.generate_hash(length=10),
+			})
+			pp.append("sub_assembly_items", {
+				"production_plan_item": po_item.temporary_name
+			})
+		pp._rename_temporary_references()
+
+		for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
+			self.assertEqual(po_item.name, subassy_item.production_plan_item)
+
+		# bad links should be erased
+		pp.append("sub_assembly_items", {
+			"production_plan_item": frappe.generate_hash(length=16)
+		})
+		pp._rename_temporary_references()
+		self.assertIsNone(pp.sub_assembly_items[-1].production_plan_item)
+		pp.sub_assembly_items.pop()
+
+		# reattempting on same doc shouldn't change anything
+		pp._rename_temporary_references()
+		for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
+			self.assertEqual(po_item.name, subassy_item.production_plan_item)
+
+
 def create_production_plan(**args):
 	"""
 	sales_order (obj): Sales Order Doc Object
diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
index f829d57..df5862f 100644
--- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
@@ -27,7 +27,8 @@
   "material_request",
   "material_request_item",
   "product_bundle_item",
-  "item_reference"
+  "item_reference",
+  "temporary_name"
  ],
  "fields": [
   {
@@ -204,17 +205,25 @@
    "fieldtype": "Data",
    "hidden": 1,
    "label": "Item Reference"
+  },
+  {
+   "fieldname": "temporary_name",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "temporary name"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-06-28 18:31:06.822168",
+ "modified": "2022-03-24 04:54:09.940224",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan Item",
+ "naming_rule": "Random",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 7eb40ec..e832ac9 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -457,7 +457,8 @@
 			mr_obj.update_requested_qty([self.material_request_item])
 
 	def update_ordered_qty(self):
-		if self.production_plan and self.production_plan_item:
+		if self.production_plan and self.production_plan_item \
+			and not self.production_plan_sub_assembly_item:
 			qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0
 
 			if self.docstatus == 1:
@@ -644,9 +645,13 @@
 		if not self.qty > 0:
 			frappe.throw(_("Quantity to Manufacture must be greater than 0."))
 
-		if self.production_plan and self.production_plan_item:
+		if self.production_plan and self.production_plan_item \
+			and not self.production_plan_sub_assembly_item:
 			qty_dict = frappe.db.get_value("Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1)
 
+			if not qty_dict:
+				return
+
 			allowance_qty = flt(frappe.db.get_single_value("Manufacturing Settings",
 			"overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0)
 
@@ -1150,6 +1155,10 @@
 		doc.insert()
 		frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name)), alert=True)
 
+	if enable_capacity_planning:
+		# automatically added scheduling rows shouldn't change status to WIP
+		doc.db_set("status", "Open")
+
 	return doc
 
 def get_work_order_operation_data(work_order, operation, workstation):
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 3199912..dc1d692 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -1,7 +1,7 @@
 [pre_model_sync]
 erpnext.patches.v12_0.update_is_cancelled_field
-erpnext.patches.v13_0.add_bin_unique_constraint
 erpnext.patches.v11_0.rename_production_order_to_work_order
+erpnext.patches.v13_0.add_bin_unique_constraint
 erpnext.patches.v11_0.refactor_naming_series
 erpnext.patches.v11_0.refactor_autoname_naming
 execute:frappe.reload_doc("accounts", "doctype", "POS Payment Method") #2020-05-28
@@ -360,4 +360,5 @@
 erpnext.patches.v14_0.delete_non_profit_doctypes
 erpnext.patches.v14_0.update_employee_advance_status
 erpnext.patches.v13_0.add_cost_center_in_loans
-erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items
+erpnext.patches.v13_0.set_return_against_in_pos_invoice_references
+erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022
diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
index bf5438c..80d5165 100644
--- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py
+++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
@@ -60,7 +60,7 @@
 
 def convert_to_seconds(value, unit):
 	seconds = 0
-	if value == 0:
+	if not value:
 		return seconds
 	if unit == 'Hours':
 		seconds = value * 3600
diff --git a/erpnext/patches/v13_0/set_return_against_in_pos_invoice_references.py b/erpnext/patches/v13_0/set_return_against_in_pos_invoice_references.py
new file mode 100644
index 0000000..6c24f52
--- /dev/null
+++ b/erpnext/patches/v13_0/set_return_against_in_pos_invoice_references.py
@@ -0,0 +1,38 @@
+import frappe
+
+
+def execute():
+	'''
+	Fetch and Set is_return & return_against from POS Invoice in POS Invoice References table.
+	'''
+
+	POSClosingEntry = frappe.qb.DocType("POS Closing Entry")
+	open_pos_closing_entries = (
+		frappe.qb
+			.from_(POSClosingEntry)
+			.select(POSClosingEntry.name)
+			.where(POSClosingEntry.docstatus == 0)
+			.run(pluck=True)
+		)
+
+	if not open_pos_closing_entries:
+		return
+
+	POSInvoiceReference = frappe.qb.DocType("POS Invoice Reference")
+	POSInvoice = frappe.qb.DocType("POS Invoice")
+	pos_invoice_references = (
+		frappe.qb
+			.from_(POSInvoiceReference)
+			.join(POSInvoice)
+			.on(POSInvoiceReference.pos_invoice == POSInvoice.name)
+			.select(POSInvoiceReference.name, POSInvoice.is_return, POSInvoice.return_against)
+			.where(POSInvoiceReference.parent.isin(open_pos_closing_entries))
+			.run(as_dict=True)
+	)
+
+	for row in pos_invoice_references:
+		frappe.db.set_value("POS Invoice Reference", row.name, "is_return", row.is_return)
+		if row.is_return:
+			frappe.db.set_value("POS Invoice Reference", row.name, "return_against", row.return_against)
+		else:
+			frappe.db.set_value("POS Invoice Reference", row.name, "return_against", None)
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 32b0f0f..9061c5f 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -708,6 +708,8 @@
 	if not_submitted_ss:
 		frappe.msgprint(_("Could not submit some Salary Slips"))
 
+	frappe.flags.via_payroll_entry = False
+
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filters):
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 5e41b66..f072135 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -38,6 +38,8 @@
 class TestSalarySlip(unittest.TestCase):
 	def setUp(self):
 		setup_test()
+		frappe.flags.pop("via_payroll_entry", None)
+
 
 	def tearDown(self):
 		frappe.db.rollback()
@@ -409,15 +411,17 @@
 		"email_salary_slip_to_employee": 1
 	})
 	def test_email_salary_slip(self):
-		frappe.db.sql("delete from `tabEmail Queue`")
+		frappe.db.delete("Email Queue")
 
-		make_employee("test_email_salary_slip@salary.com", company="_Test Company")
-		ss = make_employee_salary_slip("test_email_salary_slip@salary.com", "Monthly", "Test Salary Slip Email")
+		user_id = "test_email_salary_slip@salary.com"
+
+		make_employee(user_id, company="_Test Company")
+		ss = make_employee_salary_slip(user_id, "Monthly", "Test Salary Slip Email")
 		ss.company = "_Test Company"
 		ss.save()
 		ss.submit()
 
-		email_queue = frappe.db.sql("""select name from `tabEmail Queue`""")
+		email_queue = frappe.db.a_row_exists("Email Queue")
 		self.assertTrue(email_queue)
 
 	def test_loan_repayment_salary_slip(self):
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 00373a6..19e12e3 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1042,9 +1042,9 @@
 		var me = this;
 		this.set_dynamic_labels();
 		var company_currency = this.get_company_currency();
-		// Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
+		// Added `ignore_price_list` to determine if document is loading after mapping from another doc
 		if(this.frm.doc.currency && this.frm.doc.currency !== company_currency
-				&& !this.frm.doc.ignore_pricing_rule) {
+				&& !this.frm.doc.__onload.ignore_price_list) {
 
 			this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency,
 				function(exchange_rate) {
@@ -1070,7 +1070,7 @@
 		}
 
 		if(flt(this.frm.doc.conversion_rate)>0.0) {
-			if(this.frm.doc.ignore_pricing_rule) {
+			if(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) {
 				this.calculate_taxes_and_totals();
 			} else if (!this.in_apply_price_list){
 				this.apply_price_list();
@@ -1144,8 +1144,8 @@
 		this.set_dynamic_labels();
 
 		var company_currency = this.get_company_currency();
-		// Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
-		if(this.frm.doc.price_list_currency !== company_currency  && !this.frm.doc.ignore_pricing_rule) {
+		// Added `ignore_price_list` to determine if document is loading after mapping from another doc
+		if(this.frm.doc.price_list_currency !== company_currency  && !this.frm.doc.__onload.ignore_price_list) {
 			this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.price_list_currency, company_currency,
 				function(exchange_rate) {
 					me.frm.set_value("plc_conversion_rate", exchange_rate);
@@ -1884,6 +1884,7 @@
 				callback: function(r) {
 					if(!r.exc) {
 						item.item_tax_rate = r.message;
+						me.add_taxes_from_item_tax_template(item.item_tax_rate);
 						me.calculate_taxes_and_totals();
 					}
 				}
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 81ff351..f484545 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -608,6 +608,11 @@
 		&& doc.fg_completed_qty
 		&& erpnext.stock.bom
 		&& erpnext.stock.bom.name === doc.bom_no;
-	const itemChecks = !!item  && !item.allow_alternative_item;
+	const itemChecks = !!item
+		&& !item.allow_alternative_item
+		&& erpnext.stock.bom && erpnext.stock.items
+		&& (item.item_code in erpnext.stock.bom.items);
 	return docChecks && itemChecks;
 }
+
+//# sourceURL=serial_no_batch_selector.js
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index 019496d..6ae464d 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -264,6 +264,15 @@
 			font-size: 13px;
 		}
 
+		.filter-lookup-input {
+			background-color: white;
+			border: 1px solid var(--gray-300);
+
+			&:focus {
+				border: 1px solid var(--primary);
+			}
+		}
+
 		.filter-label {
 			font-size: 11px;
 			font-weight: 600;
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index d443f9c..55b563e 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -19,8 +19,9 @@
 
 
 def validate_gstin_for_india(doc, method):
-	if hasattr(doc, 'gst_state') and doc.gst_state:
-		doc.gst_state_number = state_numbers[doc.gst_state]
+	if hasattr(doc, 'gst_state'):
+		set_gst_state_and_state_number(doc)
+
 	if not hasattr(doc, 'gstin') or not doc.gstin:
 		return
 
@@ -50,7 +51,6 @@
 			frappe.throw(_("The input you've entered doesn't match the format of GSTIN."), title=_("Invalid GSTIN"))
 
 		validate_gstin_check_digit(doc.gstin)
-		set_gst_state_and_state_number(doc)
 
 		if not doc.gst_state:
 			frappe.throw(_("Please enter GST state"), title=_("Invalid State"))
@@ -82,17 +82,14 @@
 				frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular')
 
 def set_gst_state_and_state_number(doc):
-	if not doc.gst_state:
-		if not doc.state:
-			return
+	if not doc.gst_state and doc.state:
 		state = doc.state.lower()
 		states_lowercase = {s.lower():s for s in states}
 		if state in states_lowercase:
 			doc.gst_state = states_lowercase[state]
 		else:
 			return
-
-	doc.gst_state_number = state_numbers[doc.gst_state]
+	doc.gst_state_number = state_numbers.get(doc.gst_state)
 
 def validate_gstin_check_digit(gstin, label='GSTIN'):
 	''' Function to validate the check digit of the GSTIN.'''
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 06b4ff1..d602f0c 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -206,6 +206,7 @@
 		}, target_doc, set_missing_values, ignore_permissions=ignore_permissions)
 
 	# postprocess: fetch shipping address, set missing values
+	doclist.set_onload('ignore_price_list', True)
 
 	return doclist
 
@@ -269,6 +270,8 @@
 			}
 		}, target_doc, set_missing_values, ignore_permissions=ignore_permissions)
 
+	doclist.set_onload('ignore_price_list', True)
+
 	return doclist
 
 def _make_customer(source_name, ignore_permissions=False):
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 73e3d19..b906ec0 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -584,6 +584,8 @@
 
 	target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values)
 
+	target_doc.set_onload('ignore_price_list', True)
+
 	return target_doc
 
 @frappe.whitelist()
@@ -664,6 +666,8 @@
 	if automatically_fetch_payment_terms:
 		doclist.set_payment_schedule()
 
+	doclist.set_onload('ignore_price_list', True)
+
 	return doclist
 
 @frappe.whitelist()
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 993c61d..67948d7 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -8,7 +8,7 @@
 from frappe.utils.nestedset import get_root_of
 
 from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_stock_availability
-from erpnext.accounts.doctype.pos_profile.pos_profile import get_item_groups
+from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes, get_item_groups
 
 
 def search_by_term(search_term, warehouse, price_list):
@@ -275,3 +275,16 @@
 		contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
 		frappe.db.set_value('Customer', customer, 'mobile_no', value)
 	contact_doc.save()
+
+@frappe.whitelist()
+def get_pos_profile_data(pos_profile):
+	pos_profile = frappe.get_doc('POS Profile', pos_profile)
+	pos_profile = pos_profile.as_dict()
+
+	_customer_groups_with_children = []
+	for row in pos_profile.customer_groups:
+		children = get_child_nodes('Customer Group', row.customer_group)
+		_customer_groups_with_children.extend(children)
+
+	pos_profile.customer_groups = _customer_groups_with_children
+	return pos_profile
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index ea8459f..6974bed 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -119,10 +119,15 @@
 			this.allow_negative_stock = flt(message.allow_negative_stock) || false;
 		});
 
-		frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
-			Object.assign(this.settings, profile);
-			this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group);
-			this.make_app();
+		frappe.call({
+			method: "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_profile_data",
+			args: { "pos_profile": this.pos_profile },
+			callback: (res) => {
+				const profile = res.message;
+				Object.assign(this.settings, profile);
+				this.settings.customer_groups = profile.customer_groups.map(group => group.name);
+				this.make_app();
+			}
 		});
 	}
 
@@ -555,7 +560,7 @@
 				if (this.item_details.$component.is(':visible'))
 					this.edit_item_details_of(item_row);
 
-				if (this.check_serial_batch_selection_needed(item_row))
+				if (this.check_serial_batch_selection_needed(item_row) && !this.item_details.$component.is(':visible'))
 					this.edit_item_details_of(item_row);
 			}
 
@@ -704,7 +709,7 @@
 		frappe.dom.freeze();
 		const { doctype, name, current_item } = this.item_details;
 
-		frappe.model.set_value(doctype, name, 'qty', 0)
+		return frappe.model.set_value(doctype, name, 'qty', 0)
 			.then(() => {
 				frappe.model.clear_doc(doctype, name);
 				this.update_cart_html(current_item, true);
@@ -715,7 +720,14 @@
 	}
 
 	async save_and_checkout() {
-		this.frm.is_dirty() && await this.frm.save();
-		this.payment.checkout();
+		if (this.frm.is_dirty()) {
+			// only move to payment section if save is successful
+			frappe.route_hooks.after_save = () => this.payment.checkout();
+			return this.frm.save(
+				null, null, null, () => this.cart.toggle_checkout_btn(true) // show checkout button on error
+			);
+		} else {
+			this.payment.checkout();
+		}
 	}
 };
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index fb69b63..b75ffb2 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -60,12 +60,18 @@
 		return item && item.name == this.current_item.name;
 	}
 
-	toggle_item_details_section(item) {
+	async toggle_item_details_section(item) {
 		const current_item_changed = !this.compare_with_current_item(item);
 
 		// if item is null or highlighted cart item is clicked twice
 		const hide_item_details = !Boolean(item) || !current_item_changed;
 
+		if ((!hide_item_details && current_item_changed) || hide_item_details) {
+			// if item details is being closed OR if item details is opened but item is changed
+			// in both cases, if the current item is a serialized item, then validate and remove the item
+			await this.validate_serial_batch_item();
+		}
+
 		this.events.toggle_item_selector(!hide_item_details);
 		this.toggle_component(!hide_item_details);
 
@@ -83,7 +89,6 @@
 			this.render_form(item);
 			this.events.highlight_cart_item(item);
 		} else {
-			this.validate_serial_batch_item();
 			this.current_item = {};
 		}
 	}
@@ -103,11 +108,11 @@
 			(serialized && batched && (no_batch_selected || no_serial_selected))) {
 
 			frappe.show_alert({
-				message: __("Item will be removed since no serial / batch no selected."),
+				message: __("Item is removed since no serial / batch no selected."),
 				indicator: 'orange'
 			});
 			frappe.utils.play_sound("cancel");
-			this.events.remove_item_from_cart();
+			return this.events.remove_item_from_cart();
 		}
 	}
 
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index 1e9f6d7..b4ece46 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -170,20 +170,24 @@
     });
 
 		frappe.ui.form.on('POS Invoice', 'coupon_code', (frm) => {
-			if (!frm.doc.ignore_pricing_rule && frm.doc.coupon_code) {
-				frappe.run_serially([
-					() => frm.doc.ignore_pricing_rule=1,
-					() => frm.trigger('ignore_pricing_rule'),
-					() => frm.doc.ignore_pricing_rule=0,
-					() => frm.trigger('apply_pricing_rule'),
-					() => frm.save(),
-					() => this.update_totals_section(frm.doc)
-				]);
-			} else if (frm.doc.ignore_pricing_rule && frm.doc.coupon_code) {
-				frappe.show_alert({
-					message: __("Ignore Pricing Rule is enabled. Cannot apply coupon code."),
-					indicator: "orange"
-				});
+			if (frm.doc.coupon_code && !frm.applying_pos_coupon_code) {
+				if (!frm.doc.ignore_pricing_rule) {
+					frm.applying_pos_coupon_code = true;
+					frappe.run_serially([
+						() => frm.doc.ignore_pricing_rule=1,
+						() => frm.trigger('ignore_pricing_rule'),
+						() => frm.doc.ignore_pricing_rule=0,
+						() => frm.trigger('apply_pricing_rule'),
+						() => frm.save(),
+						() => this.update_totals_section(frm.doc),
+						() => (frm.applying_pos_coupon_code = false)
+					]);
+				} else if (frm.doc.ignore_pricing_rule) {
+					frappe.show_alert({
+						message: __("Ignore Pricing Rule is enabled. Cannot apply coupon code."),
+						indicator: "orange"
+					});
+				}
 			}
 		});
 
diff --git a/erpnext/setup/doctype/sales_person/sales_person.js b/erpnext/setup/doctype/sales_person/sales_person.js
index b71a92f..d86a8f3 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.js
+++ b/erpnext/setup/doctype/sales_person/sales_person.js
@@ -4,8 +4,12 @@
 frappe.ui.form.on('Sales Person', {
 	refresh: function(frm) {
 		if(frm.doc.__onload && frm.doc.__onload.dashboard_info) {
-			var info = frm.doc.__onload.dashboard_info;
-			frm.dashboard.add_indicator(__('Total Contribution Amount: {0}', [format_currency(info.allocated_amount, info.currency)]), 'blue');
+			let info = frm.doc.__onload.dashboard_info;
+			frm.dashboard.add_indicator(__('Total Contribution Amount Against Orders: {0}',
+				[format_currency(info.allocated_amount_against_order, info.currency)]), 'blue');
+
+			frm.dashboard.add_indicator(__('Total Contribution Amount Against Invoices: {0}',
+				[format_currency(info.allocated_amount_against_invoice, info.currency)]), 'blue');
 		}
 	},
 
diff --git a/erpnext/setup/doctype/sales_person/sales_person.py b/erpnext/setup/doctype/sales_person/sales_person.py
index b79a566..6af1b31 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.py
+++ b/erpnext/setup/doctype/sales_person/sales_person.py
@@ -28,14 +28,17 @@
 	def load_dashboard_info(self):
 		company_default_currency = get_default_currency()
 
-		allocated_amount = frappe.db.sql("""
-			select sum(allocated_amount)
-			from `tabSales Team`
-			where sales_person = %s and docstatus=1 and parenttype = 'Sales Order'
-		""",(self.sales_person_name))
+		allocated_amount_against_order = flt(frappe.db.get_value('Sales Team',
+			{'docstatus': 1, 'parenttype': 'Sales Order', 'sales_person': self.sales_person_name},
+			'sum(allocated_amount)'))
+
+		allocated_amount_against_invoice = flt(frappe.db.get_value('Sales Team',
+			{'docstatus': 1, 'parenttype': 'Sales Invoice', 'sales_person': self.sales_person_name},
+			'sum(allocated_amount)'))
 
 		info = {}
-		info["allocated_amount"] = flt(allocated_amount[0][0]) if allocated_amount else 0
+		info["allocated_amount_against_order"] = allocated_amount_against_order
+		info["allocated_amount_against_invoice"] = allocated_amount_against_invoice
 		info["currency"] = company_default_currency
 
 		self.set_onload('dashboard_info', info)
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 1d7bad2..1d95ddb 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -21,9 +21,7 @@
 def after_install():
 	frappe.get_doc({'doctype': "Role", "role_name": "Analytics"}).insert()
 	set_single_defaults()
-	create_compact_item_print_custom_field()
-	create_print_uom_after_qty_custom_field()
-	create_print_zero_amount_taxes_custom_field()
+	create_print_setting_custom_fields()
 	add_all_roles_to("Administrator")
 	create_default_cash_flow_mapper_templates()
 	create_default_success_action()
@@ -77,7 +75,7 @@
 	except frappe.ValidationError:
 		pass
 
-def create_compact_item_print_custom_field():
+def create_print_setting_custom_fields():
 	create_custom_field('Print Settings', {
 		'label': _('Compact Item Print'),
 		'fieldname': 'compact_item_print',
@@ -85,9 +83,6 @@
 		'default': 1,
 		'insert_after': 'with_letterhead'
 	})
-
-
-def create_print_uom_after_qty_custom_field():
 	create_custom_field('Print Settings', {
 		'label': _('Print UOM after Quantity'),
 		'fieldname': 'print_uom_after_quantity',
@@ -95,9 +90,6 @@
 		'default': 0,
 		'insert_after': 'compact_item_print'
 	})
-
-
-def create_print_zero_amount_taxes_custom_field():
 	create_custom_field('Print Settings', {
 		'label': _('Print taxes with zero amount'),
 		'fieldname': 'print_taxes_with_zero_amount',
diff --git a/erpnext/setup/setup_wizard/data/test_mfg.json b/erpnext/setup/setup_wizard/data/test_mfg.json
deleted file mode 100644
index efc5fa8..0000000
--- a/erpnext/setup/setup_wizard/data/test_mfg.json
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- "add_sample_data": 1,
- "bank_account": "HDFC",
- "company_abbr": "FT",
- "company_name": "For Testing",
- "company_tagline": "Just for GST",
- "country": "India",
- "currency": "INR",
- "customer_1": "Test Customer 1",
- "customer_2": "Test Customer 2",
- "domains": ["Manufacturing"],
- "email": "great@example.com",
- "full_name": "Great Tester",
- "fy_end_date": "2018-03-31",
- "fy_start_date": "2017-04-01",
- "is_purchase_item_1": 1,
- "is_purchase_item_2": 1,
- "is_purchase_item_3": 0,
- "is_purchase_item_4": 0,
- "is_purchase_item_5": 0,
- "is_sales_item_1": 1,
- "is_sales_item_2": 1,
- "is_sales_item_3": 1,
- "is_sales_item_4": 1,
- "is_sales_item_5": 1,
- "item_1": "Test Item 1",
- "item_2": "Test Item 2",
- "item_group_1": "Products",
- "item_group_2": "Products",
- "item_group_3": "Products",
- "item_group_4": "Products",
- "item_group_5": "Products",
- "item_uom_1": "Unit",
- "item_uom_2": "Unit",
- "item_uom_3": "Unit",
- "item_uom_4": "Unit",
- "item_uom_5": "Unit",
- "language": "English (United States)",
- "password": "test",
- "setup_website": 1,
- "supplier_1": "Test Supplier 1",
- "supplier_2": "Test Supplier 2",
- "timezone": "Asia/Kolkata",
- "user_accountant_1": 1,
- "user_accountant_2": 1,
- "user_accountant_3": 1,
- "user_accountant_4": 1,
- "user_purchaser_1": 1,
- "user_purchaser_2": 1,
- "user_purchaser_3": 1,
- "user_purchaser_4": 1,
- "user_sales_1": 1,
- "user_sales_2": 1,
- "user_sales_3": 1,
- "user_sales_4": 1
-}
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/operations/sample_data.py b/erpnext/setup/setup_wizard/operations/sample_data.py
deleted file mode 100644
index 1685994..0000000
--- a/erpnext/setup/setup_wizard/operations/sample_data.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import json
-import os
-import random
-
-import frappe
-import frappe.utils
-from frappe import _
-from frappe.utils.make_random import add_random_children
-
-
-def make_sample_data(domains, make_dependent = False):
-	"""Create a few opportunities, quotes, material requests, issues, todos, projects
-	to help the user get started"""
-
-	if make_dependent:
-		items = frappe.get_all("Item", {'is_sales_item': 1})
-		customers = frappe.get_all("Customer")
-		warehouses = frappe.get_all("Warehouse")
-
-		if items and customers:
-			for i in range(3):
-				customer = random.choice(customers).name
-				make_opportunity(items, customer)
-				make_quote(items, customer)
-
-		if items and warehouses:
-			make_material_request(frappe.get_all("Item"))
-
-	make_projects(domains)
-	import_notification()
-
-def make_opportunity(items, customer):
-	b = frappe.get_doc({
-		"doctype": "Opportunity",
-		"opportunity_from": "Customer",
-		"customer": customer,
-		"opportunity_type": _("Sales"),
-		"with_items": 1
-	})
-
-	add_random_children(b, "items", rows=len(items), randomize = {
-		"qty": (1, 5),
-		"item_code": ["Item"]
-	}, unique="item_code")
-
-	b.insert(ignore_permissions=True)
-
-	b.add_comment('Comment', text="This is a dummy record")
-
-def make_quote(items, customer):
-	qtn = frappe.get_doc({
-		"doctype": "Quotation",
-		"quotation_to": "Customer",
-		"party_name": customer,
-		"order_type": "Sales"
-	})
-
-	add_random_children(qtn, "items", rows=len(items), randomize = {
-		"qty": (1, 5),
-		"item_code": ["Item"]
-	}, unique="item_code")
-
-	qtn.insert(ignore_permissions=True)
-
-	qtn.add_comment('Comment', text="This is a dummy record")
-
-def make_material_request(items):
-	for i in items:
-		mr = frappe.get_doc({
-			"doctype": "Material Request",
-			"material_request_type": "Purchase",
-			"schedule_date": frappe.utils.add_days(frappe.utils.nowdate(), 7),
-			"items": [{
-				"schedule_date": frappe.utils.add_days(frappe.utils.nowdate(), 7),
-				"item_code": i.name,
-				"qty": 10
-			}]
-		})
-		mr.insert()
-		mr.submit()
-
-		mr.add_comment('Comment', text="This is a dummy record")
-
-
-def make_issue():
-	pass
-
-def make_projects(domains):
-	current_date = frappe.utils.nowdate()
-	project = frappe.get_doc({
-		"doctype": "Project",
-		"project_name": "ERPNext Implementation",
-	})
-
-	tasks = [
-		{
-			"title": "Explore ERPNext",
-			"start_date": current_date,
-			"end_date": current_date,
-			"file": "explore.md"
-		}]
-
-	if 'Education' in domains:
-		tasks += [
-			{
-				"title": _("Setup your Institute in ERPNext"),
-				"start_date": current_date,
-				"end_date": frappe.utils.add_days(current_date, 1),
-				"file": "education_masters.md"
-			},
-			{
-				"title": "Setup Master Data",
-				"start_date": current_date,
-				"end_date": frappe.utils.add_days(current_date, 1),
-				"file": "education_masters.md"
-			}]
-
-	else:
-		tasks += [
-			{
-				"title": "Setup Your Company",
-				"start_date": current_date,
-				"end_date": frappe.utils.add_days(current_date, 1),
-				"file": "masters.md"
-			},
-			{
-				"title": "Start Tracking your Sales",
-				"start_date": current_date,
-				"end_date": frappe.utils.add_days(current_date, 2),
-				"file": "sales.md"
-			},
-			{
-				"title": "Start Managing Purchases",
-				"start_date": current_date,
-				"end_date": frappe.utils.add_days(current_date, 3),
-				"file": "purchase.md"
-			},
-			{
-				"title": "Import Data",
-				"start_date": current_date,
-				"end_date": frappe.utils.add_days(current_date, 4),
-				"file": "import_data.md"
-			},
-			{
-				"title": "Go Live!",
-				"start_date": current_date,
-				"end_date": frappe.utils.add_days(current_date, 5),
-				"file": "go_live.md"
-			}]
-
-	for t in tasks:
-		with open (os.path.join(os.path.dirname(__file__), "tasks", t['file'])) as f:
-			t['description'] = frappe.utils.md_to_html(f.read())
-			del t['file']
-
-		project.append('tasks', t)
-
-	project.insert(ignore_permissions=True)
-
-def import_notification():
-	'''Import notification for task start'''
-	with open (os.path.join(os.path.dirname(__file__), "tasks/task_alert.json")) as f:
-		notification = frappe.get_doc(json.loads(f.read())[0])
-		notification.insert()
-
-	# trigger the first message!
-	from frappe.email.doctype.notification.notification import trigger_daily_alerts
-	trigger_daily_alerts()
-
-def test_sample():
-	frappe.db.sql('delete from `tabNotification`')
-	frappe.db.sql('delete from tabProject')
-	frappe.db.sql('delete from tabTask')
-	make_projects('Education')
-	import_notification()
diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py
index c9ed184..239e145 100644
--- a/erpnext/setup/setup_wizard/setup_wizard.py
+++ b/erpnext/setup/setup_wizard/setup_wizard.py
@@ -7,7 +7,6 @@
 
 from .operations import company_setup
 from .operations import install_fixtures as fixtures
-from .operations import sample_data
 
 
 def get_setup_stages(args=None):
@@ -103,16 +102,6 @@
 	frappe.local.message_log = []
 	login_as_first_user(args)
 
-	make_sample_data(args.get('domains'))
-
-def make_sample_data(domains):
-	try:
-		sample_data.make_sample_data(domains)
-	except Exception:
-		# clear message
-		if frappe.message_log:
-			frappe.message_log.pop()
-		pass
 
 def login_as_first_user(args):
 	if args.get("email") and hasattr(frappe.local, "login_manager"):
diff --git a/erpnext/setup/setup_wizard/tasks/education_masters.md b/erpnext/setup/setup_wizard/tasks/education_masters.md
deleted file mode 100644
index d0887d2..0000000
--- a/erpnext/setup/setup_wizard/tasks/education_masters.md
+++ /dev/null
@@ -1,9 +0,0 @@
-Lets start making things in ERPNext that are representative of your institution.
-
-1. Make a list of **Programs** that you offer
-1. Add a few **Courses** that your programs cover
-1. Create **Academic Terms** and **Academic Years**
-1. Start adding **Students**
-1. Group your students into **Batches**
-
-Watch this video to learn more about ERPNext Education: https://www.youtube.com/watch?v=f6foQOyGzdA
diff --git a/erpnext/setup/setup_wizard/tasks/explore.md b/erpnext/setup/setup_wizard/tasks/explore.md
deleted file mode 100644
index ce6cb60..0000000
--- a/erpnext/setup/setup_wizard/tasks/explore.md
+++ /dev/null
@@ -1,7 +0,0 @@
-Thanks for checking this out! ❤️
-
-If you are evaluating an ERP system for the first time, this is going to be quite a task! But don't worry, ERPNext is awesome.
-
-First, get familiar with the surroundings. ERPNext covers a *lot of features*, go to the home page and click on the "Explore" icon.
-
-All the best!
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/tasks/go_live.md b/erpnext/setup/setup_wizard/tasks/go_live.md
deleted file mode 100644
index 0a934a4..0000000
--- a/erpnext/setup/setup_wizard/tasks/go_live.md
+++ /dev/null
@@ -1,18 +0,0 @@
-Ready to go live with ERPNext? 🏁🏁🏁
-
-Here are the steps:
-
-1. Sync up your **Chart of Accounts**
-3. Add your opening stock using **Stock Reconciliation**
-4. Add your open invoices (both sales and purchase)
-3. Add your opening account balances by making a **Journal Entry**
-
-If you need help for going live, sign up for an account at erpnext.com or find a partner to help you with this.
-
-Or you can watch these videos 📺:
-
-Setup your chart of accounts: https://www.youtube.com/watch?v=AcfMCT7wLLo
-
-Add Open Stock: https://www.youtube.com/watch?v=nlHX0ZZ84Lw
-
-Add Opening Balances: https://www.youtube.com/watch?v=nlHX0ZZ84Lw
diff --git a/erpnext/setup/setup_wizard/tasks/import_data.md b/erpnext/setup/setup_wizard/tasks/import_data.md
deleted file mode 100644
index c5b85c9..0000000
--- a/erpnext/setup/setup_wizard/tasks/import_data.md
+++ /dev/null
@@ -1,5 +0,0 @@
-Lets import some data! 💪💪
-
-If you are already running a business, you most likely have your Items, Customers or Suppliers in some spreadsheet file somewhere, import it into ERPNext with the Data Import Tool.
-
-Watch this video to get started: https://www.youtube.com/watch?v=Ta2Xx3QoK3E
diff --git a/erpnext/setup/setup_wizard/tasks/masters.md b/erpnext/setup/setup_wizard/tasks/masters.md
deleted file mode 100644
index 6ade159..0000000
--- a/erpnext/setup/setup_wizard/tasks/masters.md
+++ /dev/null
@@ -1,7 +0,0 @@
-Start building a model of your business in ERPNext by adding your Items and Customers.
-
-These videos 📺 will help you get started:
-
-Adding Customers and Suppliers: https://www.youtube.com/watch?v=zsrrVDk6VBs
-
-Adding Items and Prices: https://www.youtube.com/watch?v=FcOsV-e8ymE
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/tasks/purchase.md b/erpnext/setup/setup_wizard/tasks/purchase.md
deleted file mode 100644
index 3f3bc3b..0000000
--- a/erpnext/setup/setup_wizard/tasks/purchase.md
+++ /dev/null
@@ -1,10 +0,0 @@
-How to manage your purchasing in ERPNext 🛒🛒🛒:
-
-1. Add a few **Suppliers**
-2. Find out what you need by making **Material Requests**.
-3. Now start placing orders via **Purchase Order**.
-4. When your suppliers deliver, make **Purchase Receipts**
-
-Now never run out of stock again! 😎
-
-Watch this video 📺 to get an overview: https://www.youtube.com/watch?v=4TN9kPyfIqM
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/tasks/sales.md b/erpnext/setup/setup_wizard/tasks/sales.md
deleted file mode 100644
index 15268fa..0000000
--- a/erpnext/setup/setup_wizard/tasks/sales.md
+++ /dev/null
@@ -1,8 +0,0 @@
-Start managing your sales with ERPNext 🔔🔔🔔:
-
-1. Add potential business contacts as **Leads**
-2. Udpate your deals in pipeline in **Opportunities**
-3. Send proposals to your leads or customers with **Quotations**
-4. Track confirmed orders with **Sales Orders**
-
-Watch this video 📺 to get an overview: https://www.youtube.com/watch?v=o9XCSZHJfpA
diff --git a/erpnext/setup/setup_wizard/tasks/school_import_data.md b/erpnext/setup/setup_wizard/tasks/school_import_data.md
deleted file mode 100644
index 1fbe049..0000000
--- a/erpnext/setup/setup_wizard/tasks/school_import_data.md
+++ /dev/null
@@ -1,5 +0,0 @@
-Lets import some data! 💪💪
-
-If you are already running a Institute, you most likely have your Students in some spreadsheet file somewhere. Import it into ERPNext with the Data Import Tool.
-
-Watch this video to get started: https://www.youtube.com/watch?v=Ta2Xx3QoK3E
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/tasks/task_alert.json b/erpnext/setup/setup_wizard/tasks/task_alert.json
deleted file mode 100644
index cac868a..0000000
--- a/erpnext/setup/setup_wizard/tasks/task_alert.json
+++ /dev/null
@@ -1,28 +0,0 @@
-[
- {
-  "attach_print": 0,
-  "condition": "doc.status in ('Open', 'Overdue')",
-  "date_changed": "exp_end_date",
-  "days_in_advance": 0,
-  "docstatus": 0,
-  "doctype": "Notification",
-  "document_type": "Task",
-  "enabled": 1,
-  "event": "Days After",
-  "is_standard": 0,
-  "message": "<p>Task due today:</p>\n\n<div>\n{{ doc.description }}\n</div>\n\n<hr>\n<p style=\"font-size: 85%\">\nThis is a notification for a task that is due today, and a sample <b>Notification</b>. In ERPNext you can setup notifications on anything, Invoices, Orders, Leads, Opportunities, so you never miss a thing.\n<br>To edit this, and setup other alerts, just type <b>Notification</b> in the search bar.</p>",
-  "method": null,
-  "modified": "2017-03-09 07:34:58.168370",
-  "module": null,
-  "name": "Task Due Alert",
-  "recipients": [
-   {
-    "cc": null,
-    "condition": null,
-    "email_by_document_field": "owner"
-   }
-  ],
-  "subject": "{{ doc.subject }}",
-  "value_changed": null
- }
-]
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/utils.py b/erpnext/setup/setup_wizard/utils.py
deleted file mode 100644
index f1ec50af..0000000
--- a/erpnext/setup/setup_wizard/utils.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import json
-import os
-
-from frappe.desk.page.setup_wizard.setup_wizard import setup_complete
-
-
-def complete():
-	with open(os.path.join(os.path.dirname(__file__),
-		'data', 'test_mfg.json'), 'r') as f:
-		data = json.loads(f.read())
-
-	setup_complete(data)
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index a4f2207..6db1961 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -5,28 +5,17 @@
 import frappe
 from frappe import _
 from frappe.utils import add_days, flt, get_datetime_str, nowdate
+from frappe.utils.data import now_datetime
+from frappe.utils.nestedset import get_ancestors_of, get_root_of  # noqa
 
 from erpnext import get_default_company
 
 
-def get_root_of(doctype):
-	"""Get root element of a DocType with a tree structure"""
-	result = frappe.db.sql_list("""select name from `tab%s`
-		where lft=1 and rgt=(select max(rgt) from `tab%s` where docstatus < 2)""" %
-		(doctype, doctype))
-	return result[0] if result else None
-
-def get_ancestors_of(doctype, name):
-	"""Get ancestor elements of a DocType with a tree structure"""
-	lft, rgt = frappe.db.get_value(doctype, name, ["lft", "rgt"])
-	result = frappe.db.sql_list("""select name from `tab%s`
-		where lft<%s and rgt>%s order by lft desc""" % (doctype, "%s", "%s"), (lft, rgt))
-	return result or []
-
 def before_tests():
 	frappe.clear_cache()
 	# complete setup if missing
 	from frappe.desk.page.setup_wizard.setup_wizard import setup_complete
+	current_year = now_datetime().year
 	if not frappe.get_list("Company"):
 		setup_complete({
 			"currency"          :"USD",
@@ -36,8 +25,8 @@
 			"company_abbr"      :"WP",
 			"industry"          :"Manufacturing",
 			"country"           :"United States",
-			"fy_start_date"     :"2021-01-01",
-			"fy_end_date"       :"2021-12-31",
+			"fy_start_date"     :f"{current_year}-01-01",
+			"fy_end_date"       :f"{current_year}-12-31",
 			"language"          :"english",
 			"company_tagline"   :"Testing",
 			"email"             :"test@erpnext.com",
@@ -51,7 +40,6 @@
 	frappe.db.sql("delete from `tabSalary Slip`")
 	frappe.db.sql("delete from `tabItem Price`")
 
-	frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0)
 	enable_all_roles_and_domains()
 	set_defaults_for_tests()
 
@@ -142,13 +130,13 @@
 	add_all_roles_to('Administrator')
 
 def set_defaults_for_tests():
-	from frappe.utils.nestedset import get_root_of
-
 	selling_settings = frappe.get_single("Selling Settings")
 	selling_settings.customer_group = get_root_of("Customer Group")
 	selling_settings.territory = get_root_of("Territory")
 	selling_settings.save()
 
+	frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0)
+
 
 def insert_record(records):
 	for r in records:
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index 57d78a2..75aa1d3 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -40,18 +40,15 @@
 		filters=filters,
 		order_by=sort_by + ' ' + sort_order,
 		limit_start=start,
-		limit_page_length='21')
+		limit_page_length=21)
 
 	precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
 
 	for item in items:
 		item.update({
-			'item_name': frappe.get_cached_value(
-				"Item", item.item_code, 'item_name'),
-			'disable_quick_entry': frappe.get_cached_value(
-				"Item", item.item_code, 'has_batch_no')
-			or frappe.get_cached_value(
-				"Item", item.item_code, 'has_serial_no'),
+			'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'),
+			'disable_quick_entry': frappe.get_cached_value( "Item", item.item_code, 'has_batch_no')
+							or frappe.get_cached_value( "Item", item.item_code, 'has_serial_no'),
 			'projected_qty': flt(item.projected_qty, precision),
 			'reserved_qty': flt(item.reserved_qty, precision),
 			'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision),
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index ffa2f93..492f90b 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -519,6 +519,8 @@
 	if automatically_fetch_payment_terms:
 		doc.set_payment_schedule()
 
+	doc.set_onload('ignore_price_list', True)
+
 	return doc
 
 @frappe.whitelist()
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index c797187..524c3d1 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -422,7 +422,6 @@
    "fieldname": "has_batch_no",
    "fieldtype": "Check",
    "label": "Has Batch No",
-   "no_copy": 1,
    "oldfieldname": "has_batch_no",
    "oldfieldtype": "Select"
   },
@@ -472,7 +471,6 @@
    "fieldname": "has_serial_no",
    "fieldtype": "Check",
    "label": "Has Serial No",
-   "no_copy": 1,
    "oldfieldname": "has_serial_no",
    "oldfieldtype": "Select"
   },
@@ -921,7 +919,7 @@
  "image_field": "image",
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2022-02-11 08:07:46.663220",
+ "modified": "2022-03-25 06:38:55.942304",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item",
@@ -994,4 +992,4 @@
  "states": [],
  "title_field": "item_name",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 112420f..05e6e76 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -685,6 +685,12 @@
 		# standalone return
 		make_purchase_receipt(is_return=True, qty=-1, **typical_args)
 
+	def test_item_dashboard(self):
+		from erpnext.stock.dashboard.item_dashboard import get_data
+
+		self.assertTrue(get_data(item_code="_Test Item"))
+		self.assertTrue(get_data(warehouse="_Test Warehouse - _TC"))
+		self.assertTrue(get_data(item_group="All Item Groups"))
 
 
 def set_item_variant_settings(fields):
diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
index 5e1f7d5..058783c 100644
--- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
+++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
@@ -13,10 +13,16 @@
 		const exclude_field_types = ['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only'];
 
 		frappe.model.with_doctype('Item', () => {
+			const field_label_map = {};
 			frappe.get_meta('Item').fields.forEach(d => {
+				field_label_map[d.fieldname] = __(d.label) + ` (${d.fieldname})`;
+
 				if (!in_list(exclude_field_types, d.fieldtype)
 					&& !d.no_copy && !in_list(exclude_fields, d.fieldname)) {
-					allow_fields.push(d.fieldname);
+					allow_fields.push({
+						label: field_label_map[d.fieldname],
+						value: d.fieldname,
+					});
 				}
 			});
 
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 5bb337e..4bf37fe 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -823,6 +823,7 @@
 		}
 	}, target_doc, set_missing_values)
 
+	doclist.set_onload('ignore_price_list', True)
 	return doclist
 
 def get_invoiced_qty_map(purchase_receipt):
diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
index 4e8d71f..0ec812c 100644
--- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
@@ -396,6 +396,21 @@
 		rule_1.delete()
 		rule_2.delete()
 
+	def test_warehouse_capacity_dashbord(self):
+		from erpnext.stock.dashboard.warehouse_capacity_dashboard import get_data
+
+		item = "_Rice"
+		rule = create_putaway_rule(item_code=item, warehouse=self.warehouse_1, capacity=500,
+			uom="Kg")
+
+		capacities = get_data(warehouse=self.warehouse_1)
+		for capacity in capacities:
+			if capacity.item_code == item and capacity.warehouse == self.warehouse_1:
+				self.assertEqual(capacity.stock_capacity, 500)
+
+		get_data(warehouse=self.warehouse_1)
+		rule.delete()
+
 def create_putaway_rule(**args):
 	args = frappe._dict(args)
 	putaway = frappe.new_doc("Putaway Rule")
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index c77c6c3..2808c21 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -3,6 +3,7 @@
 
 
 import json
+from typing import List, Optional, Union
 
 import frappe
 from frappe import ValidationError, _
@@ -574,22 +575,30 @@
 	return serial_nos
 
 @frappe.whitelist()
-def auto_fetch_serial_number(qty, item_code, warehouse,
-		posting_date=None, batch_nos=None, for_doctype=None, exclude_sr_nos=None):
+def auto_fetch_serial_number(
+		qty: float,
+		item_code: str,
+		warehouse: str,
+		posting_date: Optional[str] = None,
+		batch_nos: Optional[Union[str, List[str]]] = None,
+		for_doctype: Optional[str] = None,
+		exclude_sr_nos: Optional[List[str]] = None
+	) -> List[str]:
 
 	filters = frappe._dict({"item_code": item_code, "warehouse": warehouse})
 
 	if exclude_sr_nos is None:
 		exclude_sr_nos = []
 	else:
+		exclude_sr_nos = safe_json_loads(exclude_sr_nos)
 		exclude_sr_nos = get_serial_nos(clean_serial_no_string("\n".join(exclude_sr_nos)))
 
 	if batch_nos:
 		batch_nos = safe_json_loads(batch_nos)
 		if isinstance(batch_nos, list):
 			filters.batch_no = batch_nos
-		elif isinstance(batch_nos, str):
-			filters.batch_no = [batch_nos]
+		else:
+			filters.batch_no = [str(batch_nos)]
 
 	if posting_date:
 		filters.expiry_date = posting_date
@@ -602,25 +611,60 @@
 
 	return sorted([d.get('name') for d in serial_numbers])
 
+def get_delivered_serial_nos(serial_nos):
+	'''
+	Returns serial numbers that delivered from the list of serial numbers
+	'''
+	from frappe.query_builder.functions import Coalesce
+
+	SerialNo = frappe.qb.DocType("Serial No")
+	serial_nos = get_serial_nos(serial_nos)
+	query = frappe.qb.select(SerialNo.name).from_(SerialNo).where(
+		(SerialNo.name.isin(serial_nos))
+		& (Coalesce(SerialNo.delivery_document_type, "") != "")
+	)
+
+	result = query.run()
+	if result and len(result) > 0:
+		delivered_serial_nos = [row[0] for row in result]
+		return delivered_serial_nos
+
 @frappe.whitelist()
 def get_pos_reserved_serial_nos(filters):
 	if isinstance(filters, str):
 		filters = json.loads(filters)
 
-	pos_transacted_sr_nos = frappe.db.sql("""select item.serial_no as serial_no
-		from `tabPOS Invoice` p, `tabPOS Invoice Item` item
-		where p.name = item.parent
-		and p.consolidated_invoice is NULL
-		and p.docstatus = 1
-		and item.docstatus = 1
-		and item.item_code = %(item_code)s
-		and item.warehouse = %(warehouse)s
-		and item.serial_no is NOT NULL and item.serial_no != ''
-		""", filters, as_dict=1)
+	POSInvoice = frappe.qb.DocType("POS Invoice")
+	POSInvoiceItem = frappe.qb.DocType("POS Invoice Item")
+	query = frappe.qb.from_(
+		POSInvoice
+	).from_(
+		POSInvoiceItem
+	).select(
+		POSInvoice.is_return,
+		POSInvoiceItem.serial_no
+	).where(
+		(POSInvoice.name == POSInvoiceItem.parent)
+		& (POSInvoice.docstatus == 1)
+		& (POSInvoiceItem.docstatus == 1)
+		& (POSInvoiceItem.item_code == filters.get('item_code'))
+		& (POSInvoiceItem.warehouse == filters.get('warehouse'))
+		& (POSInvoiceItem.serial_no.isnotnull())
+		& (POSInvoiceItem.serial_no != '')
+	)
+
+	pos_transacted_sr_nos = query.run(as_dict=True)
 
 	reserved_sr_nos = []
+	returned_sr_nos = []
 	for d in pos_transacted_sr_nos:
-		reserved_sr_nos += get_serial_nos(d.serial_no)
+		if d.is_return == 0:
+			reserved_sr_nos += get_serial_nos(d.serial_no)
+		elif d.is_return == 1:
+			returned_sr_nos += get_serial_nos(d.serial_no)
+
+	for sr_no in returned_sr_nos:
+		reserved_sr_nos.remove(sr_no)
 
 	return reserved_sr_nos
 
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index cca6307..7df0a56 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -274,7 +274,8 @@
 				msg=f"{partial_fetch} should be subset of {first_fetch}")
 
 		# exclusion
-		remaining = auto_fetch_serial_number(3, item_code, warehouse, exclude_sr_nos=partial_fetch)
+		remaining = auto_fetch_serial_number(3, item_code, warehouse,
+				exclude_sr_nos=json.dumps(partial_fetch))
 		self.assertEqual(sorted(remaining + partial_fetch), first_fetch)
 
 		# batchwise
diff --git a/erpnext/stock/doctype/variant_field/variant_field.json b/erpnext/stock/doctype/variant_field/variant_field.json
index ae90884..5a7da15 100644
--- a/erpnext/stock/doctype/variant_field/variant_field.json
+++ b/erpnext/stock/doctype/variant_field/variant_field.json
@@ -1,72 +1,32 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2017-08-29 16:33:33.978574", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2022-02-11 11:26:20.611960",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "field_name"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "field_name", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Field Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
+   "fieldname": "field_name",
+   "fieldtype": "Autocomplete",
+   "in_list_view": 1,
+   "label": "Field Name",
+   "reqd": 1
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2017-08-29 17:19:20.353197", 
- "modified_by": "Administrator", 
- "module": "Stock", 
- "name": "Variant Field", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-03-25 05:48:30.946201",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Variant Field",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index e099cdd..956c3c51 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -52,24 +52,6 @@
 
 			</div>
 
-			<script>
-				frappe.ready(() => {
-					$('.product-filter-filter').on('keydown', frappe.utils.debounce((e) => {
-						const $input = $(e.target);
-						const keyword = ($input.val() || '').toLowerCase();
-						const $filter_options = $input.next('.filter-options');
-
-						$filter_options.find('.custom-control').show();
-						$filter_options.find('.custom-control').each((i, el) => {
-							const $el = $(el);
-							const value = $el.data('value').toLowerCase();
-							if (!value.includes(keyword)) {
-								$el.hide();
-							}
-						});
-					}, 300));
-				})
-			</script>
 		</div>
 	</div>
 </div>
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 4741307..fb4cecf 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -300,13 +300,13 @@
 
 		{% if values | len > 20 %}
 		<!-- show inline filter if values more than 20 -->
-		<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
+		<input type="text" class="form-control form-control-sm mb-2 filter-lookup-input" placeholder="Search {{ item_field.label + 's' }}"/>
 		{% endif %}
 
 		{% if values %}
 		<div class="filter-options">
 			{% for value in values %}
-			<div class="checkbox" data-value="{{ value }}">
+			<div class="filter-lookup-wrapper checkbox" data-value="{{ value }}">
 				<label for="{{value}}">
 					<input type="checkbox"
 						class="product-filter field-filter"
@@ -329,16 +329,16 @@
 {%- macro attribute_filter_section(filters)-%}
 {% for attribute in filters %}
 	<div class="mb-4 filter-block pb-5">
-		<div class="filter-label mb-3">{{ attribute.name}}</div>
-		{% if values | len > 20 %}
+		<div class="filter-label mb-3">{{ attribute.name }}</div>
+		{% if attribute.item_attribute_values | len > 20 %}
 		<!-- show inline filter if values more than 20 -->
-		<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
+		<input type="text" class="form-control form-control-sm mb-2 filter-lookup-input" placeholder="Search {{ attribute.name + 's' }}"/>
 		{% endif %}
 
 		{% if attribute.item_attribute_values %}
 		<div class="filter-options">
 			{% for attr_value in attribute.item_attribute_values %}
-			<div class="checkbox">
+			<div class="filter-lookup-wrapper checkbox" data-value="{{ attr_value }}">
 				<label data-value="{{ attr_value }}">
 					<input type="checkbox"
 						class="product-filter attribute-filter"
diff --git a/erpnext/tests/test_zform_loads.py b/erpnext/tests/test_zform_loads.py
index b6fb636..5b82c7b 100644
--- a/erpnext/tests/test_zform_loads.py
+++ b/erpnext/tests/test_zform_loads.py
@@ -1,13 +1,14 @@
-""" dumb test to check all function calls on known form loads """
-
-import unittest
+""" smoak tests to check basic functionality calls on known form loads."""
 
 import frappe
 from frappe.desk.form.load import getdoc
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.www.printview import get_html_and_style
 
 
-class TestFormLoads(unittest.TestCase):
+class TestFormLoads(FrappeTestCase):
 
+	@change_settings("Print Settings", {"allow_print_for_cancelled": 1})
 	def test_load(self):
 		erpnext_modules = frappe.get_all("Module Def", filters={"app_name": "erpnext"}, pluck="name")
 		doctypes = frappe.get_all("DocType", {"istable": 0, "issingle": 0, "is_virtual": 0, "module": ("in", erpnext_modules)}, pluck="name")
@@ -17,14 +18,35 @@
 			if not last_doc:
 				continue
 			with self.subTest(msg=f"Loading {doctype} - {last_doc}", doctype=doctype, last_doc=last_doc):
-				try:
-					# reset previous response
-					frappe.response = frappe._dict({"docs":[]})
-					frappe.response.docinfo = None
+				self.assertFormLoad(doctype, last_doc)
+				self.assertDocPrint(doctype, last_doc)
 
-					getdoc(doctype, last_doc)
-				except Exception as e:
-					self.fail(f"Failed to load {doctype} - {last_doc}: {e}")
+	def assertFormLoad(self, doctype, docname):
+		# reset previous response
+		frappe.response = frappe._dict({"docs":[]})
+		frappe.response.docinfo = None
 
-				self.assertTrue(frappe.response.docs, msg=f"expected document in reponse, found: {frappe.response.docs}")
-				self.assertTrue(frappe.response.docinfo, msg=f"expected docinfo in reponse, found: {frappe.response.docinfo}")
+		try:
+			getdoc(doctype, docname)
+		except Exception as e:
+			self.fail(f"Failed to load {doctype}-{docname}: {e}")
+
+		self.assertTrue(frappe.response.docs, msg=f"expected document in reponse, found: {frappe.response.docs}")
+		self.assertTrue(frappe.response.docinfo, msg=f"expected docinfo in reponse, found: {frappe.response.docinfo}")
+
+	def assertDocPrint(self, doctype, docname):
+		doc = frappe.get_doc(doctype, docname)
+		doc.set("__onload", frappe._dict())
+		doc.run_method("onload")
+
+		messages_before = frappe.get_message_log()
+		ret = get_html_and_style(doc=doc.as_json(), print_format="Standard", no_letterhead=1)
+		messages_after = frappe.get_message_log()
+
+		if len(messages_after) > len(messages_before):
+			new_messages = messages_after[len(messages_before):]
+			self.fail("Print view showing error/warnings: \n"
+					+ "\n".join(str(msg) for msg in new_messages))
+
+		# html should exist
+		self.assertTrue(bool(ret["html"]))
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 1a24f2b..c8d6c06 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -1163,7 +1163,8 @@
 Grand Total,Gesamtbetrag,
 Grant,Gewähren,
 Grant Application,Antrag bewilligen,
-Grant Leaves,Grant Blätter,
+Grant Commission,Provision gewähren,
+Grant Leaves,Meldungen gewähren,
 Grant information.,Gewähren Sie Informationen.,
 Grocery,Lebensmittelgeschäft,
 Gross Pay,Bruttolohn,
@@ -1332,6 +1333,7 @@
 Inward supplies from ISD,Nachschub von ISD,
 Inward supplies liable to reverse charge (other than 1 & 2 above),Rückbelastungspflichtige Lieferungen (außer 1 &amp; 2 oben),
 Is Active,Ist aktiv(iert),
+Is Debit Note,Ist Lastschrift,
 Is Default,Ist Standard,
 Is Existing Asset,Vermögenswert existiert bereits.,
 Is Frozen,Ist gesperrt,
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html
index 3d5517c..04fc74c 100644
--- a/erpnext/www/all-products/index.html
+++ b/erpnext/www/all-products/index.html
@@ -31,24 +31,6 @@
 			{% endif %}
 		</div>
 
-		<script>
-			frappe.ready(() => {
-				$('.product-filter-filter').on('keydown', frappe.utils.debounce((e) => {
-					const $input = $(e.target);
-					const keyword = ($input.val() || '').toLowerCase();
-					const $filter_options = $input.next('.filter-options');
-
-					$filter_options.find('.custom-control').show();
-					$filter_options.find('.custom-control').each((i, el) => {
-						const $el = $(el);
-						const value = $el.data('value').toLowerCase();
-						if (!value.includes(keyword)) {
-							$el.hide();
-						}
-					});
-				}, 300));
-			})
-		</script>
 	</div>
 </div>