Merge branch 'develop' into loan_cost_center
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
index f8d191c..82705a9 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -64,10 +64,10 @@
 				<td></td>
 				<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or "&nbsp;" }}</b></td>
 				<td style="text-align: right">
-					{{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
+					{{ row.get('account', '') and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
 				</td>
 				<td style="text-align: right">
-					{{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
+					{{ row.get('account', '') and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
 				</td>
 			{% endif %}
 				<td style="text-align: right">
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
index 088c190..29f2e98 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
@@ -51,6 +51,13 @@
 				}
 			}
 		});
+		frm.set_query("account", function() {
+			return {
+				filters: {
+					'company': frm.doc.company
+				}
+			};
+		});
 		if(frm.doc.__islocal){
 			frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1));
 			frm.set_value('to_date', frappe.datetime.get_today());
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 973c837..80b95db 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -9,7 +9,6 @@
   "customer_section",
   "title",
   "naming_series",
-  "tax_invoice_number",
   "customer",
   "customer_name",
   "tax_id",
@@ -2027,12 +2026,6 @@
    "fieldtype": "Currency",
    "label": "Amount Eligible for Commission",
    "read_only": 1
-  },
-  {
-   "fieldname": "tax_invoice_number",
-   "fieldtype": "Data",
-   "label": "Tax Invoice Number",
-   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
@@ -2045,7 +2038,7 @@
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2022-03-07 16:08:53.517903",
+ "modified": "2022-03-08 16:08:53.517903",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 573da27..862ac81 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1255,14 +1255,14 @@
 	def update_billing_status_in_dn(self, update_modified=True):
 		updated_delivery_notes = []
 		for d in self.get("items"):
-			if d.so_detail:
-				updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
-			elif d.dn_detail:
+			if d.dn_detail:
 				billed_amt = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
 					where dn_detail=%s and docstatus=1""", d.dn_detail)
 				billed_amt = billed_amt and billed_amt[0][0] or 0
 				frappe.db.set_value("Delivery Note Item", d.dn_detail, "billed_amt", billed_amt, update_modified=update_modified)
 				updated_delivery_notes.append(d.delivery_note)
+			elif d.so_detail:
+				updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
 
 		for dn in set(updated_delivery_notes):
 			frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified)
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
index 792e7d2..7e51299 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
@@ -71,8 +71,7 @@
 		if doc.currency != doc.company_currency:
 			shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)
 
-		if shipping_amount:
-			self.add_shipping_rule_to_tax_table(doc, shipping_amount)
+		self.add_shipping_rule_to_tax_table(doc, shipping_amount)
 
 	def get_shipping_amount_from_rules(self, value):
 		for condition in self.get("conditions"):
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index d6f6c5b..791cd1d 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -151,7 +151,7 @@
 
 def set_other_values(party_details, party, party_type):
 	# copy
-	if party_type=="Customer":
+	if party_type == "Customer":
 		to_copy = ["customer_name", "customer_group", "territory", "language"]
 	else:
 		to_copy = ["supplier_name", "supplier_group", "language"]
@@ -170,12 +170,8 @@
 		return party.default_price_list
 
 	if party.doctype == "Customer":
-		price_list =  frappe.get_cached_value("Customer Group",
-			party.customer_group, "default_price_list")
-		if price_list:
-			return price_list
+		return frappe.db.get_value("Customer Group", party.customer_group, "default_price_list")
 
-	return None
 
 def set_price_list(party_details, party, party_type, given_price_list, pos=None):
 	# price list
diff --git a/erpnext/accounts/test_party.py b/erpnext/accounts/test_party.py
new file mode 100644
index 0000000..f7a1a85
--- /dev/null
+++ b/erpnext/accounts/test_party.py
@@ -0,0 +1,16 @@
+import frappe
+from frappe.tests.utils import FrappeTestCase
+
+from erpnext.accounts.party import get_default_price_list
+
+
+class PartyTestCase(FrappeTestCase):
+	def test_get_default_price_list_should_return_none_for_invalid_group(self):
+		customer = frappe.get_doc({
+			'doctype': 'Customer',
+			'customer_name': 'test customer',
+		}).insert(ignore_permissions=True, ignore_mandatory=True)
+		customer.customer_group = None
+		customer.save()
+		price_list = get_default_price_list(customer)
+		assert price_list is None
diff --git a/erpnext/assets/doctype/asset/asset_dashboard.py b/erpnext/assets/doctype/asset/asset_dashboard.py
index 1833b0e..c81b611 100644
--- a/erpnext/assets/doctype/asset/asset_dashboard.py
+++ b/erpnext/assets/doctype/asset/asset_dashboard.py
@@ -1,5 +1,6 @@
 from frappe import _
 
+
 def get_data():
 	return {
 		'non_standard_fieldnames': {
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 874fb63..6e04242 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -23,7 +23,7 @@
 		frappe.db.commit()
 
 def get_depreciable_assets(date):
-	return frappe.db.sql_list("""select a.name
+	return frappe.db.sql_list("""select distinct a.name
 		from tabAsset a, `tabDepreciation Schedule` ds
 		where a.name = ds.parent and a.docstatus=1 and ds.schedule_date<=%s and a.calculate_depreciation = 1
 			and a.status in ('Submitted', 'Partially Depreciated')
diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py
index f3c751c..a533da0 100644
--- a/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py
@@ -17,8 +17,8 @@
 	def setUp(self) -> None:
 		create_item("Test MR Report Item")
 		self.setup_material_request() # to order and receive
-		self.setup_material_request(order=True) # to receive (ordered)
-		self.setup_material_request(order=True, receive=True) # complete (ordered & received)
+		self.setup_material_request(order=True, days=1) # to receive (ordered)
+		self.setup_material_request(order=True, receive=True, days=2) # complete (ordered & received)
 
 		self.filters = frappe._dict(
 			company="_Test Company", from_date=today(), to_date=add_days(today(), 30),
@@ -32,9 +32,8 @@
 		data = get_data(self.filters)
 		self.assertEqual(len(data), 2) # MRs today should be fetched
 
-		self.filters.from_date = add_days(today(), 1)
-		data = get_data(self.filters)
-		self.assertEqual(len(data), 0) # MRs today should not be fetched as from date is tomorrow
+		data = get_data(self.filters.update({"from_date": add_days(today(), 10)}))
+		self.assertEqual(len(data), 0) # MRs today should not be fetched as from date is in future
 
 	def test_ordered_received_material_requests(self):
 		data = get_data(self.filters)
@@ -44,19 +43,19 @@
 		self.assertEqual(data[0].ordered_qty, 0.0)
 		self.assertEqual(data[1].ordered_qty, 57.0)
 
-	def setup_material_request(self, order=False, receive=False):
+	def setup_material_request(self, order=False, receive=False, days=0):
 		po = None
 		test_records = frappe.get_test_records('Material Request')
 
 		mr = frappe.copy_doc(test_records[0])
-		mr.transaction_date = today()
-		mr.schedule_date = add_days(today(), 1)
+		mr.transaction_date = add_days(today(), days)
+		mr.schedule_date = add_days(mr.transaction_date, 1)
 		for row in mr.items:
 			row.item_code = "Test MR Report Item"
 			row.item_name = "Test MR Report Item"
 			row.description = "Test MR Report Item"
 			row.uom = "Nos"
-			row.schedule_date = add_days(today(), 1)
+			row.schedule_date = mr.schedule_date
 		mr.submit()
 
 		if order or receive:
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index a1bb667..d362cdd 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -37,6 +37,8 @@
 			self.set_discount_amount()
 			self.apply_discount_amount()
 
+		self.calculate_shipping_charges()
+
 		if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
 			self.calculate_total_advance()
 
@@ -50,7 +52,6 @@
 		self.initialize_taxes()
 		self.determine_exclusive_rate()
 		self.calculate_net_total()
-		self.calculate_shipping_charges()
 		self.calculate_taxes()
 		self.manipulate_grand_total_for_inclusive_tax()
 		self.calculate_totals()
@@ -276,6 +277,8 @@
 			shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule)
 			shipping_rule.apply(self.doc)
 
+			self._calculate()
+
 	def calculate_taxes(self):
 		rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment')
 		if not rounding_adjustment_computed:
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index c31b068..33ec552 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -214,6 +214,7 @@
 				})
 
 			contact.insert(ignore_permissions=True)
+			contact.reload() # load changes by hooks on contact
 
 			return contact
 
diff --git a/erpnext/e_commerce/api.py b/erpnext/e_commerce/api.py
index 43cb36c..84554ae 100644
--- a/erpnext/e_commerce/api.py
+++ b/erpnext/e_commerce/api.py
@@ -48,7 +48,6 @@
 
 	sub_categories = []
 	if item_group:
-		field_filters['item_group'] = item_group
 		sub_categories = get_child_groups_for_website(item_group, immediate=True)
 
 	engine = ProductQuery()
diff --git a/erpnext/e_commerce/product_data_engine/filters.py b/erpnext/e_commerce/product_data_engine/filters.py
index c4a3cb9..89d4ffd 100644
--- a/erpnext/e_commerce/product_data_engine/filters.py
+++ b/erpnext/e_commerce/product_data_engine/filters.py
@@ -14,6 +14,8 @@
 		self.item_group = item_group
 
 	def get_field_filters(self):
+		from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
+
 		if not self.item_group and not self.doc.enable_field_filters:
 			return
 
@@ -25,18 +27,26 @@
 		fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)]
 
 		for df in fields:
-			item_filters, item_or_filters = {}, []
+			item_filters, item_or_filters = {"published_in_website": 1}, []
 			link_doctype_values = self.get_filtered_link_doctype_records(df)
 
 			if df.fieldtype == "Link":
 				if self.item_group:
-					item_or_filters.extend([
-						["item_group", "=", self.item_group],
-						["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
-					])
+					include_child = frappe.db.get_value("Item Group", self.item_group, "include_descendants")
+					if include_child:
+						include_groups = get_child_groups_for_website(self.item_group, include_self=True)
+						include_groups = [x.name for x in include_groups]
+						item_or_filters.extend([
+							["item_group", "in", include_groups],
+							["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
+						])
+					else:
+						item_or_filters.extend([
+							["item_group", "=", self.item_group],
+							["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
+						])
 
 				# Get link field values attached to published items
-				item_filters['published_in_website'] = 1
 				item_values = frappe.get_all(
 					"Item",
 					fields=[df.fieldname],
diff --git a/erpnext/e_commerce/product_data_engine/query.py b/erpnext/e_commerce/product_data_engine/query.py
index cfc3c7b..5c92e3d 100644
--- a/erpnext/e_commerce/product_data_engine/query.py
+++ b/erpnext/e_commerce/product_data_engine/query.py
@@ -46,10 +46,10 @@
 		self.filter_with_discount = bool(fields.get("discount"))
 		result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0
 
-		website_item_groups = self.get_website_item_group_results(item_group, website_item_groups)
-
 		if fields:
 			self.build_fields_filters(fields)
+		if item_group:
+			self.build_item_group_filters(item_group)
 		if search_term:
 			self.build_search_filters(search_term)
 		if self.settings.hide_variants:
@@ -61,8 +61,6 @@
 		else:
 			result, count = self.query_items(start=start)
 
-		result = self.combine_web_item_group_results(item_group, result, website_item_groups)
-
 		# sort combined results by ranking
 		result = sorted(result, key=lambda x: x.get("ranking"), reverse=True)
 
@@ -167,6 +165,25 @@
 				# `=` will be faster than `IN` for most cases
 				self.filters.append([field, "=", values])
 
+	def build_item_group_filters(self, item_group):
+		"Add filters for Item group page and include Website Item Groups."
+		from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
+		item_group_filters = []
+
+		item_group_filters.append(["Website Item", "item_group", "=", item_group])
+		# Consider Website Item Groups
+		item_group_filters.append(["Website Item Group", "item_group", "=", item_group])
+
+		if frappe.db.get_value("Item Group", item_group, "include_descendants"):
+			# include child item group's items as well
+			# eg. Group Node A, will show items of child 1 and child 2 as well
+			# on it's web page
+			include_groups = get_child_groups_for_website(item_group, include_self=True)
+			include_groups = [x.name for x in include_groups]
+			item_group_filters.append(["Website Item", "item_group", "in", include_groups])
+
+		self.or_filters.extend(item_group_filters)
+
 	def build_search_filters(self, search_term):
 		"""Query search term in specified fields
 
@@ -190,19 +207,6 @@
 		for field in search_fields:
 			self.or_filters.append([field, "like", search])
 
-	def get_website_item_group_results(self, item_group, website_item_groups):
-		"""Get Web Items for Item Group Page via Website Item Groups."""
-		if item_group:
-			website_item_groups = frappe.db.get_all(
-				"Website Item",
-				fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"],
-				filters=[
-					["Website Item Group", "item_group", "=", item_group],
-					["published", "=", 1]
-				]
-			)
-		return website_item_groups
-
 	def add_display_details(self, result, discount_list, cart_items):
 		"""Add price and availability details in result."""
 		for item in result:
@@ -278,16 +282,6 @@
 
 		return []
 
-	def combine_web_item_group_results(self, item_group, result, website_item_groups):
-		"""Combine results with context of website item groups into item results."""
-		if item_group and website_item_groups:
-			items_list = {row.name for row in result}
-			for row in website_item_groups:
-				if row.wig_parent not in items_list:
-					result.append(row)
-
-		return result
-
 	def filter_results_by_discount(self, fields, result):
 		if fields and fields.get("discount"):
 			discount_percent = frappe.utils.flt(fields["discount"][0])
diff --git a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py
index f0f7918..6549ba6 100644
--- a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py
+++ b/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py
@@ -13,8 +13,7 @@
 class TestItemGroupProductDataEngine(unittest.TestCase):
 	"Test Products & Sub-Category Querying for Product Listing on Item Group Page."
 
-	@classmethod
-	def setUpClass(cls):
+	def setUp(self):
 		item_codes = [
 			("Test Mobile A", "_Test Item Group B"),
 			("Test Mobile B", "_Test Item Group B"),
@@ -28,8 +27,10 @@
 			if not frappe.db.exists("Website Item", {"item_code": item_code}):
 				create_regular_web_item(item_code, item_args=item_args)
 
-	@classmethod
-	def tearDownClass(cls):
+		frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
+		frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1)
+
+	def tearDown(self):
 		frappe.db.rollback()
 
 	def test_product_listing_in_item_group(self):
@@ -87,7 +88,6 @@
 
 	def test_item_group_with_sub_groups(self):
 		"Test Valid Sub Item Groups in Item Group Page."
-		frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
 		frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0)
 
 		result = get_product_filter_data(query_args={
@@ -114,4 +114,45 @@
 
 		# check if child group is fetched if shown in website
 		self.assertIn("_Test Item Group B - 1", child_groups)
-		self.assertIn("_Test Item Group B - 2", child_groups)
\ No newline at end of file
+		self.assertIn("_Test Item Group B - 2", child_groups)
+
+	def test_item_group_page_with_descendants_included(self):
+		"""
+		Test if 'include_descendants' pulls Items belonging to descendant Item Groups (Level 2 & 3).
+		> _Test Item Group B [Level 1]
+			> _Test Item Group B - 1 [Level 2]
+				> _Test Item Group B - 1 - 1 [Level 3]
+		"""
+		frappe.get_doc({ # create Level 3 nested child group
+			"doctype": "Item Group",
+			"is_group": 1,
+			"item_group_name": "_Test Item Group B - 1 - 1",
+			"parent_item_group": "_Test Item Group B - 1"
+		}).insert()
+
+		create_regular_web_item( # create an item belonging to level 3 item group
+			"Test Mobile F",
+			item_args={"item_group": "_Test Item Group B - 1 - 1"}
+		)
+
+		frappe.db.set_value("Item Group", "_Test Item Group B - 1 - 1", "show_in_website", 1)
+
+		# enable 'include descendants' in Level 1
+		frappe.db.set_value("Item Group", "_Test Item Group B", "include_descendants", 1)
+
+		result = get_product_filter_data(query_args={
+			"field_filters": {},
+			"attribute_filters": {},
+			"start": 0,
+			"item_group": "_Test Item Group B"
+		})
+
+		items = result.get("items")
+		item_codes = [item.get("item_code") for item in items]
+
+		# check if all sub groups' items are pulled
+		self.assertEqual(len(items), 6)
+		self.assertIn("Test Mobile A", item_codes)
+		self.assertIn("Test Mobile C", item_codes)
+		self.assertIn("Test Mobile E", item_codes)
+		self.assertIn("Test Mobile F", item_codes)
\ No newline at end of file
diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py
index 372aed0..0f3f69d 100644
--- a/erpnext/e_commerce/shopping_cart/cart.py
+++ b/erpnext/e_commerce/shopping_cart/cart.py
@@ -120,7 +120,11 @@
 def request_for_quotation():
 	quotation = _get_cart_quotation()
 	quotation.flags.ignore_permissions = True
-	quotation.submit()
+
+	if get_shopping_cart_settings().save_quotations_as_draft:
+		quotation.save()
+	else:
+		quotation.submit()
 	return quotation.name
 
 @frappe.whitelist()
diff --git a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
index 9c389d0..4a3787a 100644
--- a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
+++ b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
@@ -6,7 +6,7 @@
 
 import frappe
 from frappe.tests.utils import change_settings
-from frappe.utils import add_months, nowdate
+from frappe.utils import add_months, cint, nowdate
 
 from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
 from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
@@ -14,22 +14,17 @@
 	_get_cart_quotation,
 	get_cart_quotation,
 	get_party,
+	request_for_quotation,
 	update_cart,
 )
 from erpnext.tests.utils import create_test_contact_and_address
 
-# test_dependencies = ['Payment Terms Template']
 
 class TestShoppingCart(unittest.TestCase):
 	"""
 		Note:
 		Shopping Cart == Quotation
 	"""
-
-	@classmethod
-	def tearDownClass(cls):
-		frappe.db.sql("delete from `tabTax Rule`")
-
 	def setUp(self):
 		frappe.set_user("Administrator")
 		create_test_contact_and_address()
@@ -45,6 +40,10 @@
 		frappe.set_user("Administrator")
 		self.disable_shopping_cart()
 
+	@classmethod
+	def tearDownClass(cls):
+		frappe.db.sql("delete from `tabTax Rule`")
+
 	def test_get_cart_new_user(self):
 		self.login_as_new_user()
 
@@ -179,6 +178,28 @@
 		# test if items are rendered without error
 		frappe.render_template("templates/includes/cart/cart_items.html", cart)
 
+	@change_settings("E Commerce Settings",{
+		"save_quotations_as_draft": 1
+	})
+	def test_cart_without_checkout_and_draft_quotation(self):
+		"Test impact of 'save_quotations_as_draft' checkbox."
+		frappe.local.shopping_cart_settings = None
+
+		# add item to cart
+		update_cart("_Test Item", 1)
+		quote_name = request_for_quotation() # Request for Quote
+		quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus"))
+
+		self.assertEqual(quote_doctstatus, 0)
+
+		frappe.db.set_value("E Commerce Settings", None, "save_quotations_as_draft", 0)
+		frappe.local.shopping_cart_settings = None
+		update_cart("_Test Item", 1)
+		quote_name = request_for_quotation() # Request for Quote
+		quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus"))
+
+		self.assertEqual(quote_doctstatus, 1)
+
 	def create_tax_rule(self):
 		tax_rule = frappe.get_test_records("Tax Rule")[0]
 		try:
diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py
index c74967d..6095413 100644
--- a/erpnext/hr/doctype/attendance/test_attendance.py
+++ b/erpnext/hr/doctype/attendance/test_attendance.py
@@ -25,7 +25,9 @@
 		self.assertEqual(attendance, fetch_attendance)
 
 	def test_unmarked_days(self):
-		first_day = get_first_day(getdate())
+		now = now_datetime()
+		previous_month = now.month - 1
+		first_day = now.replace(day=1).replace(month=previous_month).date()
 
 		employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
 		frappe.db.delete('Attendance', {'employee': employee})
@@ -34,7 +36,7 @@
 		holiday_list = make_holiday_list()
 		frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list)
 
-		first_sunday = get_first_sunday(holiday_list)
+		first_sunday = get_first_sunday(holiday_list, for_date=first_day)
 		mark_attendance(employee, first_day, 'Present')
 		month_name = get_month_name(first_day)
 
@@ -49,7 +51,9 @@
 		self.assertIn(first_sunday, unmarked_days)
 
 	def test_unmarked_days_excluding_holidays(self):
-		first_day = get_first_day(getdate())
+		now = now_datetime()
+		previous_month = now.month - 1
+		first_day = now.replace(day=1).replace(month=previous_month).date()
 
 		employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
 		frappe.db.delete('Attendance', {'employee': employee})
@@ -58,7 +62,7 @@
 		holiday_list = make_holiday_list()
 		frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list)
 
-		first_sunday = get_first_sunday(holiday_list)
+		first_sunday = get_first_sunday(holiday_list, for_date=first_day)
 		mark_attendance(employee, first_day, 'Present')
 		month_name = get_month_name(first_day)
 
@@ -83,6 +87,10 @@
 			relieving_date=relieving_date)
 		frappe.db.delete('Attendance', {'employee': employee})
 
+		from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
+		holiday_list = make_holiday_list()
+		frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list)
+
 		attendance_date = add_days(first_day, 2)
 		mark_attendance(employee, attendance_date, 'Present')
 		month_name = get_month_name(first_day)
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index ef5f4bc..345d8dc 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -495,7 +495,6 @@
 			number_of_days = date_diff(to_date, from_date) + .5
 		else:
 			number_of_days = date_diff(to_date, from_date) + 1
-
 	else:
 		number_of_days = date_diff(to_date, from_date) + 1
 
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 7b3aa49..01e0ca0 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -133,7 +133,9 @@
 
 		holiday_list = make_holiday_list()
 		employee = get_employee()
-		frappe.db.set_value("Company", employee.company, "default_holiday_list", holiday_list)
+		original_holiday_list = employee.holiday_list
+		frappe.db.set_value("Employee", employee.name, "holiday_list", holiday_list)
+
 		first_sunday = get_first_sunday(holiday_list)
 
 		leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name)
@@ -143,6 +145,8 @@
 
 		leave_application.cancel()
 
+		frappe.db.set_value("Employee", employee.name, "holiday_list", original_holiday_list)
+
 	def test_attendance_update_for_exclude_holidays(self):
 		# Case 2: leave type with 'Include holidays within leaves as leaves' disabled
 		frappe.delete_doc_if_exists("Leave Type", "Test Do Not Include Holidays", force=1)
@@ -157,7 +161,8 @@
 
 		holiday_list = make_holiday_list()
 		employee = get_employee()
-		frappe.db.set_value("Company", employee.company, "default_holiday_list", holiday_list)
+		original_holiday_list = employee.holiday_list
+		frappe.db.set_value("Employee", employee.name, "holiday_list", holiday_list)
 		first_sunday = get_first_sunday(holiday_list)
 
 		# already marked attendance on a holiday should be deleted in this case
@@ -177,7 +182,7 @@
 		attendance.flags.ignore_validate = True
 		attendance.save()
 
-		leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name)
+		leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name, employee.company)
 		leave_application.reload()
 		# holiday should be excluded while marking attendance
 		self.assertEqual(leave_application.total_leave_days, 3)
@@ -189,6 +194,8 @@
 		# attendance on non-holiday updated
 		self.assertEqual(frappe.db.get_value("Attendance", attendance.name, "status"), "On Leave")
 
+		frappe.db.set_value("Employee", employee.name, "holiday_list", original_holiday_list)
+
 	def test_block_list(self):
 		self._clear_roles()
 
@@ -327,7 +334,8 @@
 		employee = get_employee()
 
 		default_holiday_list = make_holiday_list()
-		frappe.db.set_value("Company", employee.company, "default_holiday_list", default_holiday_list)
+		original_holiday_list = employee.holiday_list
+		frappe.db.set_value("Employee", employee.name, "holiday_list", default_holiday_list)
 		first_sunday = get_first_sunday(default_holiday_list)
 
 		optional_leave_date = add_days(first_sunday, 1)
@@ -378,6 +386,8 @@
 		# check leave balance is reduced
 		self.assertEqual(get_leave_balance_on(employee.name, leave_type, optional_leave_date), 9)
 
+		frappe.db.set_value("Employee", employee.name, "holiday_list", original_holiday_list)
+
 	def test_leaves_allowed(self):
 		employee = get_employee()
 		leave_period = get_leave_period()
@@ -782,9 +792,10 @@
 	allocate_leave.submit()
 
 
-def get_first_sunday(holiday_list):
-	month_start_date = get_first_day(nowdate())
-	month_end_date = get_last_day(nowdate())
+def get_first_sunday(holiday_list, for_date=None):
+	date = for_date or getdate()
+	month_start_date = get_first_day(date)
+	month_end_date = get_last_day(date)
 	first_sunday = frappe.db.sql("""
 		select holiday_date from `tabHoliday`
 		where parent = %s
diff --git a/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py
new file mode 100644
index 0000000..952af81
--- /dev/null
+++ b/erpnext/hr/report/monthly_attendance_sheet/test_monthly_attendance_sheet.py
@@ -0,0 +1,44 @@
+import frappe
+from dateutil.relativedelta import relativedelta
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import now_datetime
+
+from erpnext.hr.doctype.attendance.attendance import mark_attendance
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.report.monthly_attendance_sheet.monthly_attendance_sheet import execute
+
+
+class TestMonthlyAttendanceSheet(FrappeTestCase):
+	def setUp(self):
+		self.employee = make_employee("test_employee@example.com")
+		frappe.db.delete('Attendance', {'employee': self.employee})
+
+	def test_monthly_attendance_sheet_report(self):
+		now = now_datetime()
+		previous_month = now.month - 1
+		previous_month_first = now.replace(day=1).replace(month=previous_month).date()
+
+		company = frappe.db.get_value('Employee', self.employee, 'company')
+
+		# mark different attendance status on first 3 days of previous month
+		mark_attendance(self.employee, previous_month_first, 'Absent')
+		mark_attendance(self.employee, previous_month_first + relativedelta(days=1), 'Present')
+		mark_attendance(self.employee, previous_month_first + relativedelta(days=2), 'On Leave')
+
+		filters = frappe._dict({
+			'month': previous_month,
+			'year': now.year,
+			'company': company,
+		})
+		report = execute(filters=filters)
+		employees = report[1][0]
+		datasets = report[3]['data']['datasets']
+		absent = datasets[0]['values']
+		present = datasets[1]['values']
+		leaves = datasets[2]['values']
+
+		# ensure correct attendance is reflect on the report
+		self.assertIn(self.employee, employees)
+		self.assertEqual(absent[0], 1)
+		self.assertEqual(present[1], 1)
+		self.assertEqual(leaves[2], 1)
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 37d2b9f..a025ff7 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -2,6 +2,7 @@
 # License: GNU General Public License v3. See license.txt
 
 import functools
+import re
 from collections import deque
 from operator import itemgetter
 from typing import List
@@ -103,25 +104,33 @@
 	)
 
 	def autoname(self):
-		names = frappe.db.sql_list("""select name from `tabBOM` where item=%s""", self.item)
+		# ignore amended documents while calculating current index
+		existing_boms = frappe.get_all(
+			"BOM",
+			filters={"item": self.item, "amended_from": ["is", "not set"]},
+			pluck="name"
+		)
 
-		if names:
-			# name can be BOM/ITEM/001, BOM/ITEM/001-1, BOM-ITEM-001, BOM-ITEM-001-1
-
-			# split by item
-			names = [name.split(self.item, 1) for name in names]
-			names = [d[-1][1:] for d in filter(lambda x: len(x) > 1 and x[-1], names)]
-
-			# split by (-) if cancelled
-			if names:
-				names = [cint(name.split('-')[-1]) for name in names]
-				idx = max(names) + 1
-			else:
-				idx = 1
+		if existing_boms:
+			index = self.get_next_version_index(existing_boms)
 		else:
-			idx = 1
+			index = 1
 
-		name = 'BOM-' + self.item + ('-%.3i' % idx)
+		prefix = self.doctype
+		suffix = "%.3i" % index  # convert index to string (1 -> "001")
+		bom_name = f"{prefix}-{self.item}-{suffix}"
+
+		if len(bom_name) <= 140:
+			name = bom_name
+		else:
+			# since max characters for name is 140, remove enough characters from the
+			# item name to fit the prefix, suffix and the separators
+			truncated_length = 140 - (len(prefix) + len(suffix) + 2)
+			truncated_item_name = self.item[:truncated_length]
+			# if a partial word is found after truncate, remove the extra characters
+			truncated_item_name = truncated_item_name.rsplit(" ", 1)[0]
+			name = f"{prefix}-{truncated_item_name}-{suffix}"
+
 		if frappe.db.exists("BOM", name):
 			conflicting_bom = frappe.get_doc("BOM", name)
 
@@ -134,6 +143,26 @@
 
 		self.name = name
 
+	@staticmethod
+	def get_next_version_index(existing_boms: List[str]) -> int:
+		# split by "/" and "-"
+		delimiters = ["/", "-"]
+		pattern = "|".join(map(re.escape, delimiters))
+		bom_parts = [re.split(pattern, bom_name) for bom_name in existing_boms]
+
+		# filter out BOMs that do not follow the following formats: BOM/ITEM/001, BOM-ITEM-001
+		valid_bom_parts = list(filter(lambda x: len(x) > 1 and x[-1], bom_parts))
+
+		# extract the current index from the BOM parts
+		if valid_bom_parts:
+			# handle cancelled and submitted documents
+			indexes = [cint(part[-1]) for part in valid_bom_parts]
+			index = max(indexes) + 1
+		else:
+			index = 1
+
+		return index
+
 	def validate(self):
 		self.route = frappe.scrub(self.name).replace('_', '-')
 
@@ -192,12 +221,13 @@
 		if self.routing:
 			self.set("operations", [])
 			fields = ["sequence_id", "operation", "workstation", "description",
-				"time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate"]
+				"time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate",
+				"set_cost_based_on_bom_qty", "fixed_time"]
 
 			for row in frappe.get_all("BOM Operation", fields = fields,
 				filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="sequence_id, idx"):
 				child = self.append('operations', row)
-				child.hour_rate = flt(row.hour_rate / self.conversion_rate, 2)
+				child.hour_rate = flt(row.hour_rate / self.conversion_rate, child.precision("hour_rate"))
 
 	def set_bom_material_details(self):
 		for item in self.get("items"):
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 3cc91b3..4417123 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -432,6 +432,69 @@
 		self.assertEqual(bom.transfer_material_against, "Work Order")
 		bom.delete()
 
+	def test_bom_name_length(self):
+		""" test >140 char names"""
+		bom_tree = {
+			"x" * 140 : {
+				" ".join(["abc"] * 35): {}
+			}
+		}
+		create_nested_bom(bom_tree, prefix="")
+
+	def test_version_index(self):
+
+		bom = frappe.new_doc("BOM")
+
+		version_index_test_cases = [
+			(1, []),
+			(1, ["BOM#XYZ"]),
+			(2, ["BOM/ITEM/001"]),
+			(2, ["BOM-ITEM-001"]),
+			(3, ["BOM-ITEM-001", "BOM-ITEM-002"]),
+			(4, ["BOM-ITEM-001", "BOM-ITEM-002", "BOM-ITEM-003"]),
+		]
+
+		for expected_index, existing_boms in version_index_test_cases:
+			with self.subTest():
+				self.assertEqual(expected_index, bom.get_next_version_index(existing_boms),
+						msg=f"Incorrect index for {existing_boms}")
+
+	def test_bom_versioning(self):
+		bom_tree = {
+			frappe.generate_hash(length=10) : {
+				frappe.generate_hash(length=10): {}
+			}
+		}
+		bom = create_nested_bom(bom_tree, prefix="")
+		self.assertEqual(int(bom.name.split("-")[-1]), 1)
+		original_bom_name = bom.name
+
+		bom.cancel()
+		bom.reload()
+		self.assertEqual(bom.name, original_bom_name)
+
+		# create a new amendment
+		amendment = frappe.copy_doc(bom)
+		amendment.docstatus = 0
+		amendment.amended_from = bom.name
+
+		amendment.save()
+		amendment.submit()
+		amendment.reload()
+
+		self.assertNotEqual(amendment.name, bom.name)
+		# `origname-001-1` version
+		self.assertEqual(int(amendment.name.split("-")[-1]), 1)
+		self.assertEqual(int(amendment.name.split("-")[-2]), 1)
+
+		# create a new version
+		version = frappe.copy_doc(amendment)
+		version.docstatus = 0
+		version.amended_from = None
+		version.save()
+		self.assertNotEqual(amendment.name, version.name)
+		self.assertEqual(int(version.name.split("-")[-1]), 2)
+
 
 def get_default_bom(item_code="_Test FG Item 2"):
 	return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
index c7be7ef..341f969 100644
--- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
+++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
@@ -66,7 +66,8 @@
    "label": "Hour Rate",
    "oldfieldname": "hour_rate",
    "oldfieldtype": "Currency",
-   "options": "currency"
+   "options": "currency",
+   "precision": "2"
   },
   {
    "description": "In minutes",
@@ -186,7 +187,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-12-15 03:00:00.473173",
+ "modified": "2022-03-10 06:19:08.462027",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Operation",
diff --git a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
index b3ee340..7ae4c42 100644
--- a/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
+++ b/erpnext/patches/v12_0/move_bank_account_swift_number_to_bank.py
@@ -5,10 +5,13 @@
 	frappe.reload_doc('accounts', 'doctype', 'bank', force=1)
 
 	if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account') and frappe.db.has_column('Bank Account', 'swift_number'):
-		frappe.db.sql("""
-			UPDATE `tabBank` b, `tabBank Account` ba
-			SET b.swift_number = ba.swift_number WHERE b.name = ba.bank
-		""")
+		try:
+			frappe.db.sql("""
+				UPDATE `tabBank` b, `tabBank Account` ba
+				SET b.swift_number = ba.swift_number WHERE b.name = ba.bank
+			""")
+		except Exception as e:
+			frappe.log_error(e, title="Patch Migration Failed")
 
 	frappe.reload_doc('accounts', 'doctype', 'bank_account')
 	frappe.reload_doc('accounts', 'doctype', 'payment_request')
diff --git a/erpnext/projects/report/project_profitability/project_profitability.py b/erpnext/projects/report/project_profitability/project_profitability.py
index 9520cd1..23c3b82 100644
--- a/erpnext/projects/report/project_profitability/project_profitability.py
+++ b/erpnext/projects/report/project_profitability/project_profitability.py
@@ -1,14 +1,12 @@
 # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
-
 import frappe
 from frappe import _
 from frappe.utils import flt
 
 
 def execute(filters=None):
-	columns, data = [], []
 	data = get_data(filters)
 	columns = get_columns()
 	charts = get_chart_data(data)
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index ae0e2a3..9fec43b 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -39,6 +39,8 @@
 		this._calculate_taxes_and_totals();
 		this.calculate_discount_amount();
 
+		this.calculate_shipping_charges();
+
 		// Advance calculation applicable to Sales /Purchase Invoice
 		if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
 			&& this.frm.doc.docstatus < 2 && !this.frm.doc.is_return) {
@@ -81,7 +83,6 @@
 		this.initialize_taxes();
 		this.determine_exclusive_rate();
 		this.calculate_net_total();
-		this.calculate_shipping_charges();
 		this.calculate_taxes();
 		this.manipulate_grand_total_for_inclusive_tax();
 		this.calculate_totals();
@@ -275,6 +276,7 @@
 		frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
 		if (frappe.meta.get_docfield(this.frm.doc.doctype, "shipping_rule", this.frm.doc.name)) {
 			this.shipping_rule();
+			this._calculate_taxes_and_totals();
 		}
 	}
 
diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
index beac7ed..92a10c2 100644
--- a/erpnext/regional/report/datev/datev.py
+++ b/erpnext/regional/report/datev/datev.py
@@ -343,8 +343,7 @@
 			/* against number or, if empty, party against number */
 			%(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)',
 
-			/* disable automatic VAT deduction */
-			'40' as 'BU-Schlüssel',
+			'' as 'BU-Schlüssel',
 
 			gl.posting_date as 'Belegdatum',
 			gl.voucher_no as 'Belegfeld 1',
diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py
index b1a5d10..147a59f 100644
--- a/erpnext/regional/report/irs_1099/irs_1099.py
+++ b/erpnext/regional/report/irs_1099/irs_1099.py
@@ -30,7 +30,6 @@
 	if region != 'United States':
 		return [], []
 
-	data = []
 	columns = get_columns()
 	conditions = ""
 	if filters.supplier_group:
diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py
index 4133687..464939f 100644
--- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py
+++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py
@@ -118,8 +118,7 @@
 			"customer_type": "Company",
 		})
 		customer.insert()
-	else:
-		customer = frappe.get_doc("Customer", "_Test UAE Customer")
+
 
 def make_supplier():
 	if not frappe.db.exists("Supplier", "_Test UAE Supplier"):
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index b528479..86a0882 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1477,6 +1477,28 @@
 		self.assertEqual(so.items[0].work_order_qty, wo.produced_qty)
 		self.assertEqual(mr.status, "Manufactured")
 
+	def test_sales_order_with_shipping_rule(self):
+		from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
+		shipping_rule = create_shipping_rule(shipping_rule_type = "Selling", shipping_rule_name = "Shipping Rule - Sales Invoice Test")
+		sales_order = make_sales_order(do_not_save=True)
+		sales_order.shipping_rule = shipping_rule.name
+
+		sales_order.items[0].qty = 1
+		sales_order.save()
+		self.assertEqual(sales_order.taxes[0].tax_amount, 50)
+
+		sales_order.items[0].qty = 2
+		sales_order.save()
+		self.assertEqual(sales_order.taxes[0].tax_amount, 100)
+
+		sales_order.items[0].qty = 3
+		sales_order.save()
+		self.assertEqual(sales_order.taxes[0].tax_amount, 200)
+
+		sales_order.items[0].qty = 21
+		sales_order.save()
+		self.assertEqual(sales_order.taxes[0].tax_amount, 0)
+
 def automatically_fetch_payment_terms(enable=1):
 	accounts_settings = frappe.get_doc("Accounts Settings")
 	accounts_settings.automatically_fetch_payment_terms = enable
diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json
index 3e0680f..50f923d 100644
--- a/erpnext/setup/doctype/item_group/item_group.json
+++ b/erpnext/setup/doctype/item_group/item_group.json
@@ -20,12 +20,14 @@
   "sec_break_taxes",
   "taxes",
   "sb9",
-  "show_in_website",
   "route",
-  "weightage",
-  "slideshow",
   "website_title",
   "description",
+  "show_in_website",
+  "include_descendants",
+  "column_break_16",
+  "weightage",
+  "slideshow",
   "website_specifications",
   "website_filters_section",
   "filter_fields",
@@ -111,7 +113,7 @@
   },
   {
    "default": "0",
-   "description": "Check this if you want to show in website",
+   "description": "Make Item Group visible in website",
    "fieldname": "show_in_website",
    "fieldtype": "Check",
    "label": "Show in Website"
@@ -124,6 +126,7 @@
    "unique": 1
   },
   {
+   "depends_on": "show_in_website",
    "fieldname": "weightage",
    "fieldtype": "Int",
    "label": "Weightage"
@@ -186,6 +189,8 @@
    "report_hide": 1
   },
   {
+   "collapsible": 1,
+   "depends_on": "show_in_website",
    "fieldname": "website_filters_section",
    "fieldtype": "Section Break",
    "label": "Website Filters"
@@ -203,9 +208,22 @@
    "options": "Website Attribute"
   },
   {
+   "depends_on": "show_in_website",
    "fieldname": "website_title",
    "fieldtype": "Data",
    "label": "Title"
+  },
+  {
+   "fieldname": "column_break_16",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "depends_on": "show_in_website",
+   "description": "Include Website Items belonging to child Item Groups",
+   "fieldname": "include_descendants",
+   "fieldtype": "Check",
+   "label": "Include Descendants"
   }
  ],
  "icon": "fa fa-sitemap",
@@ -214,11 +232,12 @@
  "is_tree": 1,
  "links": [],
  "max_attachments": 3,
- "modified": "2021-02-18 13:40:30.049650",
+ "modified": "2022-03-09 12:27:11.055782",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Item Group",
  "name_case": "Title Case",
+ "naming_rule": "By fieldname",
  "nsm_parent_field": "parent_item_group",
  "owner": "Administrator",
  "permissions": [
@@ -285,5 +304,6 @@
  "search_fields": "parent_item_group",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 4f92240..5c7194b 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -111,7 +111,7 @@
 		from erpnext.stock.doctype.item.item import validate_item_default_company_links
 		validate_item_default_company_links(self.item_group_defaults)
 
-def get_child_groups_for_website(item_group_name, immediate=False):
+def get_child_groups_for_website(item_group_name, immediate=False, include_self=False):
 	"""Returns child item groups *excluding* passed group."""
 	item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1)
 	filters = {
@@ -123,6 +123,12 @@
 	if immediate:
 		filters["parent_item_group"] = item_group_name
 
+	if include_self:
+		filters.update({
+			"lft": [">=", item_group.lft],
+			"rgt": ["<=", item_group.rgt]
+		})
+
 	return frappe.get_all(
 		"Item Group",
 		filters=filters,
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 2a4d639..fbcc803 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -342,25 +342,21 @@
 	from frappe.query_builder.functions import Sum
 
 	# Billed against Sales Order directly
-	si = frappe.qb.DocType("Sales Invoice").as_("si")
 	si_item = frappe.qb.DocType("Sales Invoice Item").as_("si_item")
 	sum_amount = Sum(si_item.amount).as_("amount")
 
-	billed_against_so = frappe.qb.from_(si).from_(si_item).select(sum_amount).where(
-		(si_item.parent == si.name) &
+	billed_against_so = frappe.qb.from_(si_item).select(sum_amount).where(
 		(si_item.so_detail == so_detail) &
 		((si_item.dn_detail.isnull()) | (si_item.dn_detail == '')) &
-		(si_item.docstatus == 1) &
-		(si.update_stock == 0)
+		(si_item.docstatus == 1)
 	).run()
 	billed_against_so = billed_against_so and billed_against_so[0][0] or 0
 
 	# Get all Delivery Note Item rows against the Sales Order Item row
-
 	dn = frappe.qb.DocType("Delivery Note").as_("dn")
 	dn_item = frappe.qb.DocType("Delivery Note Item").as_("dn_item")
 
-	dn_details = frappe.qb.from_(dn).from_(dn_item).select(dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent, dn_item.stock_qty, dn_item.returned_qty).where(
+	dn_details = frappe.qb.from_(dn).from_(dn_item).select(dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent).where(
 		(dn.name == dn_item.parent) &
 		(dn_item.so_detail == so_detail) &
 		(dn.docstatus == 1) &
@@ -385,11 +381,7 @@
 
 		# Distribute billed amount directly against SO between DNs based on FIFO
 		if billed_against_so and billed_amt_agianst_dn < dnd.amount:
-			if dnd.returned_qty:
-				pending_to_bill = flt(dnd.amount) * (dnd.stock_qty - dnd.returned_qty) / dnd.stock_qty
-			else:
-				pending_to_bill = flt(dnd.amount)
-			pending_to_bill -= billed_amt_agianst_dn
+			pending_to_bill = flt(dnd.amount) - billed_amt_agianst_dn
 			if pending_to_bill <= billed_against_so:
 				billed_amt_agianst_dn += pending_to_bill
 				billed_against_so -= pending_to_bill
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index ffea9c2..9e8b3bd 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -165,21 +165,21 @@
 		frm.set_value('has_batch_no', 0);
 		frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset);
 
-		frm.call({
-			method: "set_asset_naming_series",
-			doc: frm.doc,
-			callback: function() {
+		frappe.call({
+			method: "erpnext.stock.doctype.item.item.get_asset_naming_series",
+			callback: function(r) {
 				frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1);
-				frm.trigger("set_asset_naming_series");
+				frm.events.set_asset_naming_series(frm, r.message);
 			}
 		});
 
 		frm.trigger('auto_create_assets');
 	},
 
-	set_asset_naming_series: function(frm) {
-		if (frm.doc.__onload && frm.doc.__onload.asset_naming_series) {
-			frm.set_df_property("asset_naming_series", "options", frm.doc.__onload.asset_naming_series);
+	set_asset_naming_series: function(frm, asset_naming_series) {
+		if ((frm.doc.__onload && frm.doc.__onload.asset_naming_series) || asset_naming_series) {
+			let naming_series = (frm.doc.__onload && frm.doc.__onload.asset_naming_series) || asset_naming_series;
+			frm.set_df_property("asset_naming_series", "options", naming_series);
 		}
 	},
 
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 494fb3b..8ede955 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -50,15 +50,7 @@
 class Item(Document):
 	def onload(self):
 		self.set_onload('stock_exists', self.stock_ledger_created())
-		self.set_asset_naming_series()
-
-	@frappe.whitelist()
-	def set_asset_naming_series(self):
-		if not hasattr(self, '_asset_naming_series'):
-			from erpnext.assets.doctype.asset.asset import get_asset_naming_series
-			self._asset_naming_series = get_asset_naming_series()
-
-		self.set_onload('asset_naming_series', self._asset_naming_series)
+		self.set_onload('asset_naming_series', get_asset_naming_series())
 
 	def autoname(self):
 		if frappe.db.get_default("item_naming_by") == "Naming Series":
@@ -400,6 +392,7 @@
 			self.validate_properties_before_merge(new_name)
 			self.validate_duplicate_product_bundles_before_merge(old_name, new_name)
 			self.validate_duplicate_website_item_before_merge(old_name, new_name)
+			self.delete_old_bins(old_name)
 
 	def after_rename(self, old_name, new_name, merge):
 		if merge:
@@ -428,6 +421,9 @@
 					frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
 											json.dumps(item_wise_tax_detail), update_modified=False)
 
+	def delete_old_bins(self, old_name):
+		frappe.db.delete("Bin", {"item_code": old_name})
+
 	def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name):
 		records = frappe.db.sql(""" SELECT parent, COUNT(*) as records
 			FROM `tabStock Reconciliation Item`
@@ -508,11 +504,11 @@
 		existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
 
-		repost_stock_for_warehouses = frappe.db.sql_list("""select distinct warehouse
-			from tabBin where item_code=%s""", new_name)
+		repost_stock_for_warehouses = frappe.get_all("Stock Ledger Entry",
+				"warehouse", filters={"item_code": new_name}, pluck="warehouse", distinct=True)
 
 		# Delete all existing bins to avoid duplicate bins for the same item and warehouse
-		frappe.db.sql("delete from `tabBin` where item_code=%s", new_name)
+		frappe.db.delete("Bin", {"item_code": new_name})
 
 		for warehouse in repost_stock_for_warehouses:
 			repost_stock(new_name, warehouse)
@@ -999,7 +995,7 @@
 	if uom == stock_uom:
 		return 1.0
 
-	from_uom, to_uom = uom, stock_uom   # renaming for readability
+	from_uom, to_uom = uom, stock_uom	# renaming for readability
 
 	exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": to_uom, "from_uom": from_uom}, ["value"], as_dict=1)
 	if exact_match:
@@ -1011,9 +1007,9 @@
 
 	# This attempts to try and get conversion from intermediate UOM.
 	# case:
-	#            g -> mg = 1000
-	#            g -> kg = 0.001
-	# therefore  kg -> mg = 1000  / 0.001 = 1,000,000
+	#			 g -> mg = 1000
+	#			 g -> kg = 0.001
+	# therefore	 kg -> mg = 1000  / 0.001 = 1,000,000
 	intermediate_match = frappe.db.sql("""
 			select (first.value / second.value) as value
 			from `tabUOM Conversion Factor` first
@@ -1072,3 +1068,11 @@
 							frappe.bold(item_default.company),
 							frappe.bold(frappe.unscrub(field))
 						), title=_("Invalid Item Defaults"))
+
+
+@frappe.whitelist()
+def get_asset_naming_series():
+	from erpnext.assets.doctype.asset.asset import get_asset_naming_series
+
+	return get_asset_naming_series()
+
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index d7671b1..d57308b 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -371,23 +371,24 @@
 		variant.save()
 
 	def test_item_merging(self):
-		create_item("Test Item for Merging 1")
-		create_item("Test Item for Merging 2")
+		old = create_item(frappe.generate_hash(length=20)).name
+		new = create_item(frappe.generate_hash(length=20)).name
 
-		make_stock_entry(item_code="Test Item for Merging 1", target="_Test Warehouse - _TC",
+		make_stock_entry(item_code=old, target="_Test Warehouse - _TC",
 			qty=1, rate=100)
-		make_stock_entry(item_code="Test Item for Merging 2", target="_Test Warehouse 1 - _TC",
+		make_stock_entry(item_code=old, target="_Test Warehouse 1 - _TC",
+			qty=1, rate=100)
+		make_stock_entry(item_code=new, target="_Test Warehouse 1 - _TC",
 			qty=1, rate=100)
 
-		frappe.rename_doc("Item", "Test Item for Merging 1", "Test Item for Merging 2", merge=True)
+		frappe.rename_doc("Item", old, new, merge=True)
 
-		self.assertFalse(frappe.db.exists("Item", "Test Item for Merging 1"))
+		self.assertFalse(frappe.db.exists("Item", old))
 
 		self.assertTrue(frappe.db.get_value("Bin",
-			{"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse - _TC"}))
-
+			{"item_code": new, "warehouse": "_Test Warehouse - _TC"}))
 		self.assertTrue(frappe.db.get_value("Bin",
-			{"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse 1 - _TC"}))
+			{"item_code": new, "warehouse": "_Test Warehouse 1 - _TC"}))
 
 	def test_item_merging_with_product_bundle(self):
 		from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 5f53be0..e68b0ab 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -214,6 +214,7 @@
 					material_request_type: frm.doc.material_request_type,
 					plc_conversion_rate: 1,
 					rate: item.rate,
+					uom: item.uom,
 					conversion_factor: item.conversion_factor
 				},
 				overwrite_warehouse: overwrite_warehouse
@@ -392,6 +393,7 @@
 	item_code: function(frm, doctype, name) {
 		const item = locals[doctype][name];
 		item.rate = 0;
+		item.uom = '';
 		set_schedule_date(frm);
 		frm.events.get_item_data(frm, item, true);
 	},
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index fa28f22..0017fa7 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -769,8 +769,7 @@
 			update_purchase_receipt_status,
 		)
 
-		pr = make_purchase_receipt(do_not_submit=True)
-		pr.submit()
+		pr = make_purchase_receipt()
 
 		update_purchase_receipt_status(pr.name, "Closed")
 		self.assertEqual(
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 977d470..f4d52ad 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -118,7 +118,8 @@
 		doc.set_status('Failed')
 		raise
 	finally:
-		frappe.db.commit()
+		if not frappe.flags.in_test:
+			frappe.db.commit()
 
 def repost_sl_entries(doc):
 	if doc.based_on == 'Transaction':
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index b4f43a7..24f47c1 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -18,7 +18,6 @@
 	is_reposting_item_valuation_in_progress()
 	if not filters: filters = {}
 
-	from_date = filters.get('from_date')
 	to_date = filters.get('to_date')
 
 	if filters.get("company"):
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index ba1081f..353bfa4 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -838,11 +838,13 @@
 		for warehouse, data in self.data.items():
 			bin_name = get_or_make_bin(self.item_code, warehouse)
 
-			frappe.db.set_value('Bin', bin_name, {
-				"valuation_rate": data.valuation_rate,
+			updated_values = {
 				"actual_qty": data.qty_after_transaction,
 				"stock_value": data.stock_value
-			})
+			}
+			if data.valuation_rate is not None:
+				updated_values["valuation_rate"] = data.valuation_rate
+			frappe.db.set_value('Bin', bin_name, updated_values)
 
 
 def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
diff --git a/erpnext/templates/pages/non_profit/__init__.py b/erpnext/templates/pages/non_profit/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/templates/pages/non_profit/__init__.py
diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html
index 95ba8f7..dd3c23a 100644
--- a/erpnext/www/lms/macros/hero.html
+++ b/erpnext/www/lms/macros/hero.html
@@ -39,16 +39,13 @@
 		frappe.call(opts).then(res => {
 			let success_dialog = new frappe.ui.Dialog({
 				title: __('Success'),
-				primary_action_label: __('View Program Content'),
+				primary_action_label: __('OK'),
 				primary_action: function() {
 					window.location.reload();
-				},
-				secondary_action: function() {
-					window.location.reload();
 				}
 			})
 			success_dialog.show();
-			success_dialog.set_message(__('You have successfully enrolled for the program '));
+			success_dialog.set_message(__('You have successfully enrolled for the program.'));
 		})
 	}
 </script>