Merge branch 'develop' into item-group-filters-listing
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/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/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