chore: Removed Shopping Cart Module

- Moved all files and web templates from Shopping Cart to E-commerce module
- Made Shopping Cart module obsolete
- Moved select E-commerce related files from Portal to E-commerce module
- Minor cleanups
- Fixed Shopping Cart and Product Configurator tests
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 6935fd7..48483b1 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -134,7 +134,7 @@
 
 	conditions = " or ".join(conditions)
 
-	from erpnext.portal.product_configurator.utils import get_item_codes_by_attributes
+	from erpnext.e_commerce.product_configurator.utils import get_item_codes_by_attributes
 	possible_variants = [i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code]
 
 	for variant in possible_variants:
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py
index 5587af7..e3197b9 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.py
+++ b/erpnext/e_commerce/doctype/website_item/website_item.py
@@ -314,7 +314,7 @@
 		context.metatags['og:site_name'] = 'ERPNext'
 
 	def set_shopping_cart_data(self, context):
-		from erpnext.shopping_cart.product_info import get_product_info_for_website
+		from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
 		context.shopping_cart = get_product_info_for_website(self.item_code, skip_quotation_creation=True)
 
 	def copy_specification_from_item_group(self):
diff --git a/erpnext/shopping_cart/filters.py b/erpnext/e_commerce/filters.py
similarity index 88%
rename from erpnext/shopping_cart/filters.py
rename to erpnext/e_commerce/filters.py
index f074081..3d28d90 100644
--- a/erpnext/shopping_cart/filters.py
+++ b/erpnext/e_commerce/filters.py
@@ -14,7 +14,7 @@
 		self.item_group = item_group
 
 	def get_field_filters(self):
-		if not self.doc.enable_field_filters: return
+		if not self.item_group and not self.doc.enable_field_filters: return
 
 		filter_fields = [row.fieldname for row in self.doc.filter_fields]
 
@@ -31,7 +31,7 @@
 						["Website Item Group", "item_group", "=", self.item_group]
 					])
 
-				values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, or_filters=or_filters, distinct="True", pluck=df.fieldname)
+				values =  frappe.get_all("Item", fields=[df.fieldname], filters=filters, distinct="True", pluck=df.fieldname)
 			else:
 				doctype = df.get_link_doctype()
 
@@ -56,7 +56,7 @@
 		return filter_data
 
 	def get_attribute_filters(self):
-		if not self.doc.enable_attribute_filters: return
+		if not self.item_group and not self.doc.enable_attribute_filters: return
 
 		attributes = [row.attribute for row in self.doc.filter_attributes]
 
diff --git a/erpnext/portal/product_configurator/__init__.py b/erpnext/e_commerce/product_configurator/__init__.py
similarity index 100%
rename from erpnext/portal/product_configurator/__init__.py
rename to erpnext/e_commerce/product_configurator/__init__.py
diff --git a/erpnext/portal/product_configurator/item_variants_cache.py b/erpnext/e_commerce/product_configurator/item_variants_cache.py
similarity index 100%
rename from erpnext/portal/product_configurator/item_variants_cache.py
rename to erpnext/e_commerce/product_configurator/item_variants_cache.py
diff --git a/erpnext/e_commerce/product_configurator/test_product_configurator.py b/erpnext/e_commerce/product_configurator/test_product_configurator.py
new file mode 100644
index 0000000..82abee3
--- /dev/null
+++ b/erpnext/e_commerce/product_configurator/test_product_configurator.py
@@ -0,0 +1,118 @@
+import unittest
+
+import frappe
+from bs4 import BeautifulSoup
+from frappe.utils import get_html_for_route
+
+from erpnext.e_commerce.product_query import ProductQuery
+from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+
+test_dependencies = ["Item"]
+
+class TestProductConfigurator(unittest.TestCase):
+	def setUp(self):
+		self.create_variant_item()
+		self.publish_items_on_website()
+
+	def test_product_list(self):
+		usual_items = frappe.get_all('Website Item', {'published': 1, 'has_variants': 0, 'variant_of': ['is', 'not set']})
+		template_items = frappe.get_all('Website Item', {'published': 1, 'has_variants': 1})
+		variant_items = frappe.get_all('Website Item', {'published': 1, 'variant_of': ['is', 'set']})
+
+		e_commerce_settings = frappe.get_doc('E Commerce Settings')
+		e_commerce_settings.enable_field_filters = 1
+		e_commerce_settings.append('filter_fields', {'fieldname': 'item_group'})
+		e_commerce_settings.append('filter_fields', {'fieldname': 'stock_uom'})
+		e_commerce_settings.save()
+
+		html = get_html_for_route('all-products')
+
+		soup = BeautifulSoup(html, 'html.parser')
+		products_list = soup.find(class_='products-list')
+		items = products_list.find_all(class_='card')
+
+		self.assertEqual(len(items), len(template_items + variant_items + usual_items))
+
+		items_with_item_group = frappe.get_all('Website Item', {'item_group': '_Test Item Group Desktops', 'published': 1})
+
+		# mock query params
+		frappe.form_dict = frappe._dict({
+			'field_filters': '{"item_group":["_Test Item Group Desktops"]}'
+		})
+		html = get_html_for_route('all-products')
+		soup = BeautifulSoup(html, 'html.parser')
+		products_list = soup.find(class_='products-list')
+		items = products_list.find_all(class_='card')
+		self.assertEqual(len(items), len(items_with_item_group))
+
+
+	def test_get_products_for_website(self):
+		engine = ProductQuery()
+		items = engine.query(attributes={
+			'Test Size': ['Medium']
+		})
+		self.assertEqual(len(items), 1)
+
+	def test_products_in_multiple_item_groups(self):
+		"""Check if product is visible on multiple item group pages barring its own."""
+		from erpnext.shopping_cart.product_query import ProductQuery
+
+	def create_variant_item(self):
+		if not frappe.db.exists('Item', '_Test Variant Item 1'):
+			frappe.get_doc({
+				"description": "_Test Variant Item 12",
+				"doctype": "Item",
+				"is_stock_item": 1,
+				"variant_of": "_Test Variant Item",
+				"item_code": "_Test Variant Item 1",
+				"item_group": "_Test Item Group",
+				"item_name": "_Test Variant Item 1",
+				"stock_uom": "_Test UOM",
+				"item_defaults": [{
+					"company": "_Test Company",
+					"default_warehouse": "_Test Warehouse - _TC",
+					"expense_account": "_Test Account Cost for Goods Sold - _TC",
+					"buying_cost_center": "_Test Cost Center - _TC",
+					"selling_cost_center": "_Test Cost Center - _TC",
+					"income_account": "Sales - _TC"
+				}],
+				"attributes": [
+					{
+						"attribute": "Test Size",
+						"attribute_value": "Medium"
+					}
+				]
+			}).insert()
+		else:
+			item_group_doc = frappe.get_doc("Item Group", "Tech Items")
+
+		doc = self.create_regular_web_item("Portal Item", item_group="Tech Items")
+		if not frappe.db.exists("Website Item Group", {"parent": "Portal Item"}):
+			doc.append("website_item_groups", {
+				"item_group": "_Test Item Group Desktops"
+			})
+			doc.save()
+
+		# check if item is visible in its own Item Group's page
+		engine = ProductQuery()
+		items = engine.query({}, {"item_group": "Tech Items"}, None, start=0, item_group="Tech Items")
+		self.assertEqual(len(items), 1)
+		self.assertEqual(items[0].item_code, "Portal Item")
+
+		# check if item is visible in configured foreign Item Group's page
+		engine = ProductQuery()
+		items = engine.query({}, {"item_group": "_Test Item Group Desktops"}, None, start=0, item_group="_Test Item Group Desktops")
+		item_codes = [row.item_code for row in items]
+
+	def publish_items_on_website(self):
+		if frappe.db.exists("Item",  "_Test Item") and not frappe.db.exists("Website Item",  {"item_code": "_Test Item"}):
+				make_website_item(frappe.get_cached_doc("Item",  "_Test Item"))
+
+		if frappe.db.exists("Item",  "_Test Variant Item") and not frappe.db.exists("Website Item",  {"item_code": "_Test Variant Item"}):
+			make_website_item(frappe.get_cached_doc("Item",  "_Test Variant Item"))
+
+		make_website_item(frappe.get_cached_doc("Item",  "_Test Variant Item 1"))
+
+		# teardown
+		doc.delete()
+		item_group_doc.delete()
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/e_commerce/product_configurator/utils.py
similarity index 75%
rename from erpnext/portal/product_configurator/utils.py
rename to erpnext/e_commerce/product_configurator/utils.py
index 7ce8f80..6c652cf 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/e_commerce/product_configurator/utils.py
@@ -1,9 +1,65 @@
 import frappe
 from frappe.utils import cint
 
-from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
-from erpnext.setup.doctype.item_group.item_group import get_child_groups
-from erpnext.shopping_cart.product_info import get_product_info_for_website
+from erpnext.e_commerce.product_configurator.item_variants_cache import ItemVariantsCacheManager
+from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
+
+def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
+	items = []
+
+	for attribute, values in attribute_filters.items():
+		attribute_values = values
+
+		if not isinstance(attribute_values, list):
+			attribute_values = [attribute_values]
+
+		if not attribute_values: continue
+
+		wheres = []
+		query_values = []
+		for attribute_value in attribute_values:
+			wheres.append('( attribute = %s and attribute_value = %s )')
+			query_values += [attribute, attribute_value]
+
+		attribute_query = ' or '.join(wheres)
+
+		if template_item_code:
+			variant_of_query = 'AND t2.variant_of = %s'
+			query_values.append(template_item_code)
+		else:
+			variant_of_query = ''
+
+		query = '''
+			SELECT
+				t1.parent
+			FROM
+				`tabItem Variant Attribute` t1
+			WHERE
+				1 = 1
+				AND (
+					{attribute_query}
+				)
+				AND EXISTS (
+					SELECT
+						1
+					FROM
+						`tabItem` t2
+					WHERE
+						t2.name = t1.parent
+						{variant_of_query}
+				)
+			GROUP BY
+				t1.parent
+			ORDER BY
+				NULL
+		'''.format(attribute_query=attribute_query, variant_of_query=variant_of_query)
+
+		item_codes = set([r[0] for r in frappe.db.sql(query, query_values)])
+		items.append(item_codes)
+
+	res = list(set.intersection(*items))
+
+	return res
 
 @frappe.whitelist(allow_guest=True)
 def get_attributes_and_values(item_code):
@@ -86,7 +142,7 @@
 	filtered_items_count = len(filtered_items)
 
 	# get product info if exact match
-	from erpnext.shopping_cart.product_info import get_product_info_for_website
+	from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
 	if exact_match:
 		data = get_product_info_for_website(exact_match[0])
 		product_info = data.product_info
diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/e_commerce/product_query.py
similarity index 98%
rename from erpnext/shopping_cart/product_query.py
rename to erpnext/e_commerce/product_query.py
index a2d2994..f984ec2 100644
--- a/erpnext/shopping_cart/product_query.py
+++ b/erpnext/e_commerce/product_query.py
@@ -3,7 +3,7 @@
 
 import frappe
 
-from erpnext.shopping_cart.product_info import get_product_info_for_website
+from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
 
 
 class ProductQuery:
diff --git a/erpnext/portal/product_configurator/__init__.py b/erpnext/e_commerce/shopping_cart/__init__.py
similarity index 100%
copy from erpnext/portal/product_configurator/__init__.py
copy to erpnext/e_commerce/shopping_cart/__init__.py
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py
similarity index 100%
rename from erpnext/shopping_cart/cart.py
rename to erpnext/e_commerce/shopping_cart/cart.py
diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/e_commerce/shopping_cart/product_info.py
similarity index 93%
rename from erpnext/shopping_cart/product_info.py
rename to erpnext/e_commerce/shopping_cart/product_info.py
index dd77536..3529002 100644
--- a/erpnext/shopping_cart/product_info.py
+++ b/erpnext/e_commerce/shopping_cart/product_info.py
@@ -1,9 +1,9 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
 import frappe
 
-from erpnext.shopping_cart.cart import _get_cart_quotation, _set_price_list
+from erpnext.e_commerce.shopping_cart.cart import _get_cart_quotation, _set_price_list
 from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
 	get_shopping_cart_settings,
 	show_quantity_in_website
diff --git a/erpnext/shopping_cart/search.py b/erpnext/e_commerce/shopping_cart/search.py
similarity index 100%
rename from erpnext/shopping_cart/search.py
rename to erpnext/e_commerce/shopping_cart/search.py
diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
similarity index 93%
rename from erpnext/shopping_cart/test_shopping_cart.py
rename to erpnext/e_commerce/shopping_cart/test_shopping_cart.py
index 63166c6..304dab4 100644
--- a/erpnext/shopping_cart/test_shopping_cart.py
+++ b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
@@ -9,7 +9,8 @@
 from frappe.utils import add_months, nowdate
 
 from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
-from erpnext.shopping_cart.cart import _get_cart_quotation, get_party, update_cart
+from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+from erpnext.e_commerce.shopping_cart.cart import _get_cart_quotation, get_party, update_cart
 from erpnext.tests.utils import create_test_contact_and_address
 
 # test_dependencies = ['Payment Terms Template']
@@ -28,6 +29,11 @@
 		frappe.set_user("Administrator")
 		create_test_contact_and_address()
 		self.enable_shopping_cart()
+		if not frappe.db.exists("Website Item", {"item_code": "_Test Item"}):
+			make_website_item(frappe.get_cached_doc("Item",  "_Test Item"))
+
+		if not frappe.db.exists("Website Item", {"item_code": "_Test Item 2"}):
+			make_website_item(frappe.get_cached_doc("Item",  "_Test Item 2"))
 
 	def tearDown(self):
 		frappe.set_user("Administrator")
diff --git a/erpnext/shopping_cart/utils.py b/erpnext/e_commerce/shopping_cart/utils.py
similarity index 94%
rename from erpnext/shopping_cart/utils.py
rename to erpnext/e_commerce/shopping_cart/utils.py
index 98b5229..ce8e560 100644
--- a/erpnext/shopping_cart/utils.py
+++ b/erpnext/e_commerce/shopping_cart/utils.py
@@ -16,7 +16,7 @@
 	role, parties = check_customer_or_supplier()
 	if role == 'Supplier': return
 	if show_cart_count():
-		from erpnext.shopping_cart.cart import set_cart_count
+		from erpnext.e_commerce.shopping_cart.cart import set_cart_count
 		set_cart_count()
 
 def clear_cart_count(login_manager):
diff --git a/erpnext/portal/product_configurator/__init__.py b/erpnext/e_commerce/web_template/__init__.py
similarity index 100%
copy from erpnext/portal/product_configurator/__init__.py
copy to erpnext/e_commerce/web_template/__init__.py
diff --git a/erpnext/shopping_cart/web_template/hero_slider/__init__.py b/erpnext/e_commerce/web_template/hero_slider/__init__.py
similarity index 100%
rename from erpnext/shopping_cart/web_template/hero_slider/__init__.py
rename to erpnext/e_commerce/web_template/hero_slider/__init__.py
diff --git a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html b/erpnext/e_commerce/web_template/hero_slider/hero_slider.html
similarity index 100%
rename from erpnext/shopping_cart/web_template/hero_slider/hero_slider.html
rename to erpnext/e_commerce/web_template/hero_slider/hero_slider.html
diff --git a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.json b/erpnext/e_commerce/web_template/hero_slider/hero_slider.json
similarity index 98%
rename from erpnext/shopping_cart/web_template/hero_slider/hero_slider.json
rename to erpnext/e_commerce/web_template/hero_slider/hero_slider.json
index 04fb1d2..2b1807c 100644
--- a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.json
+++ b/erpnext/e_commerce/web_template/hero_slider/hero_slider.json
@@ -1,4 +1,5 @@
 {
+ "__unsaved": 1,
  "creation": "2020-11-17 15:21:51.207221",
  "docstatus": 0,
  "doctype": "Web Template",
@@ -273,9 +274,9 @@
   }
  ],
  "idx": 2,
- "modified": "2020-12-29 12:30:02.794994",
+ "modified": "2021-02-24 15:57:05.889709",
  "modified_by": "Administrator",
- "module": "Shopping Cart",
+ "module": "E-commerce",
  "name": "Hero Slider",
  "owner": "Administrator",
  "standard": 1,
diff --git a/erpnext/shopping_cart/web_template/item_card_group/__init__.py b/erpnext/e_commerce/web_template/item_card_group/__init__.py
similarity index 100%
rename from erpnext/shopping_cart/web_template/item_card_group/__init__.py
rename to erpnext/e_commerce/web_template/item_card_group/__init__.py
diff --git a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.html b/erpnext/e_commerce/web_template/item_card_group/item_card_group.html
similarity index 100%
rename from erpnext/shopping_cart/web_template/item_card_group/item_card_group.html
rename to erpnext/e_commerce/web_template/item_card_group/item_card_group.html
diff --git a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.json b/erpnext/e_commerce/web_template/item_card_group/item_card_group.json
similarity index 97%
rename from erpnext/shopping_cart/web_template/item_card_group/item_card_group.json
rename to erpnext/e_commerce/web_template/item_card_group/item_card_group.json
index ad087b0..724c437 100644
--- a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.json
+++ b/erpnext/e_commerce/web_template/item_card_group/item_card_group.json
@@ -17,15 +17,12 @@
    "reqd": 0
   },
   {
-   "__unsaved": 1,
    "fieldname": "primary_action_label",
    "fieldtype": "Data",
    "label": "Primary Action Label",
    "reqd": 0
   },
   {
-   "__islocal": 1,
-   "__unsaved": 1,
    "fieldname": "primary_action",
    "fieldtype": "Data",
    "label": "Primary Action",
@@ -262,9 +259,9 @@
   }
  ],
  "idx": 0,
- "modified": "2020-11-19 18:48:52.633045",
+ "modified": "2021-02-24 16:05:31.242342",
  "modified_by": "Administrator",
- "module": "Shopping Cart",
+ "module": "E-commerce",
  "name": "Item Card Group",
  "owner": "Administrator",
  "standard": 1,
diff --git a/erpnext/shopping_cart/web_template/product_card/__init__.py b/erpnext/e_commerce/web_template/product_card/__init__.py
similarity index 100%
rename from erpnext/shopping_cart/web_template/product_card/__init__.py
rename to erpnext/e_commerce/web_template/product_card/__init__.py
diff --git a/erpnext/shopping_cart/web_template/product_card/product_card.html b/erpnext/e_commerce/web_template/product_card/product_card.html
similarity index 100%
rename from erpnext/shopping_cart/web_template/product_card/product_card.html
rename to erpnext/e_commerce/web_template/product_card/product_card.html
diff --git a/erpnext/shopping_cart/web_template/product_card/product_card.json b/erpnext/e_commerce/web_template/product_card/product_card.json
similarity index 82%
rename from erpnext/shopping_cart/web_template/product_card/product_card.json
rename to erpnext/e_commerce/web_template/product_card/product_card.json
index 1059c1b..2eb7374 100644
--- a/erpnext/shopping_cart/web_template/product_card/product_card.json
+++ b/erpnext/e_commerce/web_template/product_card/product_card.json
@@ -5,7 +5,6 @@
  "doctype": "Web Template",
  "fields": [
   {
-   "__unsaved": 1,
    "fieldname": "item",
    "fieldtype": "Link",
    "label": "Item",
@@ -13,7 +12,6 @@
    "reqd": 0
   },
   {
-   "__unsaved": 1,
    "fieldname": "featured",
    "fieldtype": "Check",
    "label": "Featured",
@@ -22,9 +20,9 @@
   }
  ],
  "idx": 0,
- "modified": "2020-11-17 15:33:34.982515",
+ "modified": "2021-02-24 16:05:17.926610",
  "modified_by": "Administrator",
- "module": "Shopping Cart",
+ "module": "E-commerce",
  "name": "Product Card",
  "owner": "Administrator",
  "standard": 1,
diff --git a/erpnext/shopping_cart/web_template/product_category_cards/__init__.py b/erpnext/e_commerce/web_template/product_category_cards/__init__.py
similarity index 100%
rename from erpnext/shopping_cart/web_template/product_category_cards/__init__.py
rename to erpnext/e_commerce/web_template/product_category_cards/__init__.py
diff --git a/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.html b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html
similarity index 100%
rename from erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.html
rename to erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html
diff --git a/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.json b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json
similarity index 95%
rename from erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.json
rename to erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json
index ba5f63b..0202165 100644
--- a/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.json
+++ b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json
@@ -74,9 +74,9 @@
   }
  ],
  "idx": 0,
- "modified": "2020-11-18 17:26:28.726260",
+ "modified": "2021-02-24 16:03:33.835635",
  "modified_by": "Administrator",
- "module": "Shopping Cart",
+ "module": "E-commerce",
  "name": "Product Category Cards",
  "owner": "Administrator",
  "standard": 1,
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 7351036..016f1c4 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -53,15 +53,15 @@
 
 on_session_creation = [
 	"erpnext.portal.utils.create_customer_or_supplier",
-	"erpnext.shopping_cart.utils.set_cart_count"
+	"erpnext.e_commerce.shopping_cart.utils.set_cart_count"
 ]
-on_logout = "erpnext.shopping_cart.utils.clear_cart_count"
+on_logout = "erpnext.e_commerce.shopping_cart.utils.clear_cart_count"
 
 treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Group', 'Sales Person', 'Territory', 'Assessment Group', 'Department']
 
 # website
-update_website_context = ["erpnext.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"]
-my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
+update_website_context = ["erpnext.e_commerce.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"]
+my_account_context = "erpnext.e_commerce.shopping_cart.utils.update_my_account_context"
 webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context"
 
 calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"]
diff --git a/erpnext/modules.txt b/erpnext/modules.txt
index 1c02db2..2a0ee6b 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -9,7 +9,6 @@
 Stock
 Support
 Utilities
-Shopping Cart
 Assets
 Portal
 Maintenance
diff --git a/erpnext/patches/v13_0/populate_e_commerce_settings.py b/erpnext/patches/v13_0/populate_e_commerce_settings.py
index 94dcd9a..19f91ef 100644
--- a/erpnext/patches/v13_0/populate_e_commerce_settings.py
+++ b/erpnext/patches/v13_0/populate_e_commerce_settings.py
@@ -32,7 +32,7 @@
 			""".format(
 				doctype=doctype,
 				fields=(",").join(['%s'] * len(fields))
-			), tuple(fields), as_dict=1, debug=1)
+			), tuple(fields), as_dict=1)
 
 		# {'enable_attribute_filters': '1', ...}
 		mapper = {row.field: row.value for row in data}
diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py
deleted file mode 100644
index 27adcef..0000000
--- a/erpnext/portal/product_configurator/test_product_configurator.py
+++ /dev/null
@@ -1,145 +0,0 @@
-from __future__ import unicode_literals
-
-import unittest
-
-import frappe
-from bs4 import BeautifulSoup
-from frappe.utils import get_html_for_route
-
-from erpnext.portal.product_configurator.utils import get_products_for_website
-
-test_dependencies = ["Item"]
-
-class TestProductConfigurator(unittest.TestCase):
-	@classmethod
-	def setUpClass(cls):
-		cls.create_variant_item()
-
-	@classmethod
-	def create_variant_item(cls):
-		if not frappe.db.exists('Item', '_Test Variant Item - 2XL'):
-			frappe.get_doc({
-				"description": "_Test Variant Item - 2XL",
-				"item_code": "_Test Variant Item - 2XL",
-				"item_name": "_Test Variant Item - 2XL",
-				"doctype": "Item",
-				"is_stock_item": 1,
-				"variant_of": "_Test Variant Item",
-				"item_group": "_Test Item Group",
-				"stock_uom": "_Test UOM",
-				"item_defaults": [{
-					"company": "_Test Company",
-					"default_warehouse": "_Test Warehouse - _TC",
-					"expense_account": "_Test Account Cost for Goods Sold - _TC",
-					"buying_cost_center": "_Test Cost Center - _TC",
-					"selling_cost_center": "_Test Cost Center - _TC",
-					"income_account": "Sales - _TC"
-				}],
-				"attributes": [
-					{
-						"attribute": "Test Size",
-						"attribute_value": "2XL"
-					}
-				],
-				"show_variant_in_website": 1
-			}).insert()
-
-	def create_regular_web_item(self, name, item_group=None):
-		if not frappe.db.exists('Item', name):
-			doc = frappe.get_doc({
-				"description": name,
-				"item_code": name,
-				"item_name": name,
-				"doctype": "Item",
-				"is_stock_item": 1,
-				"item_group": item_group or "_Test Item Group",
-				"stock_uom": "_Test UOM",
-				"item_defaults": [{
-					"company": "_Test Company",
-					"default_warehouse": "_Test Warehouse - _TC",
-					"expense_account": "_Test Account Cost for Goods Sold - _TC",
-					"buying_cost_center": "_Test Cost Center - _TC",
-					"selling_cost_center": "_Test Cost Center - _TC",
-					"income_account": "Sales - _TC"
-				}],
-				"show_in_website": 1
-			}).insert()
-		else:
-			doc = frappe.get_doc("Item", name)
-		return doc
-
-	def test_product_list(self):
-		template_items = frappe.get_all('Item', {'show_in_website': 1})
-		variant_items = frappe.get_all('Item', {'show_variant_in_website': 1})
-
-		e_commerce_settings = frappe.get_doc('E Commerce Settings')
-		e_commerce_settings.enable_field_filters = 1
-		e_commerce_settings.append('filter_fields', {'fieldname': 'item_group'})
-		e_commerce_settings.append('filter_fields', {'fieldname': 'stock_uom'})
-		e_commerce_settings.save()
-
-		html = get_html_for_route('all-products')
-
-		soup = BeautifulSoup(html, 'html.parser')
-		products_list = soup.find(class_='products-list')
-		items = products_list.find_all(class_='card')
-		self.assertEqual(len(items), len(template_items + variant_items))
-
-		items_with_item_group = frappe.get_all('Item', {'item_group': '_Test Item Group Desktops', 'show_in_website': 1})
-		variants_with_item_group = frappe.get_all('Item', {'item_group': '_Test Item Group Desktops', 'show_variant_in_website': 1})
-
-		# mock query params
-		frappe.form_dict = frappe._dict({
-			'field_filters': '{"item_group":["_Test Item Group Desktops"]}'
-		})
-		html = get_html_for_route('all-products')
-		soup = BeautifulSoup(html, 'html.parser')
-		products_list = soup.find(class_='products-list')
-		items = products_list.find_all(class_='card')
-		self.assertEqual(len(items), len(items_with_item_group + variants_with_item_group))
-
-
-	def test_get_products_for_website(self):
-		items = get_products_for_website(attribute_filters={
-			'Test Size': ['2XL']
-		})
-		self.assertEqual(len(items), 1)
-
-	def test_products_in_multiple_item_groups(self):
-		"""Check if product is visible on multiple item group pages barring its own."""
-		from erpnext.shopping_cart.product_query import ProductQuery
-
-		if not frappe.db.exists("Item Group", {"name": "Tech Items"}):
-			item_group_doc = frappe.get_doc({
-				"doctype": "Item Group",
-				"item_group_name": "Tech Items",
-				"parent_item_group": "All Item Groups",
-				"show_in_website": 1
-			}).insert()
-		else:
-			item_group_doc = frappe.get_doc("Item Group", "Tech Items")
-
-		doc = self.create_regular_web_item("Portal Item", item_group="Tech Items")
-		if not frappe.db.exists("Website Item Group", {"parent": "Portal Item"}):
-			doc.append("website_item_groups", {
-				"item_group": "_Test Item Group Desktops"
-			})
-			doc.save()
-
-		# check if item is visible in its own Item Group's page
-		engine = ProductQuery()
-		items = engine.query({}, {"item_group": "Tech Items"}, None, start=0, item_group="Tech Items")
-		self.assertEqual(len(items), 1)
-		self.assertEqual(items[0].item_code, "Portal Item")
-
-		# check if item is visible in configured foreign Item Group's page
-		engine = ProductQuery()
-		items = engine.query({}, {"item_group": "_Test Item Group Desktops"}, None, start=0, item_group="_Test Item Group Desktops")
-		item_codes = [row.item_code for row in items]
-
-		self.assertIn(len(items), [2, 3])
-		self.assertIn("Portal Item", item_codes)
-
-		# teardown
-		doc.delete()
-		item_group_doc.delete()
diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py
index 3ee2c88..a87471f 100644
--- a/erpnext/portal/utils.py
+++ b/erpnext/portal/utils.py
@@ -1,7 +1,7 @@
 import frappe
 from frappe.utils.nestedset import get_root_of
 
-from erpnext.shopping_cart.cart import get_debtors_account
+from erpnext.e_commerce.shopping_cart.cart import get_debtors_account
 from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
 	get_shopping_cart_settings,
 )
diff --git a/erpnext/public/js/conf.js b/erpnext/public/js/conf.js
index eb709e5..a0f56a2 100644
--- a/erpnext/public/js/conf.js
+++ b/erpnext/public/js/conf.js
@@ -21,6 +21,6 @@
 	'Geo': 'Settings',
 	'Portal': 'Website',
 	'Utilities': 'Settings',
-	'Shopping Cart': 'Website',
+	'E-commerce': 'Website',
 	'Contacts': 'CRM'
 });
diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js
index 227881a..6553801 100644
--- a/erpnext/public/js/shopping_cart.js
+++ b/erpnext/public/js/shopping_cart.js
@@ -63,7 +63,7 @@
 		$(".shopping-cart").on('shown.bs.dropdown', function() {
 			if (!$('.shopping-cart-menu .cart-container').length) {
 				return frappe.call({
-					method: 'erpnext.shopping_cart.cart.get_shopping_cart_menu',
+					method: 'erpnext.e_commerce.shopping_cart.cart.get_shopping_cart_menu',
 					callback: function(r) {
 						if (r.message) {
 							$('.shopping-cart-menu').html(r.message);
@@ -83,7 +83,7 @@
 		} else {
 			return frappe.call({
 				type: "POST",
-				method: "erpnext.shopping_cart.cart.update_cart",
+				method: "erpnext.e_commerce.shopping_cart.cart.update_cart",
 				args: {
 					item_code: opts.item_code,
 					qty: opts.qty,
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index d19e189..1e942d7 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -1,8 +1,6 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-from __future__ import unicode_literals
-
 import copy
 
 import frappe
@@ -13,9 +11,8 @@
 from frappe.website.website_generator import WebsiteGenerator
 from six.moves.urllib.parse import quote
 
-from erpnext.shopping_cart.filters import ProductFiltersBuilder
-from erpnext.shopping_cart.product_info import set_product_info_for_website
-from erpnext.shopping_cart.product_query import ProductQuery
+from erpnext.e_commerce.filters import ProductFiltersBuilder
+from erpnext.e_commerce.product_query import ProductQuery
 from erpnext.utilities.product import get_qty_in_stock
 
 
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 82452b6..36b3075 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -116,12 +116,14 @@
   "customer_code",
   "default_item_manufacturer",
   "default_manufacturer_part_no",
-  "total_projected_qty",
   "hub_publishing_sb",
   "publish_in_hub",
   "hub_category_to_publish",
   "hub_warehouse",
-  "synced_with_hub"
+  "synced_with_hub",
+  "more_information_section",
+  "published_in_website",
+  "total_projected_qty"
  ],
  "fields": [
   {
@@ -926,6 +928,20 @@
    "fieldtype": "Data",
    "label": "Default Manufacturer Part No",
    "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "more_information_section",
+   "fieldtype": "Section Break",
+   "label": "More Information"
+  },
+  {
+   "default": "0",
+   "depends_on": "published_in_website",
+   "fieldname": "published_in_website",
+   "fieldtype": "Check",
+   "label": "Published in Website",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-tag",
@@ -934,7 +950,7 @@
  "index_web_pages_for_search": 1,
  "links": [],
  "max_attachments": 1,
- "modified": "2021-08-27 12:23:07.277077",
+ "modified": "2021-10-12 12:23:07.277077",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 2185694..b0371c5 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -868,7 +868,7 @@
 
 def invalidate_item_variants_cache_for_website(doc):
 	"""Rebuild ItemVariantsCacheManager via Item or Website Item."""
-	from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
+	from erpnext.e_commerce.product_configurator.item_variants_cache import ItemVariantsCacheManager
 
 	item_code = None
 	is_web_item = doc.get("published_in_website") or doc.get("published")
diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js
index 8eadb84..5cb5d15 100644
--- a/erpnext/templates/generators/item/item_configure.js
+++ b/erpnext/templates/generators/item/item_configure.js
@@ -280,14 +280,14 @@
 	}
 
 	get_next_attribute_and_values(selected_attributes) {
-		return this.call('erpnext.portal.product_configurator.utils.get_next_attribute_and_values', {
+		return this.call('erpnext.e_commerce.product_configurator.utils.get_next_attribute_and_values', {
 			item_code: this.item_code,
 			selected_attributes
 		});
 	}
 
 	get_attributes_and_values() {
-		return this.call('erpnext.portal.product_configurator.utils.get_attributes_and_values', {
+		return this.call('erpnext.e_commerce.product_configurator.utils.get_attributes_and_values', {
 			item_code: this.item_code
 		});
 	}
diff --git a/erpnext/templates/generators/item/item_inquiry.js b/erpnext/templates/generators/item/item_inquiry.js
index 4724b68..0aee996 100644
--- a/erpnext/templates/generators/item/item_inquiry.js
+++ b/erpnext/templates/generators/item/item_inquiry.js
@@ -52,7 +52,7 @@
 
 		d.hide();
 
-		frappe.call('erpnext.shopping_cart.cart.create_lead_for_item_inquiry', {
+		frappe.call('erpnext.e_commerce.shopping_cart.cart.create_lead_for_item_inquiry', {
 			lead: doc,
 			subject: values.subject,
 			message: values.message
diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/includes/cart.js
index c390cd1..4de8ff7 100644
--- a/erpnext/templates/includes/cart.js
+++ b/erpnext/templates/includes/cart.js
@@ -48,7 +48,7 @@
 				const address_name = $card.closest('[data-address-name]').attr('data-address-name');
 				frappe.call({
 					type: "POST",
-					method: "erpnext.shopping_cart.cart.update_cart_address",
+					method: "erpnext.e_commerce.shopping_cart.cart.update_cart_address",
 					freeze: true,
 					args: {
 						address_type,
@@ -185,7 +185,7 @@
 		return frappe.call({
 			btn: btn,
 			type: "POST",
-			method: "erpnext.shopping_cart.cart.apply_shipping_rule",
+			method: "erpnext.e_commerce.shopping_cart.cart.apply_shipping_rule",
 			args: { shipping_rule: rule },
 			callback: function(r) {
 				if(!r.exc) {
@@ -198,7 +198,7 @@
 	place_order: function(btn) {
 		return frappe.call({
 			type: "POST",
-			method: "erpnext.shopping_cart.cart.place_order",
+			method: "erpnext.e_commerce.shopping_cart.cart.place_order",
 			btn: btn,
 			callback: function(r) {
 				if(r.exc) {
@@ -223,7 +223,7 @@
 	request_quotation: function(btn) {
 		return frappe.call({
 			type: "POST",
-			method: "erpnext.shopping_cart.cart.request_for_quotation",
+			method: "erpnext.e_commerce.shopping_cart.cart.request_for_quotation",
 			btn: btn,
 			callback: function(r) {
 				if(r.exc) {
@@ -254,7 +254,7 @@
 	apply_coupon_code: function(btn) {
 		return frappe.call({
 			type: "POST",
-			method: "erpnext.shopping_cart.cart.apply_coupon_code",
+			method: "erpnext.e_commerce.shopping_cart.cart.apply_coupon_code",
 			btn: btn,
 			args : {
 				applied_code : $('.txtcoupon').val(),
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 979298f..a9377f3 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -130,10 +130,10 @@
 			],
 			primary_action_label: __('Save'),
 			primary_action: (values) => {
-				frappe.call('erpnext.shopping_cart.cart.add_new_address', { doc: values })
+				frappe.call('erpnext.e_commerce.shopping_cart.cart.add_new_address', { doc: values })
 					.then(r => {
 						frappe.call({
-							method: "erpnext.shopping_cart.cart.update_cart_address",
+							method: "erpnext.e_commerce.shopping_cart.cart.update_cart_address",
 							args: {
 								address_type: r.message.address_type,
 								address_name: r.message.name
diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js
index 90a1d86..a3979d0 100644
--- a/erpnext/templates/includes/product_page.js
+++ b/erpnext/templates/includes/product_page.js
@@ -7,7 +7,7 @@
 
 	frappe.call({
 		type: "POST",
-		method: "erpnext.shopping_cart.product_info.get_product_info_for_website",
+		method: "erpnext.e_commerce.shopping_cart.product_info.get_product_info_for_website",
 		args: {
 			item_code: get_item_code()
 		},
diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html
index c64c634..0c7993b 100644
--- a/erpnext/templates/pages/cart.html
+++ b/erpnext/templates/pages/cart.html
@@ -96,7 +96,7 @@
 							})
 						});
 						function show_terms_and_conditions(terms_name) {
-							frappe.call('erpnext.shopping_cart.cart.get_terms_and_conditions', { terms_name })
+							frappe.call('erpnext.e_commerce.shopping_cart.cart.get_terms_and_conditions', { terms_name })
 							.then(r => {
 								frappe.msgprint({
 									title: terms_name,
diff --git a/erpnext/templates/pages/cart.py b/erpnext/templates/pages/cart.py
index 7c441f7..1f0cd5c 100644
--- a/erpnext/templates/pages/cart.py
+++ b/erpnext/templates/pages/cart.py
@@ -1,11 +1,9 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
 
 no_cache = 1
 
-
-from erpnext.shopping_cart.cart import get_cart_quotation
+from erpnext.e_commerce.shopping_cart.cart import get_cart_quotation
 
 
 def get_context(context):
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py
index 1b9df2b..e5686f0 100644
--- a/erpnext/templates/pages/product_search.py
+++ b/erpnext/templates/pages/product_search.py
@@ -7,7 +7,7 @@
 from frappe.utils import cint, cstr, nowdate
 
 from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_html
-from erpnext.shopping_cart.product_info import set_product_info_for_website
+from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website
 
 no_cache = 1
 
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py
index 688c029..d0871fc 100644
--- a/erpnext/www/all-products/index.py
+++ b/erpnext/www/all-products/index.py
@@ -1,7 +1,7 @@
 import frappe
 from frappe.utils import cint
-from erpnext.shopping_cart.product_query import ProductQuery
-from erpnext.shopping_cart.filters import ProductFiltersBuilder
+from erpnext.e_commerce.product_query import ProductQuery
+from erpnext.e_commerce.filters import ProductFiltersBuilder
 
 sitemap = 1