Merge branch 'develop' into fix-pos-issues-again
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 9d1452b..6f7a0b2 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -32,6 +32,8 @@
 		name will be changed using autoname options (in a scheduled job)
 		"""
 		self.name = frappe.generate_hash(txt="", length=10)
+		if self.meta.autoname == "hash":
+			self.to_rename = 0
 
 	def validate(self):
 		self.flags.ignore_submit_comment = True
@@ -134,7 +136,7 @@
 
 	def check_pl_account(self):
 		if self.is_opening=='Yes' and \
-				frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss":
+				frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss" and not self.is_cancelled:
 			frappe.throw(_("{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry")
 				.format(self.voucher_type, self.voucher_no, self.account))
 
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.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 7239dee..275eeb3 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -488,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/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 2c31561..7654aa4 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -32,6 +32,7 @@
 from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
 from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
 from erpnext.buying.utils import check_on_hold_or_closed_status
+from erpnext.controllers.accounts_controller import validate_account_head
 from erpnext.controllers.buying_controller import BuyingController
 from erpnext.stock import get_warehouse_account_map
 from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
@@ -107,6 +108,7 @@
 		self.validate_uom_is_integer("uom", "qty")
 		self.validate_uom_is_integer("stock_uom", "stock_qty")
 		self.set_expense_account(for_validate=True)
+		self.validate_expense_account()
 		self.set_against_expense_account()
 		self.validate_write_off_account()
 		self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items")
@@ -311,6 +313,10 @@
 			elif not item.expense_account and for_validate:
 				throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
 
+	def validate_expense_account(self):
+		for item in self.get('items'):
+			validate_account_head(item.idx, item.expense_account, self.company, 'Expense')
+
 	def set_against_expense_account(self):
 		against_accounts = []
 		for item in self.get("items"):
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 3afd72e..9433489 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -37,6 +37,7 @@
 	get_gl_entries_on_asset_regain,
 	make_depreciation_entry,
 )
+from erpnext.controllers.accounts_controller import validate_account_head
 from erpnext.controllers.selling_controller import SellingController
 from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
 from erpnext.setup.doctype.company.company import update_company_current_month_sales
@@ -111,6 +112,8 @@
 		self.validate_fixed_asset()
 		self.set_income_account_for_fixed_assets()
 		self.validate_item_cost_centers()
+		self.validate_income_account()
+
 		validate_inter_company_party(self.doctype, self.customer, self.company, self.inter_company_invoice_reference)
 
 		if cint(self.is_pos):
@@ -178,6 +181,10 @@
 			if cost_center_company != self.company:
 				frappe.throw(_("Row #{0}: Cost Center {1} does not belong to company {2}").format(frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company)))
 
+	def validate_income_account(self):
+		for item in self.get('items'):
+			validate_account_head(item.idx, item.income_account, self.company, 'Income')
+
 	def set_tax_withholding(self):
 		tax_withholding_details = get_party_tax_withholding_details(self)
 
@@ -1704,6 +1711,7 @@
 		}
 	}, target_doc, set_missing_values)
 
+	doclist.set_onload('ignore_price_list', True)
 	return doclist
 
 @frappe.whitelist()
diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
index b72d266..fe644ed 100644
--- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
+++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
@@ -267,7 +267,7 @@
 
 		total_amount += flt(amount)
 
-	return amount
+	return total_amount
 
 def get_balance_row(label, amount, account_currency):
 	if amount > 0:
diff --git a/erpnext/accounts/report/cash_flow/custom_cash_flow.py b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
index 45d147e..20f7fcf 100644
--- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
@@ -4,7 +4,8 @@
 
 import frappe
 from frappe import _
-from frappe.utils import add_to_date
+from frappe.query_builder.functions import Sum
+from frappe.utils import add_to_date, get_date_str
 
 from erpnext.accounts.report.financial_statements import get_columns, get_data, get_period_list
 from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
@@ -28,15 +29,22 @@
 
 
 def get_accounts_in_mappers(mapping_names):
-	return frappe.db.sql('''
-		select cfma.name, cfm.label, cfm.is_working_capital, cfm.is_income_tax_liability,
-		cfm.is_income_tax_expense, cfm.is_finance_cost, cfm.is_finance_cost_adjustment
-		from `tabCash Flow Mapping Accounts` cfma
-		join `tabCash Flow Mapping` cfm on cfma.parent=cfm.name
-		where cfma.parent in (%s)
-		order by cfm.is_working_capital
-	''', (', '.join('"%s"' % d for d in mapping_names)))
+	cfm = frappe.qb.DocType('Cash Flow Mapping')
+	cfma = frappe.qb.DocType('Cash Flow Mapping Accounts')
+	result = (
+		frappe.qb
+			.select(
+				cfma.name, cfm.label, cfm.is_working_capital,
+				cfm.is_income_tax_liability, cfm.is_income_tax_expense,
+				cfm.is_finance_cost, cfm.is_finance_cost_adjustment, cfma.account
+			)
+			.from_(cfm)
+			.join(cfma)
+			.on(cfm.name == cfma.parent)
+			.where(cfma.parent.isin(mapping_names))
+		).run()
 
+	return result
 
 def setup_mappers(mappers):
 	cash_flow_accounts = []
@@ -57,31 +65,31 @@
 
 		account_types = [
 			dict(
-				name=account[0], label=account[1], is_working_capital=account[2],
+				name=account[0], account_name=account[7], label=account[1], is_working_capital=account[2],
 				is_income_tax_liability=account[3], is_income_tax_expense=account[4]
 			) for account in accounts if not account[3]]
 
 		finance_costs_adjustments = [
 			dict(
-				name=account[0], label=account[1], is_finance_cost=account[5],
+				name=account[0], account_name=account[7], label=account[1], is_finance_cost=account[5],
 				is_finance_cost_adjustment=account[6]
 			) for account in accounts if account[6]]
 
 		tax_liabilities = [
 			dict(
-				name=account[0], label=account[1], is_income_tax_liability=account[3],
+				name=account[0], account_name=account[7], label=account[1], is_income_tax_liability=account[3],
 				is_income_tax_expense=account[4]
 			) for account in accounts if account[3]]
 
 		tax_expenses = [
 			dict(
-				name=account[0], label=account[1], is_income_tax_liability=account[3],
+				name=account[0], account_name=account[7], label=account[1], is_income_tax_liability=account[3],
 				is_income_tax_expense=account[4]
 			) for account in accounts if account[4]]
 
 		finance_costs = [
 			dict(
-				name=account[0], label=account[1], is_finance_cost=account[5])
+				name=account[0], account_name=account[7], label=account[1], is_finance_cost=account[5])
 			for account in accounts if account[5]]
 
 		account_types_labels = sorted(
@@ -124,27 +132,27 @@
 		)
 
 		for label in account_types_labels:
-			names = [d['name'] for d in account_types if d['label'] == label[0]]
+			names = [d['account_name'] for d in account_types if d['label'] == label[0]]
 			m = dict(label=label[0], names=names, is_working_capital=label[1])
 			mapping['account_types'].append(m)
 
 		for label in fc_adjustment_labels:
-			names = [d['name'] for d in finance_costs_adjustments if d['label'] == label[0]]
+			names = [d['account_name'] for d in finance_costs_adjustments if d['label'] == label[0]]
 			m = dict(label=label[0], names=names)
 			mapping['finance_costs_adjustments'].append(m)
 
 		for label in unique_liability_labels:
-			names = [d['name'] for d in tax_liabilities if d['label'] == label[0]]
+			names = [d['account_name'] for d in tax_liabilities if d['label'] == label[0]]
 			m = dict(label=label[0], names=names, tax_liability=label[1], tax_expense=label[2])
 			mapping['tax_liabilities'].append(m)
 
 		for label in unique_expense_labels:
-			names = [d['name'] for d in tax_expenses if d['label'] == label[0]]
+			names = [d['account_name'] for d in tax_expenses if d['label'] == label[0]]
 			m = dict(label=label[0], names=names, tax_liability=label[1], tax_expense=label[2])
 			mapping['tax_expenses'].append(m)
 
 		for label in unique_finance_costs_labels:
-			names = [d['name'] for d in finance_costs if d['label'] == label[0]]
+			names = [d['account_name'] for d in finance_costs if d['label'] == label[0]]
 			m = dict(label=label[0], names=names, is_finance_cost=label[1])
 			mapping['finance_costs'].append(m)
 
@@ -371,14 +379,30 @@
 
 
 def _get_account_type_based_data(filters, account_names, period_list, accumulated_values, opening_balances=0):
+	if not account_names or not account_names[0] or not type(account_names[0]) == str:
+		# only proceed if account_names is a list of account names
+		return {}
+
 	from erpnext.accounts.report.cash_flow.cash_flow import get_start_date
 
 	company = filters.company
 	data = {}
 	total = 0
+	GLEntry = frappe.qb.DocType('GL Entry')
+	Account = frappe.qb.DocType('Account')
+
 	for period in period_list:
 		start_date = get_start_date(period, accumulated_values, company)
-		accounts = ', '.join('"%s"' % d for d in account_names)
+
+		account_subquery = (
+			frappe.qb.from_(Account)
+			.where(
+				(Account.name.isin(account_names)) |
+				(Account.parent_account.isin(account_names))
+			)
+			.select(Account.name)
+			.as_("account_subquery")
+		)
 
 		if opening_balances:
 			date_info = dict(date=start_date)
@@ -395,32 +419,31 @@
 			else:
 				start, end = add_to_date(**date_info), add_to_date(**date_info)
 
-			gl_sum = frappe.db.sql_list("""
-				select sum(credit) - sum(debit)
-				from `tabGL Entry`
-				where company=%s and posting_date >= %s and posting_date <= %s
-					and voucher_type != 'Period Closing Voucher'
-					and account in ( SELECT name FROM tabAccount WHERE name IN (%s)
-					OR parent_account IN (%s))
-			""", (company, start, end, accounts, accounts))
-		else:
-			gl_sum = frappe.db.sql_list("""
-				select sum(credit) - sum(debit)
-				from `tabGL Entry`
-				where company=%s and posting_date >= %s and posting_date <= %s
-					and voucher_type != 'Period Closing Voucher'
-					and account in ( SELECT name FROM tabAccount WHERE name IN (%s)
-					OR parent_account IN (%s))
-			""", (company, start_date if accumulated_values else period['from_date'],
-				period['to_date'], accounts, accounts))
+			start, end = get_date_str(start), get_date_str(end)
 
-		if gl_sum and gl_sum[0]:
-			amount = gl_sum[0]
 		else:
-			amount = 0
+			start, end = start_date if accumulated_values else period['from_date'], period['to_date']
+			start, end = get_date_str(start), get_date_str(end)
 
-		total += amount
-		data.setdefault(period["key"], amount)
+		result = (
+			frappe.qb.from_(GLEntry)
+			.select(Sum(GLEntry.credit) - Sum(GLEntry.debit))
+			.where(
+				(GLEntry.company == company) &
+				(GLEntry.posting_date >= start) &
+				(GLEntry.posting_date <= end) &
+				(GLEntry.voucher_type != 'Period Closing Voucher') &
+				(GLEntry.account.isin(account_subquery))
+			)
+		).run()
+
+		if result and result[0]:
+			gl_sum = result[0][0]
+		else:
+			gl_sum = 0
+
+		total += gl_sum
+		data.setdefault(period["key"], gl_sum)
 
 	data["total"] = total
 	return data
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
index 86eb213..17475a7 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
@@ -88,10 +88,12 @@
 			posting_date="2021-05-01",
 			parent_cost_center="Main - _CD",
 			cost_center="Main - _CD",
-			do_not_submit=True,
+			do_not_save=True,
 			rate=300,
 			price_list_rate=300,
 		)
+
+		si.items[0].income_account = "Sales - _CD"
 		si.items[0].enable_deferred_revenue = 1
 		si.items[0].service_start_date = "2021-05-01"
 		si.items[0].service_end_date = "2021-08-01"
@@ -269,11 +271,13 @@
 			posting_date="2021-05-01",
 			parent_cost_center="Main - _CD",
 			cost_center="Main - _CD",
-			do_not_submit=True,
+			do_not_save=True,
 			rate=300,
 			price_list_rate=300,
 		)
+
 		si.items[0].enable_deferred_revenue = 1
+		si.items[0].income_account = "Sales - _CD"
 		si.items[0].deferred_revenue_account = deferred_revenue_account
 		si.items[0].income_account = "Sales - _CD"
 		si.save()
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
index 6a03948..ea05a35 100644
--- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
@@ -39,12 +39,14 @@
 				"label": __("From Date"),
 				"fieldtype": "Date",
 				"default": frappe.defaults.get_user_default("year_start_date"),
+				"reqd": 1
 			},
 			{
 				"fieldname": "to_date",
 				"label": __("To Date"),
 				"fieldtype": "Date",
 				"default": frappe.defaults.get_user_default("year_end_date"),
+				"reqd": 1
 			},
 			{
 				"fieldname": "finance_book",
@@ -56,6 +58,7 @@
 				"fieldname": "dimension",
 				"label": __("Select Dimension"),
 				"fieldtype": "Select",
+				"default": "Cost Center",
 				"options": get_accounting_dimension_options(),
 				"reqd": 1,
 			},
@@ -70,7 +73,7 @@
 });
 
 function get_accounting_dimension_options() {
-	let options =["", "Cost Center", "Project"];
+	let options =["Cost Center", "Project"];
 	frappe.db.get_list('Accounting Dimension',
 		{fields:['document_type']}).then((res) => {
 			res.forEach((dimension) => {
diff --git a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py
index d547470..0c2b6cb 100644
--- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.py
@@ -15,20 +15,21 @@
 
 
 def execute(filters=None):
-	validate_filters(filters)
-	dimension_items_list = get_dimension_items_list(filters.dimension, filters.company)
 
-	if not dimension_items_list:
+	validate_filters(filters)
+	dimension_list = get_dimensions(filters)
+
+	if not dimension_list:
 		return [], []
 
-	dimension_items_list = [''.join(d) for d in dimension_items_list]
-	columns = get_columns(dimension_items_list)
-	data = get_data(filters, dimension_items_list)
+	columns = get_columns(dimension_list)
+	data = get_data(filters, dimension_list)
 
 	return columns, data
 
-def get_data(filters, dimension_items_list):
+def get_data(filters, dimension_list):
 	company_currency = erpnext.get_company_currency(filters.company)
+
 	acc = frappe.db.sql("""
 		select
 			name, account_number, parent_account, lft, rgt, root_type,
@@ -51,60 +52,54 @@
 		where lft >= %s and rgt <= %s and company = %s""", (min_lft, max_rgt, filters.company))
 
 	gl_entries_by_account = {}
-	set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account)
-	format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list)
-	accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list)
-	out = prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list)
+	set_gl_entries_by_account(dimension_list, filters, account, gl_entries_by_account)
+	format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_list,
+		frappe.scrub(filters.get('dimension')))
+	accumulate_values_into_parents(accounts, accounts_by_name, dimension_list)
+	out = prepare_data(accounts, filters, company_currency, dimension_list)
 	out = filter_out_zero_value_rows(out, parent_children_map)
 
 	return out
 
-def set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account):
-	for item in dimension_items_list:
-		condition = get_condition(filters.from_date, item, filters.dimension)
-		if account:
-			condition += " and account in ({})"\
-				.format(", ".join([frappe.db.escape(d) for d in account]))
+def set_gl_entries_by_account(dimension_list, filters, account, gl_entries_by_account):
+	condition = get_condition(filters.get('dimension'))
 
-		gl_filters = {
-			"company": filters.get("company"),
-			"from_date": filters.get("from_date"),
-			"to_date": filters.get("to_date"),
-			"finance_book": cstr(filters.get("finance_book"))
-		}
+	if account:
+		condition += " and account in ({})"\
+			.format(", ".join([frappe.db.escape(d) for d in account]))
 
-		gl_filters['item'] = ''.join(item)
+	gl_filters = {
+		"company": filters.get("company"),
+		"from_date": filters.get("from_date"),
+		"to_date": filters.get("to_date"),
+		"finance_book": cstr(filters.get("finance_book"))
+	}
 
-		if filters.get("include_default_book_entries"):
-			gl_filters["company_fb"] = frappe.db.get_value("Company",
-				filters.company, 'default_finance_book')
+	gl_filters['dimensions'] = set(dimension_list)
 
-		for key, value in filters.items():
-			if value:
-				gl_filters.update({
-					key: value
-				})
+	if filters.get("include_default_book_entries"):
+		gl_filters["company_fb"] = frappe.db.get_value("Company",
+			filters.company, 'default_finance_book')
 
-		gl_entries = frappe.db.sql("""
+	gl_entries = frappe.db.sql("""
 		select
-			posting_date, account, debit, credit, is_opening, fiscal_year,
+			posting_date, account, {dimension}, debit, credit, is_opening, fiscal_year,
 			debit_in_account_currency, credit_in_account_currency, account_currency
 		from
 			`tabGL Entry`
 		where
 			company=%(company)s
 		{condition}
+		and posting_date >= %(from_date)s
 		and posting_date <= %(to_date)s
 		and is_cancelled = 0
 		order by account, posting_date""".format(
-			condition=condition),
-			gl_filters, as_dict=True) #nosec
+		dimension = frappe.scrub(filters.get('dimension')), condition=condition), gl_filters, as_dict=True) #nosec
 
-		for entry in gl_entries:
-			entry['dimension_item'] = ''.join(item)
-			gl_entries_by_account.setdefault(entry.account, []).append(entry)
+	for entry in gl_entries:
+		gl_entries_by_account.setdefault(entry.account, []).append(entry)
 
-def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list):
+def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_list, dimension_type):
 
 	for entries in gl_entries_by_account.values():
 		for entry in entries:
@@ -114,11 +109,12 @@
 					_("Could not retrieve information for {0}.").format(entry.account), title="Error",
 					raise_exception=1
 				)
-			for item in dimension_items_list:
-				if item == entry.dimension_item:
-					d[frappe.scrub(item)] = d.get(frappe.scrub(item), 0.0) + flt(entry.debit) - flt(entry.credit)
 
-def prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list):
+			for dimension in dimension_list:
+				if dimension == entry.get(dimension_type):
+					d[frappe.scrub(dimension)] = d.get(frappe.scrub(dimension), 0.0) + flt(entry.debit) - flt(entry.credit)
+
+def prepare_data(accounts, filters, company_currency, dimension_list):
 	data = []
 
 	for d in accounts:
@@ -135,13 +131,13 @@
 				if d.account_number else d.account_name)
 		}
 
-		for item in dimension_items_list:
-			row[frappe.scrub(item)] = flt(d.get(frappe.scrub(item), 0.0), 3)
+		for dimension in dimension_list:
+			row[frappe.scrub(dimension)] = flt(d.get(frappe.scrub(dimension), 0.0), 3)
 
-			if abs(row[frappe.scrub(item)]) >= 0.005:
+			if abs(row[frappe.scrub(dimension)]) >= 0.005:
 				# ignore zero values
 				has_value = True
-				total += flt(d.get(frappe.scrub(item), 0.0), 3)
+				total += flt(d.get(frappe.scrub(dimension), 0.0), 3)
 
 		row["has_value"] = has_value
 		row["total"] = total
@@ -149,62 +145,55 @@
 
 	return data
 
-def accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list):
+def accumulate_values_into_parents(accounts, accounts_by_name, dimension_list):
 	"""accumulate children's values in parent accounts"""
 	for d in reversed(accounts):
 		if d.parent_account:
-			for item in dimension_items_list:
-				accounts_by_name[d.parent_account][frappe.scrub(item)] = \
-					accounts_by_name[d.parent_account].get(frappe.scrub(item), 0.0) + d.get(frappe.scrub(item), 0.0)
+			for dimension in dimension_list:
+				accounts_by_name[d.parent_account][frappe.scrub(dimension)] = \
+					accounts_by_name[d.parent_account].get(frappe.scrub(dimension), 0.0) + d.get(frappe.scrub(dimension), 0.0)
 
-def get_condition(from_date, item, dimension):
+def get_condition(dimension):
 	conditions = []
 
-	if from_date:
-		conditions.append("posting_date >= %(from_date)s")
-	if dimension:
-		if dimension not in ['Cost Center', 'Project']:
-			if dimension in ['Customer', 'Supplier']:
-				dimension = 'Party'
-			else:
-				dimension = 'Voucher No'
-		txt = "{0} = %(item)s".format(frappe.scrub(dimension))
-		conditions.append(txt)
+	conditions.append("{0} in %(dimensions)s".format(frappe.scrub(dimension)))
 
 	return " and {}".format(" and ".join(conditions)) if conditions else ""
 
-def get_dimension_items_list(dimension, company):
-	meta = frappe.get_meta(dimension, cached=False)
-	fieldnames = [d.fieldname for d in meta.get("fields")]
-	filters = {}
-	if 'company' in fieldnames:
-		filters['company'] = company
-	return frappe.get_all(dimension, filters, as_list=True)
+def get_dimensions(filters):
+	meta = frappe.get_meta(filters.get('dimension'), cached=False)
+	query_filters = {}
 
-def get_columns(dimension_items_list, accumulated_values=1, company=None):
+	if meta.has_field('company'):
+		query_filters = {'company': filters.get('company')}
+
+	return frappe.get_all(filters.get('dimension'), filters=query_filters, pluck='name')
+
+def get_columns(dimension_list):
 	columns = [{
 		"fieldname": "account",
 		"label": _("Account"),
 		"fieldtype": "Link",
 		"options": "Account",
 		"width": 300
+	},
+	{
+		"fieldname": "currency",
+		"label": _("Currency"),
+		"fieldtype": "Link",
+		"options": "Currency",
+		"hidden": 1
 	}]
-	if company:
+
+	for dimension in dimension_list:
 		columns.append({
-			"fieldname": "currency",
-			"label": _("Currency"),
-			"fieldtype": "Link",
-			"options": "Currency",
-			"hidden": 1
-		})
-	for item in dimension_items_list:
-		columns.append({
-			"fieldname": frappe.scrub(item),
-			"label": item,
+			"fieldname": frappe.scrub(dimension),
+			"label": dimension,
 			"fieldtype": "Currency",
 			"options": "currency",
 			"width": 150
 		})
+
 	columns.append({
 			"fieldname": "total",
 			"label": "Total",
diff --git a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py
index 3b73628..904513c 100644
--- a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py
+++ b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py
@@ -199,31 +199,39 @@
 	invoice_list = get_invoices(filters)
 	invoice_list_names = ",".join('"' + invoice['name'] + '"' for invoice in invoice_list)
 	if invoice_list:
-		inv_mop_detail = frappe.db.sql("""select a.owner, a.posting_date,
-			ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount
-			from `tabSales Invoice` a, `tabSales Invoice Payment` b
-			where a.name = b.parent
-			and a.docstatus = 1
-			and a.name in ({invoice_list_names})
-			group by a.owner, a.posting_date, mode_of_payment
-			union
-			select a.owner,a.posting_date,
-			ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_paid_amount) as paid_amount
-			from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c
-			where a.name = c.reference_name
-			and b.name = c.parent
-			and b.docstatus = 1
-			and a.name in ({invoice_list_names})
-			group by a.owner, a.posting_date, mode_of_payment
-			union
-			select a.owner, a.posting_date,
-			ifnull(a.voucher_type,'') as mode_of_payment, sum(b.credit)
-			from `tabJournal Entry` a, `tabJournal Entry Account` b
-			where a.name = b.parent
-			and a.docstatus = 1
-			and b.reference_type = "Sales Invoice"
-			and b.reference_name in ({invoice_list_names})
-			group by a.owner, a.posting_date, mode_of_payment
+		inv_mop_detail = frappe.db.sql("""
+			select t.owner,
+			       t.posting_date,
+				   t.mode_of_payment,
+				   sum(t.paid_amount) as paid_amount
+			from (
+				select a.owner, a.posting_date,
+				ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount
+				from `tabSales Invoice` a, `tabSales Invoice Payment` b
+				where a.name = b.parent
+				and a.docstatus = 1
+				and a.name in ({invoice_list_names})
+				group by a.owner, a.posting_date, mode_of_payment
+				union
+				select a.owner,a.posting_date,
+				ifnull(b.mode_of_payment, '') as mode_of_payment, sum(c.allocated_amount) as paid_amount
+				from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c
+				where a.name = c.reference_name
+				and b.name = c.parent
+				and b.docstatus = 1
+				and a.name in ({invoice_list_names})
+				group by a.owner, a.posting_date, mode_of_payment
+				union
+				select a.owner, a.posting_date,
+				ifnull(a.voucher_type,'') as mode_of_payment, sum(b.credit)
+				from `tabJournal Entry` a, `tabJournal Entry Account` b
+				where a.name = b.parent
+				and a.docstatus = 1
+				and b.reference_type = "Sales Invoice"
+				and b.reference_name in ({invoice_list_names})
+				group by a.owner, a.posting_date, mode_of_payment
+			) t
+			group by t.owner, t.posting_date, t.mode_of_payment
 			""".format(invoice_list_names=invoice_list_names), as_dict=1)
 
 		inv_change_amount = frappe.db.sql("""select a.owner, a.posting_date,
@@ -231,7 +239,7 @@
 			from `tabSales Invoice` a, `tabSales Invoice Payment` b
 			where a.name = b.parent
 			and a.name in ({invoice_list_names})
-			and b.mode_of_payment = 'Cash'
+			and b.type = 'Cash'
 			and a.base_change_amount > 0
 			group by a.owner, a.posting_date, mode_of_payment""".format(invoice_list_names=invoice_list_names), as_dict=1)
 
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/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index a94af10..34ff457 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1566,12 +1566,12 @@
 		tax.rate = None
 
 
-def validate_account_head(idx, account, company):
+def validate_account_head(idx, account, company, context=''):
 	account_company = frappe.get_cached_value('Account', account, 'company')
 
 	if account_company != company:
-		frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}')
-			.format(idx, frappe.bold(account), frappe.bold(company)), title=_('Invalid Account'))
+		frappe.throw(_('Row {0}: {3} Account {1} does not belong to Company {2}')
+			.format(idx, frappe.bold(account), frappe.bold(company), context), title=_('Invalid Account'))
 
 
 def validate_cost_center(tax, doc):
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/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/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/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
index c11a821..ae5ac7b 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -8,13 +8,14 @@
 import frappe
 from frappe import _, bold
 from frappe.model.document import Document
-from frappe.utils import date_diff, flt, formatdate, get_last_day, getdate
+from frappe.utils import date_diff, flt, formatdate, get_last_day, get_link_to_form, getdate
 
 
 class LeavePolicyAssignment(Document):
 	def validate(self):
-		self.validate_policy_assignment_overlap()
 		self.set_dates()
+		self.validate_policy_assignment_overlap()
+		self.warn_about_carry_forwarding()
 
 	def on_submit(self):
 		self.grant_leave_alloc_for_employee()
@@ -38,6 +39,20 @@
 			frappe.throw(_("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}")
 				.format(bold(self.leave_policy), bold(self.employee), bold(formatdate(self.effective_from)), bold(formatdate(self.effective_to))))
 
+	def warn_about_carry_forwarding(self):
+		if not self.carry_forward:
+			return
+
+		leave_types = get_leave_type_details()
+		leave_policy = frappe.get_doc("Leave Policy", self.leave_policy)
+
+		for policy in leave_policy.leave_policy_details:
+			leave_type = leave_types.get(policy.leave_type)
+			if not leave_type.is_carry_forward:
+				msg = _("Leaves for the Leave Type {0} won't be carry-forwarded since carry-forwarding is disabled.").format(
+						frappe.bold(get_link_to_form("Leave Type", leave_type.name)))
+				frappe.msgprint(msg, indicator="orange", alert=True)
+
 	@frappe.whitelist()
 	def grant_leave_alloc_for_employee(self):
 		if self.leaves_allocated:
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.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 48cd753..2b6e696 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -1018,21 +1018,21 @@
 	required_qty = item.get("quantity")
 	# get available material by transferring to production warehouse
 	for d in locations:
-		if required_qty <=0: return
+		if required_qty <= 0:
+			return
 
 		new_dict = copy.deepcopy(item)
 		quantity = required_qty if d.get("qty") > required_qty else d.get("qty")
 
-		if required_qty > 0:
-			new_dict.update({
-				"quantity": quantity,
-				"material_request_type": "Material Transfer",
-				"uom": new_dict.get("stock_uom"),  # internal transfer should be in stock UOM
-				"from_warehouse": d.get("warehouse")
-			})
+		new_dict.update({
+			"quantity": quantity,
+			"material_request_type": "Material Transfer",
+			"uom": new_dict.get("stock_uom"),  # internal transfer should be in stock UOM
+			"from_warehouse": d.get("warehouse")
+		})
 
-			required_qty -= quantity
-			new_mr_items.append(new_dict)
+		required_qty -= quantity
+		new_mr_items.append(new_dict)
 
 	# raise purchase request for remaining qty
 	if required_qty:
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 7eb40ec..8d7ab85 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -1150,6 +1150,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 16d8c73..3199912 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -333,6 +333,7 @@
 erpnext.patches.v13_0.delete_bank_reconciliation_detail
 erpnext.patches.v13_0.enable_provisional_accounting
 erpnext.patches.v13_0.non_profit_deprecation_warning
+erpnext.patches.v13_0.enable_ksa_vat_docs #1
 
 [post_model_sync]
 erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
diff --git a/erpnext/patches/v13_0/enable_ksa_vat_docs.py b/erpnext/patches/v13_0/enable_ksa_vat_docs.py
new file mode 100644
index 0000000..3f48262
--- /dev/null
+++ b/erpnext/patches/v13_0/enable_ksa_vat_docs.py
@@ -0,0 +1,12 @@
+import frappe
+
+from erpnext.regional.saudi_arabia.setup import add_permissions, add_print_formats
+
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'})
+	if not company:
+		return
+
+	add_print_formats()
+	add_permissions()
\ No newline at end of file
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..43ee5b3 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -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();
@@ -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 08270bd..f484545 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -75,7 +75,7 @@
 				fieldtype:'Float',
 				read_only: me.has_batch && !me.has_serial_no,
 				label: __(me.has_batch && !me.has_serial_no ? 'Selected Qty' : 'Qty'),
-				default: flt(me.item.stock_qty),
+				default: flt(me.item.stock_qty) || flt(me.item.transfer_qty),
 			},
 			...get_pending_qty_fields(me),
 			{
@@ -94,14 +94,16 @@
 				description: __('Fetch Serial Numbers based on FIFO'),
 				click: () => {
 					let qty = this.dialog.fields_dict.qty.get_value();
+					let already_selected_serial_nos = get_selected_serial_nos(me);
 					let numbers = frappe.call({
 						method: "erpnext.stock.doctype.serial_no.serial_no.auto_fetch_serial_number",
 						args: {
 							qty: qty,
 							item_code: me.item_code,
 							warehouse: typeof me.warehouse_details.name == "string" ? me.warehouse_details.name : '',
-							batch_no: me.item.batch_no || null,
-							posting_date: me.frm.doc.posting_date || me.frm.doc.transaction_date
+							batch_nos: me.item.batch_no || null,
+							posting_date: me.frm.doc.posting_date || me.frm.doc.transaction_date,
+							exclude_sr_nos: already_selected_serial_nos
 						}
 					});
 
@@ -577,21 +579,40 @@
 	return pending_qty_fields;
 }
 
-function calc_total_selected_qty(me) {
+// get all items with same item code except row for which selector is open.
+function get_rows_with_same_item_code(me) {
 	const { frm: { doc: { items }}, item: { name, item_code }} = me;
-	const totalSelectedQty = items
-		.filter( item => ( item.name !== name ) && ( item.item_code === item_code ) )
-		.map( item => flt(item.qty) )
-		.reduce( (i, j) => i + j, 0);
+	return items.filter(item => (item.name !== name) && (item.item_code === item_code))
+}
+
+function calc_total_selected_qty(me) {
+	const totalSelectedQty = get_rows_with_same_item_code(me)
+		.map(item => flt(item.qty))
+		.reduce((i, j) => i + j, 0);
 	return totalSelectedQty;
 }
 
+function get_selected_serial_nos(me) {
+	const selected_serial_nos = get_rows_with_same_item_code(me)
+		.map(item => item.serial_no)
+		.filter(serial => serial)
+		.map(sr_no_string => sr_no_string.split('\n'))
+		.reduce((acc, arr) => acc.concat(arr), [])
+		.filter(serial => serial);
+	return selected_serial_nos;
+};
+
 function check_can_calculate_pending_qty(me) {
 	const { frm: { doc }, item } = me;
 	const docChecks = doc.bom_no
 		&& 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.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 7e99a06..fe2f14e 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -130,6 +130,7 @@
   "per_delivered",
   "column_break_81",
   "per_billed",
+  "per_picked",
   "billing_status",
   "sales_team_section_break",
   "sales_partner",
@@ -1514,13 +1515,19 @@
    "fieldtype": "Currency",
    "label": "Amount Eligible for Commission",
    "read_only": 1
+  },
+  {
+   "fieldname": "per_picked",
+   "fieldtype": "Percent",
+   "label": "% Picked",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-10-05 12:16:40.775704",
+ "modified": "2022-03-15 21:38:31.437586",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order",
@@ -1594,6 +1601,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "timeline_field": "customer",
  "title_field": "customer_name",
  "track_changes": 1,
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/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 7e55499..195e964 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -23,6 +23,7 @@
   "quantity_and_rate",
   "qty",
   "stock_uom",
+  "picked_qty",
   "col_break2",
   "uom",
   "conversion_factor",
@@ -798,12 +799,17 @@
    "fieldtype": "Check",
    "label": "Grant Commission",
    "read_only": 1
+  },
+  {
+   "fieldname": "picked_qty",
+   "fieldtype": "Float",
+   "label": "Picked Qty"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-02-24 14:41:57.325799",
+ "modified": "2022-03-15 20:17:33.984799",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order Item",
diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.js b/erpnext/selling/report/sales_analytics/sales_analytics.js
index 6b03c7d..d527e42 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.js
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.js
@@ -82,62 +82,42 @@
 					const tree_type = frappe.query_report.filters[0].value;
 					if (data_doctype != tree_type) return;
 
-					row_name = data[2].content;
-					length = data.length;
-
-					if (tree_type == "Customer") {
-						row_values = data
-							.slice(4, length - 1)
-							.map(function (column) {
-								return column.content;
-							});
-					} else if (tree_type == "Item") {
-						row_values = data
-							.slice(5, length - 1)
-							.map(function (column) {
-								return column.content;
-							});
-					} else {
-						row_values = data
-							.slice(3, length - 1)
-							.map(function (column) {
-								return column.content;
-							});
-					}
-
-					entry = {
-						name: row_name,
-						values: row_values,
-					};
-
-					let raw_data = frappe.query_report.chart.data;
-					let new_datasets = raw_data.datasets;
-
-					let element_found = new_datasets.some((element, index, array)=>{
-						if(element.name == row_name){
-							array.splice(index, 1)
-							return true
+					const row_name = data[2].content;
+					const raw_data = frappe.query_report.chart.data;
+					const new_datasets = raw_data.datasets;
+					const element_found = new_datasets.some(
+						(element, index, array) => {
+							if (element.name == row_name) {
+								array.splice(index, 1);
+								return true;
+							}
+							return false;
 						}
-						return false
-					})
+					);
+					const slice_at = { Customer: 4, Item: 5 }[tree_type] || 3;
 
 					if (!element_found) {
-						new_datasets.push(entry);
+						new_datasets.push({
+							name: row_name,
+							values: data
+								.slice(slice_at, data.length - 1)
+								.map(column => column.content),
+						});
 					}
 
-					let new_data = {
+					const new_data = {
 						labels: raw_data.labels,
 						datasets: new_datasets,
 					};
-					chart_options = {
+
+					frappe.query_report.render_chart({
 						data: new_data,
 						type: "line",
-					};
-					frappe.query_report.render_chart(chart_options);
+					});
 
 					frappe.query_report.raw_chart_data = new_data;
 				},
 			},
 		});
 	},
-}
+};
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/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.py b/erpnext/stock/doctype/item/item.py
index 8ede955..3abeecd 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -107,6 +107,7 @@
 		self.validate_variant_attributes()
 		self.validate_variant_based_on_change()
 		self.validate_fixed_asset()
+		self.clear_retain_sample()
 		self.validate_retain_sample()
 		self.validate_uom_conversion_factor()
 		self.validate_customer_provided_part()
@@ -209,6 +210,13 @@
 			frappe.throw(_("{0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(
 				self.item_code))
 
+	def clear_retain_sample(self):
+		if not self.has_batch_no:
+			self.retain_sample = None
+
+		if not self.retain_sample:
+			self.sample_quantity = None
+
 	def add_default_uom_in_conversion_factor_table(self):
 		if not self.is_new() and self.has_value_changed("stock_uom"):
 			self.uoms = []
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 669cabc..112420f 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -656,6 +656,19 @@
 		make_stock_entry(qty=1, item_code=item.name, target="_Test Warehouse - _TC", posting_date = add_days(today(), 5))
 		self.consume_item_code_with_differet_stock_transactions(item_code=item.name)
 
+	@change_settings("Stock Settings", {"sample_retention_warehouse": "_Test Warehouse - _TC"})
+	def test_retain_sample(self):
+		item = make_item("_TestRetainSample", {'has_batch_no': 1, 'retain_sample': 1, 'sample_quantity': 1})
+
+		self.assertEqual(item.has_batch_no, 1)
+		self.assertEqual(item.retain_sample, 1)
+		self.assertEqual(item.sample_quantity, 1)
+
+		item.has_batch_no = None
+		item.save()
+		self.assertEqual(item.retain_sample, None)
+		self.assertEqual(item.sample_quantity, None)
+		item.delete()
 
 	def consume_item_code_with_differet_stock_transactions(self, item_code, warehouse="_Test Warehouse - _TC"):
 		from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 51209ac..49fefae 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -82,6 +82,9 @@
 		self.reset_default_field_value("set_warehouse", "items", "warehouse")
 		self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
 
+	def before_update_after_submit(self):
+		self.validate_schedule_date()
+
 	def validate_material_request_type(self):
 		""" Validate fields in accordance with selected type """
 
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index 2bad42a..dd66cff 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -177,6 +177,7 @@
    "fieldtype": "Column Break"
   },
   {
+   "allow_on_submit": 1,
    "bold": 1,
    "columns": 2,
    "fieldname": "schedule_date",
@@ -459,7 +460,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-11-03 14:40:24.409826",
+ "modified": "2022-03-10 18:42:42.705190",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Material Request Item",
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 730fd7a..13b74b5 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -146,10 +146,6 @@
 			customer: frm.doc.customer
 		};
 		frm.get_items_btn = frm.add_custom_button(__('Get Items'), () => {
-			if (!frm.doc.customer) {
-				frappe.msgprint(__('Please select Customer first'));
-				return;
-			}
 			erpnext.utils.map_current_doc({
 				method: 'erpnext.selling.doctype.sales_order.sales_order.create_pick_list',
 				source_doctype: 'Sales Order',
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index b2eaecb..3a49686 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -3,6 +3,8 @@
 
 import json
 from collections import OrderedDict, defaultdict
+from itertools import groupby
+from operator import itemgetter
 
 import frappe
 from frappe import _
@@ -24,8 +26,21 @@
 	def before_save(self):
 		self.set_item_locations()
 
+		# set percentage picked in SO
+		for location in self.get('locations'):
+			if location.sales_order and frappe.db.get_value("Sales Order",location.sales_order,"per_picked") == 100:
+				frappe.throw("Row " + str(location.idx) + " has been picked already!")
+
 	def before_submit(self):
 		for item in self.locations:
+			# if the user has not entered any picked qty, set it to stock_qty, before submit
+			if item.picked_qty == 0:
+				item.picked_qty = item.stock_qty
+
+			if item.sales_order_item:
+				# update the picked_qty in SO Item
+				self.update_so(item.sales_order_item,item.picked_qty,item.item_code)
+
 			if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'):
 				continue
 			if not item.serial_no:
@@ -37,6 +52,32 @@
 			frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
 				.format(frappe.bold(item.item_code), frappe.bold(item.idx)), title=_("Quantity Mismatch"))
 
+	def before_cancel(self):
+		#update picked_qty in SO Item on cancel of PL
+		for location in self.get('locations'):
+			if location.sales_order_item:
+				self.update_so(location.sales_order_item,0,location.item_code)
+
+	def update_so(self,so_item,picked_qty,item_code):
+		so_doc = frappe.get_doc("Sales Order",frappe.db.get_value("Sales Order Item",so_item,"parent"))
+		already_picked,actual_qty = frappe.db.get_value("Sales Order Item",so_item,["picked_qty","qty"])
+
+		if self.docstatus == 1:
+			if (((already_picked + picked_qty)/ actual_qty)*100) > (100 + flt(frappe.db.get_single_value('Stock Settings', 'over_delivery_receipt_allowance'))):
+				frappe.throw('You are picking more than required quantity for ' + item_code + '. Check if there is any other pick list created for '+so_doc.name)
+
+		frappe.db.set_value("Sales Order Item",so_item,"picked_qty",already_picked+picked_qty)
+
+		total_picked_qty = 0
+		total_so_qty = 0
+		for item in so_doc.get('items'):
+			total_picked_qty += flt(item.picked_qty)
+			total_so_qty += flt(item.stock_qty)
+		total_picked_qty=total_picked_qty + picked_qty
+		per_picked = total_picked_qty/total_so_qty * 100
+
+		so_doc.db_set("per_picked", flt(per_picked) ,update_modified=False)
+
 	@frappe.whitelist()
 	def set_item_locations(self, save=False):
 		self.validate_for_qty()
@@ -64,10 +105,6 @@
 			item_doc.name = None
 
 			for row in locations:
-				row.update({
-					'picked_qty': row.stock_qty
-				})
-
 				location = item_doc.as_dict()
 				location.update(row)
 				self.append('locations', location)
@@ -340,63 +377,102 @@
 def create_delivery_note(source_name, target_doc=None):
 	pick_list = frappe.get_doc('Pick List', source_name)
 	validate_item_locations(pick_list)
-
-	sales_orders = [d.sales_order for d in pick_list.locations if d.sales_order]
-	sales_orders = set(sales_orders)
-
+	sales_dict = dict()
+	sales_orders = []
 	delivery_note = None
-	for sales_order in sales_orders:
-		delivery_note = create_delivery_note_from_sales_order(sales_order,
-			delivery_note, skip_item_mapping=True)
+	for location in pick_list.locations:
+		if location.sales_order:
+			sales_orders.append([frappe.db.get_value("Sales Order",location.sales_order,'customer'),location.sales_order])
+	# Group sales orders by customer
+	for key,keydata in groupby(sales_orders,key=itemgetter(0)):
+		sales_dict[key] = set([d[1] for d in keydata])
 
-	# map rows without sales orders as well
-	if not delivery_note:
+	if sales_dict:
+		delivery_note = create_dn_with_so(sales_dict,pick_list)
+
+	is_item_wo_so = 0
+	for location in pick_list.locations :
+		if not location.sales_order:
+			is_item_wo_so = 1
+			break
+	if is_item_wo_so == 1:
+		# Create a DN for items without sales orders as well
+		delivery_note = create_dn_wo_so(pick_list)
+
+	frappe.msgprint(_('Delivery Note(s) created for the Pick List'))
+	return delivery_note
+
+def create_dn_wo_so(pick_list):
 		delivery_note = frappe.new_doc("Delivery Note")
 
-	item_table_mapper = {
-		'doctype': 'Delivery Note Item',
-		'field_map': {
-			'rate': 'rate',
-			'name': 'so_detail',
-			'parent': 'against_sales_order',
-		},
-		'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
-	}
-
-	item_table_mapper_without_so = {
-		'doctype': 'Delivery Note Item',
-		'field_map': {
-			'rate': 'rate',
-			'name': 'name',
-			'parent': '',
+		item_table_mapper_without_so = {
+			'doctype': 'Delivery Note Item',
+			'field_map': {
+				'rate': 'rate',
+				'name': 'name',
+				'parent': '',
+			}
 		}
-	}
+		map_pl_locations(pick_list,item_table_mapper_without_so,delivery_note)
+		delivery_note.insert(ignore_mandatory = True)
+
+		return delivery_note
+
+
+def create_dn_with_so(sales_dict,pick_list):
+	delivery_note = None
+
+	for customer in sales_dict:
+		for so in sales_dict[customer]:
+			delivery_note = None
+			delivery_note = create_delivery_note_from_sales_order(so,
+				delivery_note, skip_item_mapping=True)
+
+			item_table_mapper = {
+				'doctype': 'Delivery Note Item',
+				'field_map': {
+					'rate': 'rate',
+					'name': 'so_detail',
+					'parent': 'against_sales_order',
+				},
+				'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
+			}
+			break
+		if delivery_note:
+			# map all items of all sales orders of that customer
+			for so in sales_dict[customer]:
+				map_pl_locations(pick_list,item_table_mapper,delivery_note,so)
+			delivery_note.insert(ignore_mandatory = True)
+
+	return delivery_note
+
+def map_pl_locations(pick_list,item_mapper,delivery_note,sales_order = None):
 
 	for location in pick_list.locations:
-		if location.sales_order_item:
-			sales_order_item = frappe.get_cached_doc('Sales Order Item', {'name':location.sales_order_item})
-		else:
-			sales_order_item = None
+		if location.sales_order == sales_order:
+			if location.sales_order_item:
+				sales_order_item = frappe.get_cached_doc('Sales Order Item', {'name':location.sales_order_item})
+			else:
+				sales_order_item = None
 
-		source_doc, table_mapper = [sales_order_item, item_table_mapper] if sales_order_item \
-			else [location, item_table_mapper_without_so]
+			source_doc, table_mapper = [sales_order_item, item_mapper] if sales_order_item \
+				else [location, item_mapper]
 
-		dn_item = map_child_doc(source_doc, delivery_note, table_mapper)
+			dn_item = map_child_doc(source_doc, delivery_note, table_mapper)
 
-		if dn_item:
-			dn_item.warehouse = location.warehouse
-			dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1)
-			dn_item.batch_no = location.batch_no
-			dn_item.serial_no = location.serial_no
+			if dn_item:
+				dn_item.warehouse = location.warehouse
+				dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1)
+				dn_item.batch_no = location.batch_no
+				dn_item.serial_no = location.serial_no
 
-			update_delivery_note_item(source_doc, dn_item, delivery_note)
-
+				update_delivery_note_item(source_doc, dn_item, delivery_note)
 	set_delivery_note_missing_values(delivery_note)
 
 	delivery_note.pick_list = pick_list.name
-	delivery_note.customer = pick_list.customer if pick_list.customer else None
+	delivery_note.company = pick_list.company
+	delivery_note.customer = frappe.get_value("Sales Order",sales_order,"customer")
 
-	return delivery_note
 
 @frappe.whitelist()
 def create_stock_entry(pick_list):
@@ -561,4 +637,4 @@
 	item.material_request = location.material_request
 	item.serial_no = location.serial_no
 	item.batch_no = location.batch_no
-	item.material_request_item = location.material_request_item
+	item.material_request_item = location.material_request_item
\ No newline at end of file
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index f3b6b89..f60104c 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -17,7 +17,6 @@
 
 
 class TestPickList(FrappeTestCase):
-
 	def test_pick_list_picks_warehouse_for_each_item(self):
 		try:
 			frappe.get_doc({
@@ -188,7 +187,6 @@
 			}]
 		})
 		pick_list.set_item_locations()
-
 		self.assertEqual(pick_list.locations[0].batch_no, oldest_batch_no)
 
 		pr1.cancel()
@@ -311,6 +309,7 @@
 					'item_code': '_Test Item',
 					'qty': 1,
 					'conversion_factor': 5,
+					'stock_qty':5,
 					'delivery_date': frappe.utils.today()
 				}, {
 					'item_code': '_Test Item',
@@ -329,9 +328,9 @@
 			'purpose': 'Delivery',
 			'locations': [{
 				'item_code': '_Test Item',
-				'qty': 1,
-				'stock_qty': 5,
-				'conversion_factor': 5,
+				'qty': 2,
+				'stock_qty': 1,
+				'conversion_factor': 0.5,
 				'sales_order': sales_order.name,
 				'sales_order_item': sales_order.items[0].name ,
 			}, {
@@ -389,6 +388,95 @@
 		for expected_item, created_item in zip(expected_items, pl.locations):
 			_compare_dicts(expected_item, created_item)
 
+	def test_multiple_dn_creation(self):
+		sales_order_1 = frappe.get_doc({
+				'doctype': 'Sales Order',
+				'customer': '_Test Customer',
+				'company': '_Test Company',
+				'items': [{
+					'item_code': '_Test Item',
+					'qty': 1,
+					'conversion_factor': 1,
+					'delivery_date': frappe.utils.today()
+				}],
+			}).insert()
+		sales_order_1.submit()
+		sales_order_2 = frappe.get_doc({
+				'doctype': 'Sales Order',
+				'customer': '_Test Customer 1',
+				'company': '_Test Company',
+				'items': [{
+					'item_code': '_Test Item 2',
+					'qty': 1,
+					'conversion_factor': 1,
+					'delivery_date': frappe.utils.today()
+				},
+				],
+			}).insert()
+		sales_order_2.submit()
+		pick_list = frappe.get_doc({
+			'doctype': 'Pick List',
+			'company': '_Test Company',
+			'items_based_on': 'Sales Order',
+			'purpose': 'Delivery',
+			'picker':'P001',
+			'locations': [{
+				'item_code': '_Test Item ',
+				'qty': 1,
+				'stock_qty': 1,
+				'conversion_factor': 1,
+				'sales_order': sales_order_1.name,
+				'sales_order_item': sales_order_1.items[0].name ,
+			}, {
+				'item_code': '_Test Item 2',
+				'qty': 1,
+				'stock_qty': 1,
+				'conversion_factor': 1,
+				'sales_order': sales_order_2.name,
+				'sales_order_item': sales_order_2.items[0].name ,
+			}
+			]
+		})
+		pick_list.set_item_locations()
+		pick_list.submit()
+		create_delivery_note(pick_list.name)
+		for dn in frappe.get_all("Delivery Note",filters={"pick_list":pick_list.name,"customer":"_Test Customer"},fields={"name"}):
+			for dn_item in frappe.get_doc("Delivery Note",dn.name).get("items"):
+				self.assertEqual(dn_item.item_code, '_Test Item')
+				self.assertEqual(dn_item.against_sales_order,sales_order_1.name)
+		for dn in frappe.get_all("Delivery Note",filters={"pick_list":pick_list.name,"customer":"_Test Customer 1"},fields={"name"}):
+			for dn_item in frappe.get_doc("Delivery Note",dn.name).get("items"):
+				self.assertEqual(dn_item.item_code, '_Test Item 2')
+				self.assertEqual(dn_item.against_sales_order,sales_order_2.name)
+		#test DN creation without so
+		pick_list_1 = frappe.get_doc({
+			'doctype': 'Pick List',
+			'company': '_Test Company',
+			'purpose': 'Delivery',
+			'picker':'P001',
+			'locations': [{
+				'item_code': '_Test Item ',
+				'qty': 1,
+				'stock_qty': 1,
+				'conversion_factor': 1,
+			}, {
+				'item_code': '_Test Item 2',
+				'qty': 2,
+				'stock_qty': 2,
+				'conversion_factor': 1,
+			}
+			]
+		})
+		pick_list_1.set_item_locations()
+		pick_list_1.submit()
+		create_delivery_note(pick_list_1.name)
+		for dn in frappe.get_all("Delivery Note",filters={"pick_list":pick_list_1.name},fields={"name"}):
+			for dn_item in frappe.get_doc("Delivery Note",dn.name).get("items"):
+				if dn_item.item_code == '_Test Item':
+					self.assertEqual(dn_item.qty,1)
+				if dn_item.item_code == '_Test Item 2':
+					self.assertEqual(dn_item.qty,2)
+
 	# def test_pick_list_skips_items_in_expired_batch(self):
 	# 	pass
 
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/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index df872af..66c7707 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -3,11 +3,22 @@
 
 
 import json
+from typing import List, Optional, Union
 
 import frappe
 from frappe import ValidationError, _
 from frappe.model.naming import make_autoname
-from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate
+from frappe.query_builder.functions import Coalesce
+from frappe.utils import (
+	add_days,
+	cint,
+	cstr,
+	flt,
+	get_link_to_form,
+	getdate,
+	nowdate,
+	safe_json_loads,
+)
 
 from erpnext.controllers.stock_controller import StockController
 from erpnext.stock.get_item_details import get_reserved_qty_for_so
@@ -564,26 +575,41 @@
 	return serial_nos
 
 @frappe.whitelist()
-def auto_fetch_serial_number(qty, item_code, warehouse, posting_date=None, batch_nos=None, for_doctype=None):
-	filters = { "item_code": item_code, "warehouse": warehouse }
+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:
-		try:
-			filters["batch_no"] = json.loads(batch_nos) if (type(json.loads(batch_nos)) == list) else [json.loads(batch_nos)]
-		except Exception:
-			filters["batch_no"] = [batch_nos]
+		batch_nos = safe_json_loads(batch_nos)
+		if isinstance(batch_nos, list):
+			filters.batch_no = batch_nos
+		else:
+			filters.batch_no = [str(batch_nos)]
 
 	if posting_date:
-		filters["expiry_date"] = posting_date
+		filters.expiry_date = posting_date
 
 	serial_numbers = []
 	if for_doctype == 'POS Invoice':
-		reserved_sr_nos = get_pos_reserved_serial_nos(filters)
-		serial_numbers = fetch_serial_numbers(filters, qty, do_not_include=reserved_sr_nos)
-	else:
-		serial_numbers = fetch_serial_numbers(filters, qty)
+		exclude_sr_nos.extend(get_pos_reserved_serial_nos(filters))
 
-	return [d.get('name') for d in serial_numbers]
+	serial_numbers = fetch_serial_numbers(filters, qty, do_not_include=exclude_sr_nos)
+
+	return sorted([d.get('name') for d in serial_numbers])
 
 def get_delivered_serial_nos(serial_nos):
 	'''
@@ -650,37 +676,37 @@
 def fetch_serial_numbers(filters, qty, do_not_include=None):
 	if do_not_include is None:
 		do_not_include = []
-	batch_join_selection = ""
-	batch_no_condition = ""
+
 	batch_nos = filters.get("batch_no")
 	expiry_date = filters.get("expiry_date")
+	serial_no = frappe.qb.DocType("Serial No")
+
+	query = (
+		frappe.qb
+			.from_(serial_no)
+			.select(serial_no.name)
+			.where(
+				(serial_no.item_code == filters["item_code"])
+				& (serial_no.warehouse == filters["warehouse"])
+				& (Coalesce(serial_no.sales_invoice, "") == "")
+				& (Coalesce(serial_no.delivery_document_no, "") == "")
+			)
+			.orderby(serial_no.creation)
+			.limit(qty or 1)
+	)
+
+	if do_not_include:
+		query = query.where(serial_no.name.notin(do_not_include))
+
 	if batch_nos:
-		batch_no_condition = """and sr.batch_no in ({}) """.format(', '.join("'%s'" % d for d in batch_nos))
+		query = query.where(serial_no.batch_no.isin(batch_nos))
 
 	if expiry_date:
-		batch_join_selection = "LEFT JOIN `tabBatch` batch on sr.batch_no = batch.name "
-		expiry_date_cond = "AND ifnull(batch.expiry_date, '2500-12-31') >= %(expiry_date)s "
-		batch_no_condition += expiry_date_cond
+		batch = frappe.qb.DocType("Batch")
+		query = (query
+			.left_join(batch).on(serial_no.batch_no == batch.name)
+			.where(Coalesce(batch.expiry_date, "4000-12-31") >= expiry_date)
+		)
 
-	excluded_sr_nos = ", ".join(["" + frappe.db.escape(sr) + "" for sr in do_not_include]) or "''"
-	serial_numbers = frappe.db.sql("""
-		SELECT sr.name FROM `tabSerial No` sr {batch_join_selection}
-		WHERE
-			sr.name not in ({excluded_sr_nos}) AND
-			sr.item_code = %(item_code)s AND
-			sr.warehouse = %(warehouse)s AND
-			ifnull(sr.sales_invoice,'') = '' AND
-			ifnull(sr.delivery_document_no, '') = ''
-			{batch_no_condition}
-		ORDER BY
-			sr.creation
-		LIMIT
-			{qty}
-		""".format(
-				excluded_sr_nos=excluded_sr_nos,
-				qty=qty or 1,
-				batch_join_selection=batch_join_selection,
-				batch_no_condition=batch_no_condition
-			), filters, as_dict=1)
-
+	serial_numbers = query.run(as_dict=True)
 	return serial_numbers
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index 057a7d4..7df0a56 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -6,10 +6,12 @@
 
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from erpnext.stock.doctype.serial_no.serial_no import *
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
@@ -18,9 +20,6 @@
 test_dependencies = ["Item"]
 test_records = frappe.get_test_records('Serial No')
 
-from frappe.tests.utils import FrappeTestCase
-
-from erpnext.stock.doctype.serial_no.serial_no import *
 
 
 class TestSerialNo(FrappeTestCase):
@@ -242,3 +241,57 @@
 			)
 		self.assertEqual(value_diff, -113)
 
+	def test_auto_fetch(self):
+		item_code = make_item(properties={
+			"has_serial_no": 1,
+			"has_batch_no": 1,
+			"create_new_batch": 1,
+			"serial_no_series": "TEST.#######"
+		}).name
+		warehouse = "_Test Warehouse - _TC"
+
+		in1 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=5)
+		in2 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=5)
+
+		in1.reload()
+		in2.reload()
+
+		batch1 = in1.items[0].batch_no
+		batch2 = in2.items[0].batch_no
+
+		batch_wise_serials = {
+			batch1 : get_serial_nos(in1.items[0].serial_no),
+			batch2: get_serial_nos(in2.items[0].serial_no)
+		}
+
+		# Test FIFO
+		first_fetch = auto_fetch_serial_number(5, item_code, warehouse)
+		self.assertEqual(first_fetch, batch_wise_serials[batch1])
+
+		# partial FIFO
+		partial_fetch = auto_fetch_serial_number(2, item_code, warehouse)
+		self.assertTrue(set(partial_fetch).issubset(set(first_fetch)),
+				msg=f"{partial_fetch} should be subset of {first_fetch}")
+
+		# exclusion
+		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
+		for batch, expected_serials in batch_wise_serials.items():
+			fetched_sr = auto_fetch_serial_number(5, item_code, warehouse, batch_nos=batch)
+			self.assertEqual(fetched_sr, sorted(expected_serials))
+
+		# non existing warehouse
+		self.assertEqual(auto_fetch_serial_number(10, item_code, warehouse="Nonexisting"), [])
+
+		# multi batch
+		all_serials = [sr for sr_list in batch_wise_serials.values() for sr in sr_list]
+		fetched_serials = auto_fetch_serial_number(10, item_code, warehouse, batch_nos=list(batch_wise_serials.keys()))
+		self.assertEqual(sorted(all_serials), fetched_serials)
+
+		# expiry date
+		frappe.db.set_value("Batch", batch1, "expiry_date", "1980-01-01")
+		non_expired_serials = auto_fetch_serial_number(5, item_code, warehouse, posting_date="2021-01-01", batch_nos=batch1)
+		self.assertEqual(non_expired_serials, [])
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index c538307..2f59304 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -26,6 +26,8 @@
 		name will be changed using autoname options (in a scheduled job)
 		"""
 		self.name = frappe.generate_hash(txt="", length=10)
+		if self.meta.autoname == "hash":
+			self.to_rename = 0
 
 	def validate(self):
 		self.flags.ignore_submit_comment = True
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index 2352235..fc57995 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -7,9 +7,11 @@
 
 import frappe
 from frappe.core.page.permission_manager.permission_manager import reset
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_days, today
 
+from erpnext.accounts.doctype.gl_entry.gl_entry import rename_gle_sle_docs
 from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
@@ -939,3 +941,62 @@
 	# Used to isolate valuation sensitive
 	# tests to prevent future tests from failing.
 	return str(uuid4())[:8].upper()
+
+
+class TestDeferredNaming(FrappeTestCase):
+
+	@classmethod
+	def setUpClass(cls) -> None:
+		super().setUpClass()
+		cls.gle_autoname = frappe.get_meta("GL Entry").autoname
+		cls.sle_autoname = frappe.get_meta("Stock Ledger Entry").autoname
+
+	def setUp(self) -> None:
+		self.item = make_item().name
+		self.warehouse = "Stores - TCP1"
+		self.company = "_Test Company with perpetual inventory"
+
+	def tearDown(self) -> None:
+		make_property_setter(doctype="GL Entry", for_doctype=True,
+				property="autoname", value=self.gle_autoname, property_type="Data", fieldname=None)
+		make_property_setter(doctype="Stock Ledger Entry", for_doctype=True,
+				property="autoname", value=self.sle_autoname, property_type="Data", fieldname=None)
+
+		# since deferred naming autocommits, commit all changes to avoid flake
+		frappe.db.commit()  # nosemgrep
+
+	@staticmethod
+	def get_gle_sles(se):
+		filters = {"voucher_type": se.doctype, "voucher_no": se.name}
+		gle = set(frappe.get_list("GL Entry", filters, pluck="name"))
+		sle = set(frappe.get_list("Stock Ledger Entry", filters, pluck="name"))
+		return gle, sle
+
+	def test_deferred_naming(self):
+		se = make_stock_entry(item_code=self.item, to_warehouse=self.warehouse,
+				qty=10, rate=100, company=self.company)
+
+		gle, sle = self.get_gle_sles(se)
+		rename_gle_sle_docs()
+		renamed_gle, renamed_sle  = self.get_gle_sles(se)
+
+		self.assertFalse(gle & renamed_gle, msg="GLEs not renamed")
+		self.assertFalse(sle & renamed_sle, msg="SLEs not renamed")
+		se.cancel()
+
+	def test_hash_naming(self):
+		# disable naming series
+		for doctype in ("GL Entry", "Stock Ledger Entry"):
+			make_property_setter(doctype=doctype, for_doctype=True,
+					property="autoname", value="hash", property_type="Data", fieldname=None)
+
+		se = make_stock_entry(item_code=self.item, to_warehouse=self.warehouse,
+				qty=10, rate=100, company=self.company)
+
+		gle, sle = self.get_gle_sles(se)
+		rename_gle_sle_docs()
+		renamed_gle, renamed_sle  = self.get_gle_sles(se)
+
+		self.assertEqual(gle, renamed_gle, msg="GLEs are renamed while using hash naming")
+		self.assertEqual(sle, renamed_sle, msg="SLEs are renamed while using hash naming")
+		se.cancel()
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py
index cdb7719..08d7c99 100644
--- a/erpnext/stock/doctype/warehouse/test_warehouse.py
+++ b/erpnext/stock/doctype/warehouse/test_warehouse.py
@@ -4,12 +4,12 @@
 import frappe
 from frappe.test_runner import make_test_records
 from frappe.tests.utils import FrappeTestCase
-from frappe.utils import cint
 
 import erpnext
-from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
+from erpnext.accounts.doctype.account.test_account import create_account
 from erpnext.stock.doctype.item.test_item import create_item
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.stock.doctype.warehouse.warehouse import convert_to_group_or_ledger, get_children
 
 test_records = frappe.get_test_records('Warehouse')
 
@@ -65,6 +65,33 @@
 					f"{item} linked to {item_default.default_warehouse} in {warehouse_ids}."
 				)
 
+	def test_group_non_group_conversion(self):
+
+		warehouse = frappe.get_doc("Warehouse", create_warehouse("TestGroupConversion"))
+
+		convert_to_group_or_ledger(warehouse.name)
+		warehouse.reload()
+		self.assertEqual(warehouse.is_group, 1)
+
+		child = create_warehouse("GroupWHChild", {"parent_warehouse": warehouse.name})
+		# chid exists
+		self.assertRaises(frappe.ValidationError, convert_to_group_or_ledger, warehouse.name)
+		frappe.delete_doc("Warehouse", child)
+
+		convert_to_group_or_ledger(warehouse.name)
+		warehouse.reload()
+		self.assertEqual(warehouse.is_group, 0)
+
+		make_stock_entry(item_code="_Test Item", target=warehouse.name, qty=1)
+		# SLE exists
+		self.assertRaises(frappe.ValidationError, convert_to_group_or_ledger, warehouse.name)
+
+	def test_get_children(self):
+		company = "_Test Company"
+
+		children = get_children("Warehouse", parent=company, company=company, is_root=True)
+		self.assertTrue(any(wh['value'] == "_Test Warehouse - _TC" for wh in children))
+
 
 def create_warehouse(warehouse_name, properties=None, company=None):
 	if not company:
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 9cfad86..4c7f41d 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -41,14 +41,11 @@
 
 	def on_trash(self):
 		# delete bin
-		bins = frappe.db.sql("select * from `tabBin` where warehouse = %s",
-			self.name, as_dict=1)
+		bins = frappe.get_all("Bin", fields="*", filters={"warehouse": self.name})
 		for d in bins:
 			if d['actual_qty'] or d['reserved_qty'] or d['ordered_qty'] or \
 					d['indented_qty'] or d['projected_qty'] or d['planned_qty']:
 				throw(_("Warehouse {0} can not be deleted as quantity exists for Item {1}").format(self.name, d['item_code']))
-			else:
-				frappe.db.sql("delete from `tabBin` where name = %s", d['name'])
 
 		if self.check_if_sle_exists():
 			throw(_("Warehouse can not be deleted as stock ledger entry exists for this warehouse."))
@@ -56,16 +53,15 @@
 		if self.check_if_child_exists():
 			throw(_("Child warehouse exists for this warehouse. You can not delete this warehouse."))
 
+		frappe.db.delete("Bin", filters={"warehouse": self.name})
 		self.update_nsm_model()
 		self.unlink_from_items()
 
 	def check_if_sle_exists(self):
-		return frappe.db.sql("""select name from `tabStock Ledger Entry`
-			where warehouse = %s limit 1""", self.name)
+		return frappe.db.exists("Stock Ledger Entry", {"warehouse": self.name})
 
 	def check_if_child_exists(self):
-		return frappe.db.sql("""select name from `tabWarehouse`
-			where parent_warehouse = %s limit 1""", self.name)
+		return frappe.db.exists("Warehouse", {"parent_warehouse": self.name})
 
 	def convert_to_group_or_ledger(self):
 		if self.is_group:
@@ -92,10 +88,7 @@
 			return 1
 
 	def unlink_from_items(self):
-		frappe.db.sql("""
-				update `tabItem Default`
-				set default_warehouse=NULL
-				where default_warehouse=%s""", self.name)
+		frappe.db.set_value("Item Default", {"default_warehouse": self.name}, "default_warehouse", None)
 
 @frappe.whitelist()
 def get_children(doctype, parent=None, company=None, is_root=False):
@@ -164,15 +157,16 @@
 	frappe.get_doc(args).insert()
 
 @frappe.whitelist()
-def convert_to_group_or_ledger():
-	args = frappe.form_dict
-	return frappe.get_doc("Warehouse", args.docname).convert_to_group_or_ledger()
+def convert_to_group_or_ledger(docname=None):
+	if not docname:
+		docname = frappe.form_dict.docname
+	return frappe.get_doc("Warehouse", docname).convert_to_group_or_ledger()
 
 def get_child_warehouses(warehouse):
-	lft, rgt = frappe.get_cached_value("Warehouse", warehouse, ["lft", "rgt"])
+	from frappe.utils.nestedset import get_descendants_of
 
-	return frappe.db.sql_list("""select name from `tabWarehouse`
-		where lft >= %s and rgt <= %s""", (lft, rgt))
+	children = get_descendants_of("Warehouse", warehouse, ignore_permissions=True, order_by="lft")
+	return children + [warehouse]   # append self for backward compatibility
 
 def get_warehouses_based_on_account(account, company=None):
 	warehouses = []
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index 97a740e..7ca4003 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -12,7 +12,7 @@
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
 Filters = frappe._dict
-precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
+
 
 def execute(filters: Filters = None) -> Tuple:
 	to_date = filters["to_date"]
@@ -30,6 +30,8 @@
 	_func = itemgetter(1)
 	data = []
 
+	precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True))
+
 	for item, item_dict in item_details.items():
 		earliest_age, latest_age = 0, 0
 		details = item_dict["details"]
@@ -76,6 +78,9 @@
 	return flt(age_qty / total_qty, 2) if total_qty else 0.0
 
 def get_range_age(filters: Filters, fifo_queue: List, to_date: str, item_dict: Dict) -> Tuple:
+
+	precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True))
+
 	range1 = range2 = range3 = above_range3 = 0.0
 
 	for item in fifo_queue:
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/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>