test: Product Query & Filter Engine, Item Group Page
- Test for ProductQuery engine and ProductFilters engine - Test for engine for Item Group too - Renamed ‘product_configurator’ to ‘variant_selector’ - Cleaned up filters.py - Modal freeze backdrop lighter only in cart, since there’s nothing over it - Fixed unusual spacing in variant selector dialog - Made `get_child_groups_for_website` more readable - Replaced ‘Configure’ with ‘Select’ for variant selection
This commit is contained in:
parent
7d1df9d4c3
commit
80fbe16be8
@ -132,7 +132,7 @@ def find_variant(template, args, variant_item_code=None):
|
||||
|
||||
conditions = " or ".join(conditions)
|
||||
|
||||
from erpnext.e_commerce.product_configurator.utils import get_item_codes_by_attributes
|
||||
from erpnext.e_commerce.variant_selector.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:
|
||||
|
@ -8,11 +8,22 @@ from frappe.utils import cint
|
||||
|
||||
from erpnext.e_commerce.product_data_engine.query import ProductQuery
|
||||
from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder
|
||||
from erpnext.setup.doctype.item_group.item_group import get_child_groups
|
||||
from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_product_filter_data(query_args=None):
|
||||
"""Get filtered products and discount filters."""
|
||||
"""
|
||||
Returns filtered products and discount filters.
|
||||
:param query_args (dict): contains filters to get products list
|
||||
|
||||
Query Args filters:
|
||||
search (str): Search Term.
|
||||
field_filters (dict): Keys include item_group, brand, etc.
|
||||
attribute_filters(dict): Keys include Color, Size, etc.
|
||||
start (int): Offset items by
|
||||
item_group (str): Valid Item Group
|
||||
from_filters (bool): Set as True to jump to page 1
|
||||
"""
|
||||
if isinstance(query_args, str):
|
||||
query_args = json.loads(query_args)
|
||||
|
||||
@ -35,7 +46,7 @@ def get_product_filter_data(query_args=None):
|
||||
sub_categories = []
|
||||
if item_group:
|
||||
field_filters['item_group'] = item_group
|
||||
sub_categories = get_child_groups(item_group)
|
||||
sub_categories = get_child_groups_for_website(item_group, immediate=True)
|
||||
|
||||
engine = ProductQuery()
|
||||
try:
|
||||
@ -46,7 +57,7 @@ def get_product_filter_data(query_args=None):
|
||||
start=start,
|
||||
item_group=item_group
|
||||
)
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
traceback = frappe.get_traceback()
|
||||
frappe.log_error(traceback, frappe._("Product Engine Error"))
|
||||
return {"exc": "Something went wrong!"}
|
||||
|
@ -26,6 +26,10 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
"price_list": "_Test Price List India"
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.rollback()
|
||||
|
||||
def setUp(self):
|
||||
if self._testMethodName in WEBITEM_DESK_TESTS:
|
||||
make_item("Test Web Item", {
|
||||
@ -38,22 +42,13 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
]
|
||||
})
|
||||
elif self._testMethodName in WEBITEM_PRICE_TESTS:
|
||||
self.create_regular_web_item()
|
||||
create_regular_web_item()
|
||||
make_web_item_price(item_code="Test Mobile Phone")
|
||||
make_web_pricing_rule(
|
||||
title="Test Pricing Rule for Test Mobile Phone",
|
||||
item_code="Test Mobile Phone",
|
||||
selling=1)
|
||||
|
||||
def tearDown(self):
|
||||
if self._testMethodName in WEBITEM_DESK_TESTS:
|
||||
frappe.get_doc("Item", "Test Web Item").delete()
|
||||
elif self._testMethodName in WEBITEM_PRICE_TESTS:
|
||||
frappe.delete_doc("Pricing Rule", "Test Pricing Rule for Test Mobile Phone")
|
||||
frappe.get_cached_doc("Item Price", {"item_code": "Test Mobile Phone"}).delete()
|
||||
frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete()
|
||||
|
||||
|
||||
def test_index_creation(self):
|
||||
"Check if index is getting created in db."
|
||||
from erpnext.e_commerce.doctype.website_item.website_item import on_doctype_update
|
||||
@ -105,6 +100,8 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
item.reload()
|
||||
self.assertEqual(item.published_in_website, 0)
|
||||
|
||||
item.delete()
|
||||
|
||||
def test_publish_variant_and_template(self):
|
||||
"Check if template is published on publishing variant."
|
||||
# template "Test Web Item" created on setUp
|
||||
@ -256,7 +253,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
2) Showing stock availability disabled
|
||||
"""
|
||||
item_code = "Test Mobile Phone"
|
||||
self.create_regular_web_item()
|
||||
create_regular_web_item()
|
||||
setup_e_commerce_settings({"show_stock_availability": 1})
|
||||
|
||||
frappe.local.shopping_cart_settings = None
|
||||
@ -298,7 +295,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
item_code = "Test Mobile Phone"
|
||||
self.create_regular_web_item()
|
||||
create_regular_web_item()
|
||||
setup_e_commerce_settings({"show_stock_availability": 1})
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
@ -339,7 +336,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
def test_recommended_item(self):
|
||||
"Check if added recommended items are fetched correctly."
|
||||
item_code = "Test Mobile Phone"
|
||||
web_item = self.create_regular_web_item(item_code)
|
||||
web_item = create_regular_web_item(item_code)
|
||||
|
||||
setup_e_commerce_settings({
|
||||
"enable_recommendations": 1,
|
||||
@ -347,7 +344,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
})
|
||||
|
||||
# create recommended web item and price for it
|
||||
recommended_web_item = self.create_regular_web_item("Test Mobile Phone 1")
|
||||
recommended_web_item = create_regular_web_item("Test Mobile Phone 1")
|
||||
make_web_item_price(item_code="Test Mobile Phone 1")
|
||||
|
||||
# add recommended item to first web item
|
||||
@ -379,14 +376,14 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
self.assertFalse(bool(recommended_items[0].get("price_info"))) # price not fetched
|
||||
|
||||
# tear down
|
||||
frappe.get_cached_doc("Item Price", {"item_code": "Test Mobile Phone 1"}).delete()
|
||||
web_item.delete()
|
||||
recommended_web_item.delete()
|
||||
frappe.get_cached_doc("Item", "Test Mobile Phone 1").delete()
|
||||
|
||||
def test_recommended_item_for_guest_user(self):
|
||||
"Check if added recommended items are fetched correctly for guest user."
|
||||
item_code = "Test Mobile Phone"
|
||||
web_item = self.create_regular_web_item(item_code)
|
||||
web_item = create_regular_web_item(item_code)
|
||||
|
||||
# price visible to guests
|
||||
setup_e_commerce_settings({
|
||||
@ -396,7 +393,7 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
})
|
||||
|
||||
# create recommended web item and price for it
|
||||
recommended_web_item = self.create_regular_web_item("Test Mobile Phone 1")
|
||||
recommended_web_item = create_regular_web_item("Test Mobile Phone 1")
|
||||
make_web_item_price(item_code="Test Mobile Phone 1")
|
||||
|
||||
# add recommended item to first web item
|
||||
@ -428,22 +425,24 @@ class TestWebsiteItem(unittest.TestCase):
|
||||
|
||||
# tear down
|
||||
frappe.set_user("Administrator")
|
||||
frappe.get_cached_doc("Item Price", {"item_code": "Test Mobile Phone 1"}).delete()
|
||||
web_item.delete()
|
||||
recommended_web_item.delete()
|
||||
frappe.get_cached_doc("Item", "Test Mobile Phone 1").delete()
|
||||
|
||||
def create_regular_web_item(self, item_code=None):
|
||||
"Create Regular Item and Website Item."
|
||||
item_code = item_code or "Test Mobile Phone"
|
||||
item = make_item(item_code)
|
||||
def create_regular_web_item(item_code=None, item_args=None, web_args=None):
|
||||
"Create Regular Item and Website Item."
|
||||
item_code = item_code or "Test Mobile Phone"
|
||||
item = make_item(item_code, properties=item_args)
|
||||
|
||||
if not frappe.db.exists("Website Item", {"item_code": item_code}):
|
||||
web_item = make_website_item(item, save=False)
|
||||
web_item.save()
|
||||
else:
|
||||
web_item = frappe.get_cached_doc("Website Item", {"item_code": item_code})
|
||||
if not frappe.db.exists("Website Item", {"item_code": item_code}):
|
||||
web_item = make_website_item(item, save=False)
|
||||
if web_args:
|
||||
web_item.update(web_args)
|
||||
web_item.save()
|
||||
else:
|
||||
web_item = frappe.get_cached_doc("Website Item", {"item_code": item_code})
|
||||
|
||||
return web_item
|
||||
return web_item
|
||||
|
||||
def make_web_item_price(**kwargs):
|
||||
item_code = kwargs.get("item_code")
|
||||
|
@ -1,91 +0,0 @@
|
||||
import frappe, unittest
|
||||
from erpnext.e_commerce.product_data_engine.query import ProductQuery
|
||||
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
||||
|
||||
test_dependencies = ["Item"]
|
||||
#TODO: Rename to test item variant configurator
|
||||
|
||||
class TestProductConfigurator(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.create_variant_item()
|
||||
self.publish_items_on_website()
|
||||
|
||||
# TODO: E-commerce server side tests
|
||||
# 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})
|
||||
|
||||
# products_settings = frappe.get_doc('Products Settings')
|
||||
# products_settings.enable_field_filters = 1
|
||||
# products_settings.append('filter_fields', {'fieldname': 'item_group'})
|
||||
# products_settings.append('filter_fields', {'fieldname': 'stock_uom'})
|
||||
# products_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()
|
||||
# result = engine.query({}, {"item_group": "Tech Items"}, None, start=0, item_group="Tech Items")
|
||||
# items = result["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()
|
||||
# result = engine.query({}, {"item_group": "_Test Item Group Desktops"}, None, start=0, item_group="_Test Item Group Desktops")
|
||||
# items = result["items"]
|
||||
# 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()
|
@ -6,7 +6,7 @@ from frappe.utils import floor
|
||||
|
||||
class ProductFiltersBuilder:
|
||||
def __init__(self, item_group=None):
|
||||
if not item_group or item_group == "E Commerce Settings":
|
||||
if not item_group:
|
||||
self.doc = frappe.get_doc("E Commerce Settings")
|
||||
else:
|
||||
self.doc = frappe.get_doc("Item Group", item_group)
|
||||
@ -17,36 +17,39 @@ class ProductFiltersBuilder:
|
||||
if not self.item_group and not self.doc.enable_field_filters:
|
||||
return
|
||||
|
||||
filter_fields = [row.fieldname for row in self.doc.filter_fields]
|
||||
fields, filter_data = [], []
|
||||
filter_fields = [row.fieldname for row in self.doc.filter_fields] # fields in settings
|
||||
|
||||
meta = frappe.get_meta('Item')
|
||||
fields = [df for df in meta.fields if df.fieldname in filter_fields]
|
||||
# filter valid field filters i.e. those that exist in Item
|
||||
item_meta = frappe.get_meta('Item', cached=True)
|
||||
fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)]
|
||||
|
||||
filter_data = []
|
||||
for df in fields:
|
||||
filters, or_filters = {}, []
|
||||
item_filters, item_or_filters = {}, []
|
||||
link_doctype_values = self.get_filtered_link_doctype_records(df)
|
||||
|
||||
if df.fieldtype == "Link":
|
||||
if self.item_group:
|
||||
or_filters.extend([
|
||||
item_or_filters.extend([
|
||||
["item_group", "=", self.item_group],
|
||||
["Website Item Group", "item_group", "=", self.item_group]
|
||||
["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
|
||||
])
|
||||
|
||||
values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, distinct="True", pluck=df.fieldname)
|
||||
# Get link field values attached to published items
|
||||
item_filters['published_in_website'] = 1
|
||||
item_values = frappe.get_all(
|
||||
"Item",
|
||||
fields=[df.fieldname],
|
||||
filters=item_filters,
|
||||
or_filters=item_or_filters,
|
||||
distinct="True",
|
||||
pluck=df.fieldname
|
||||
)
|
||||
|
||||
values = list(set(item_values) & link_doctype_values) # intersection of both
|
||||
else:
|
||||
doctype = df.get_link_doctype()
|
||||
|
||||
# apply enable/disable/show_in_website filter
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
if meta.has_field('enabled'):
|
||||
filters['enabled'] = 1
|
||||
if meta.has_field('disabled'):
|
||||
filters['disabled'] = 0
|
||||
if meta.has_field('show_in_website'):
|
||||
filters['show_in_website'] = 1
|
||||
|
||||
values = [d.name for d in frappe.get_all(doctype, filters)]
|
||||
# table multiselect
|
||||
values = list(link_doctype_values)
|
||||
|
||||
# Remove None
|
||||
if None in values:
|
||||
@ -57,6 +60,36 @@ class ProductFiltersBuilder:
|
||||
|
||||
return filter_data
|
||||
|
||||
def get_filtered_link_doctype_records(self, field):
|
||||
"""
|
||||
Get valid link doctype records depending on filters.
|
||||
Apply enable/disable/show_in_website filter.
|
||||
Returns:
|
||||
set: A set containing valid record names
|
||||
"""
|
||||
link_doctype = field.get_link_doctype()
|
||||
meta = frappe.get_meta(link_doctype, cached=True) if link_doctype else None
|
||||
if meta:
|
||||
filters = self.get_link_doctype_filters(meta)
|
||||
link_doctype_values = set(d.name for d in frappe.get_all(link_doctype, filters))
|
||||
|
||||
return link_doctype_values if meta else set()
|
||||
|
||||
def get_link_doctype_filters(self, meta):
|
||||
"Filters for Link Doctype eg. 'show_in_website'."
|
||||
filters = {}
|
||||
if not meta:
|
||||
return filters
|
||||
|
||||
if meta.has_field('enabled'):
|
||||
filters['enabled'] = 1
|
||||
if meta.has_field('disabled'):
|
||||
filters['disabled'] = 0
|
||||
if meta.has_field('show_in_website'):
|
||||
filters['show_in_website'] = 1
|
||||
|
||||
return filters
|
||||
|
||||
def get_attribute_filters(self):
|
||||
if not self.item_group and not self.doc.enable_attribute_filters:
|
||||
return
|
||||
@ -92,9 +125,9 @@ class ProductFiltersBuilder:
|
||||
def get_discount_filters(self, discounts):
|
||||
discount_filters = []
|
||||
|
||||
# [25.89, 60.5]
|
||||
# [25.89, 60.5] min max
|
||||
min_discount, max_discount = discounts[0], discounts[1]
|
||||
# [25, 60]
|
||||
# [25, 60] rounded min max
|
||||
min_range_absolute, max_range_absolute = floor(min_discount), floor(max_discount)
|
||||
min_range = int(min_discount - (min_range_absolute % 10)) # 20
|
||||
max_range = int(max_discount - (max_range_absolute % 10)) # 60
|
||||
|
@ -0,0 +1,116 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
from erpnext.e_commerce.api import get_product_filter_data
|
||||
from erpnext.e_commerce.doctype.website_item.test_website_item import create_regular_web_item
|
||||
|
||||
test_dependencies = ["Item", "Item Group"]
|
||||
|
||||
class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
"Test Products & Sub-Category Querying for Product Listing on Item Group Page."
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
item_codes = [
|
||||
("Test Mobile A", "_Test Item Group B"),
|
||||
("Test Mobile B", "_Test Item Group B"),
|
||||
("Test Mobile C", "_Test Item Group B - 1"),
|
||||
("Test Mobile D", "_Test Item Group B - 1"),
|
||||
("Test Mobile E", "_Test Item Group B - 2")
|
||||
]
|
||||
for item in item_codes:
|
||||
item_code = item[0]
|
||||
item_args = {"item_group": item[1]}
|
||||
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.rollback()
|
||||
|
||||
def test_product_listing_in_item_group(self):
|
||||
"Test if only products belonging to the Item Group are fetched."
|
||||
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]
|
||||
|
||||
self.assertEqual(len(items), 2)
|
||||
self.assertIn("Test Mobile A", item_codes)
|
||||
self.assertNotIn("Test Mobile C", item_codes)
|
||||
|
||||
def test_products_in_multiple_item_groups(self):
|
||||
"""Test if product is visible on multiple item group pages barring its own."""
|
||||
website_item = frappe.get_doc("Website Item", {"item_code": "Test Mobile E"})
|
||||
|
||||
# show item belonging to '_Test Item Group B - 2' in '_Test Item Group B - 1' as well
|
||||
website_item.append("website_item_groups", {
|
||||
"item_group": "_Test Item Group B - 1"
|
||||
})
|
||||
website_item.save()
|
||||
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B - 1"
|
||||
})
|
||||
|
||||
items = result.get("items")
|
||||
item_codes = [item.get("item_code") for item in items]
|
||||
|
||||
self.assertEqual(len(items), 3)
|
||||
self.assertIn("Test Mobile E", item_codes) # visible in other item groups
|
||||
self.assertIn("Test Mobile C", item_codes)
|
||||
self.assertIn("Test Mobile D", item_codes)
|
||||
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B - 2"
|
||||
})
|
||||
|
||||
items = result.get("items")
|
||||
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertEqual(items[0].get("item_code"), "Test Mobile E") # visible in own item group
|
||||
|
||||
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={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B"
|
||||
})
|
||||
|
||||
self.assertTrue(bool(result.get("sub_categories")))
|
||||
|
||||
child_groups = [d.name for d in result.get("sub_categories")]
|
||||
# check if child group is fetched if shown in website
|
||||
self.assertIn("_Test Item Group B - 1", child_groups)
|
||||
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1)
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B"
|
||||
})
|
||||
child_groups = [d.name for d in result.get("sub_categories")]
|
||||
|
||||
# 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)
|
@ -2,37 +2,337 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
test_dependencies = ["Item"]
|
||||
from erpnext.e_commerce.product_data_engine.query import ProductQuery
|
||||
from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder
|
||||
from erpnext.e_commerce.doctype.website_item.test_website_item import create_regular_web_item
|
||||
from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import setup_e_commerce_settings
|
||||
|
||||
test_dependencies = ["Item", "Item Group"]
|
||||
|
||||
class TestProductDataEngine(unittest.TestCase):
|
||||
"Test Products Querying for Product Listing."
|
||||
def test_product_list_ordering(self):
|
||||
"Check if website items appear by ranking."
|
||||
pass
|
||||
"Test Products Querying and Filters for Product Listing."
|
||||
|
||||
def test_product_list_paging(self):
|
||||
pass
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
item_codes = [
|
||||
("Test 11I Laptop", "Products"), # rank 1
|
||||
("Test 12I Laptop", "Products"), # rank 2
|
||||
("Test 13I Laptop", "Products"), # rank 3
|
||||
("Test 14I Laptop", "Raw Material"), # rank 4
|
||||
("Test 15I Laptop", "Raw Material"), # rank 5
|
||||
("Test 16I Laptop", "Raw Material"), # rank 6
|
||||
("Test 17I Laptop", "Products") # rank 7
|
||||
]
|
||||
for index, item in enumerate(item_codes, start=1):
|
||||
item_code = item[0]
|
||||
item_args = {"item_group": item[1]}
|
||||
web_args = {"ranking": index}
|
||||
if not frappe.db.exists("Website Item", {"item_code": item_code}):
|
||||
create_regular_web_item(item_code, item_args=item_args, web_args=web_args)
|
||||
|
||||
setup_e_commerce_settings({
|
||||
"products_per_page": 4,
|
||||
"enable_field_filters": 1,
|
||||
"filter_fields": [{"fieldname": "item_group"}],
|
||||
"enable_attribute_filters": 1,
|
||||
"filter_attributes": [{"attribute": "Test Size"}]
|
||||
})
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_product_list_ordering_and_paging(self):
|
||||
"Test if website items appear by ranking on different pages."
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
self.assertIsNotNone(items)
|
||||
self.assertEqual(len(items), 4)
|
||||
self.assertGreater(result.get("items_count"), 4)
|
||||
|
||||
# check if items appear as per ranking set in setUpClass
|
||||
self.assertEqual(items[0].get("item_code"), "Test 17I Laptop")
|
||||
self.assertEqual(items[1].get("item_code"), "Test 16I Laptop")
|
||||
self.assertEqual(items[2].get("item_code"), "Test 15I Laptop")
|
||||
self.assertEqual(items[3].get("item_code"), "Test 14I Laptop")
|
||||
|
||||
# check next page
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=4,
|
||||
item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
# check if items appear as per ranking set in setUpClass on next page
|
||||
self.assertEqual(items[0].get("item_code"), "Test 13I Laptop")
|
||||
self.assertEqual(items[1].get("item_code"), "Test 12I Laptop")
|
||||
self.assertEqual(items[2].get("item_code"), "Test 11I Laptop")
|
||||
|
||||
def test_change_product_ranking(self):
|
||||
"Test if item on second page appear on first if ranking is changed."
|
||||
item_code = "Test 12I Laptop"
|
||||
old_ranking = frappe.db.get_value("Website Item", {"item_code": item_code}, "ranking")
|
||||
|
||||
# low rank, appears on second page
|
||||
self.assertEqual(old_ranking, 2)
|
||||
|
||||
# set ranking as highest rank
|
||||
frappe.db.set_value("Website Item", {"item_code": item_code}, "ranking", 10)
|
||||
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
# check if item is the first item on the first page
|
||||
self.assertEqual(items[0].get("item_code"), item_code)
|
||||
self.assertEqual(items[1].get("item_code"), "Test 17I Laptop")
|
||||
|
||||
# tear down
|
||||
frappe.db.set_value("Website Item", {"item_code": item_code}, "ranking", old_ranking)
|
||||
|
||||
def test_product_list_field_filter_builder(self):
|
||||
"Test if field filters are fetched correctly."
|
||||
frappe.db.set_value("Item Group", "Raw Material", "show_in_website", 0)
|
||||
|
||||
filter_engine = ProductFiltersBuilder()
|
||||
field_filters = filter_engine.get_field_filters()
|
||||
|
||||
# Web Items belonging to 'Products' and 'Raw Material' are available
|
||||
# but only 'Products' has 'show_in_website' enabled
|
||||
item_group_filters = field_filters[0]
|
||||
docfield = item_group_filters[0]
|
||||
valid_item_groups = item_group_filters[1]
|
||||
|
||||
self.assertEqual(docfield.options, "Item Group")
|
||||
self.assertIn("Products", valid_item_groups)
|
||||
self.assertNotIn("Raw Material", valid_item_groups)
|
||||
|
||||
frappe.db.set_value("Item Group", "Raw Material", "show_in_website", 1)
|
||||
field_filters = filter_engine.get_field_filters()
|
||||
|
||||
#'Products' and 'Raw Materials' both have 'show_in_website' enabled
|
||||
item_group_filters = field_filters[0]
|
||||
docfield = item_group_filters[0]
|
||||
valid_item_groups = item_group_filters[1]
|
||||
|
||||
self.assertEqual(docfield.options, "Item Group")
|
||||
self.assertIn("Products", valid_item_groups)
|
||||
self.assertIn("Raw Material", valid_item_groups)
|
||||
|
||||
def test_product_list_with_field_filter(self):
|
||||
pass
|
||||
"Test if field filters are applied correctly."
|
||||
field_filters = {"item_group": "Raw Material"}
|
||||
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields=field_filters,
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
# check if only 'Raw Material' are fetched in the right order
|
||||
self.assertEqual(len(items), 3)
|
||||
self.assertEqual(items[0].get("item_code"), "Test 16I Laptop")
|
||||
self.assertEqual(items[1].get("item_code"), "Test 15I Laptop")
|
||||
|
||||
# def test_product_list_with_field_filter_table_multiselect(self):
|
||||
# TODO
|
||||
# pass
|
||||
|
||||
def test_product_list_attribute_filter_builder(self):
|
||||
"Test if attribute filters are fetched correctly."
|
||||
create_variant_web_item()
|
||||
|
||||
filter_engine = ProductFiltersBuilder()
|
||||
attribute_filter = filter_engine.get_attribute_filters()[0]
|
||||
attribute = attribute_filter.item_attribute_values[0]
|
||||
|
||||
self.assertEqual(attribute_filter.name, "Test Size")
|
||||
self.assertEqual(len(attribute_filter.item_attribute_values), 1)
|
||||
self.assertEqual(attribute.attribute_value, "Large")
|
||||
|
||||
def test_product_list_with_attribute_filter(self):
|
||||
pass
|
||||
"Test if attribute filters are applied correctly."
|
||||
create_variant_web_item()
|
||||
|
||||
def test_product_list_with_discount_filter(self):
|
||||
pass
|
||||
attribute_filters = {"Test Size": ["Large"]}
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes=attribute_filters,
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
def test_product_list_with_mixed_filtes(self):
|
||||
pass
|
||||
# check if only items with Test Size 'Large' are fetched
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertEqual(items[0].get("item_code"), "Test Web Item-L")
|
||||
|
||||
def test_product_list_with_mixed_filtes_item_group(self):
|
||||
pass
|
||||
def test_product_list_discount_filter_builder(self):
|
||||
"Test if discount filters are fetched correctly."
|
||||
from erpnext.e_commerce.doctype.website_item.test_website_item import make_web_item_price, make_web_pricing_rule
|
||||
|
||||
def test_products_in_multiple_item_groups(self):
|
||||
"Check if product is visible on multiple item group pages barring its own."
|
||||
pass
|
||||
item_code = "Test 12I Laptop"
|
||||
make_web_item_price(item_code=item_code)
|
||||
make_web_pricing_rule(
|
||||
title=f"Test Pricing Rule for {item_code}",
|
||||
item_code=item_code,
|
||||
selling=1
|
||||
)
|
||||
|
||||
setup_e_commerce_settings({"show_price": 1})
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=4,
|
||||
item_group=None
|
||||
)
|
||||
|
||||
self.assertTrue(bool(result.get("discounts")))
|
||||
|
||||
filter_engine = ProductFiltersBuilder()
|
||||
discount_filters = filter_engine.get_discount_filters(result["discounts"])
|
||||
|
||||
self.assertEqual(len(discount_filters[0]), 2)
|
||||
self.assertEqual(discount_filters[0][0], 10)
|
||||
self.assertEqual(discount_filters[0][1], "10% and above")
|
||||
|
||||
def test_product_list_with_discount_filters(self):
|
||||
"Test if discount filters are applied correctly."
|
||||
from erpnext.e_commerce.doctype.website_item.test_website_item import make_web_item_price, make_web_pricing_rule
|
||||
|
||||
field_filters = {"discount": [10]}
|
||||
|
||||
make_web_item_price(item_code="Test 12I Laptop")
|
||||
make_web_pricing_rule(
|
||||
title="Test Pricing Rule for Test 12I Laptop", # 10% discount
|
||||
item_code="Test 12I Laptop",
|
||||
selling=1
|
||||
)
|
||||
make_web_item_price(item_code="Test 13I Laptop")
|
||||
make_web_pricing_rule(
|
||||
title="Test Pricing Rule for Test 13I Laptop", # 15% discount
|
||||
item_code="Test 13I Laptop",
|
||||
discount_percentage=15,
|
||||
selling=1
|
||||
)
|
||||
|
||||
setup_e_commerce_settings({"show_price": 1})
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes={},
|
||||
fields=field_filters,
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
# check if only product with 10% and above discount are fetched in the right order
|
||||
self.assertEqual(len(items), 2)
|
||||
self.assertEqual(items[0].get("item_code"), "Test 13I Laptop")
|
||||
self.assertEqual(items[1].get("item_code"), "Test 12I Laptop")
|
||||
|
||||
def test_product_list_with_api(self):
|
||||
"Test products listing using API."
|
||||
from erpnext.e_commerce.api import get_product_filter_data
|
||||
|
||||
create_variant_web_item()
|
||||
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {
|
||||
"item_group": "Products"
|
||||
},
|
||||
"attribute_filters": {
|
||||
"Test Size": ["Large"]
|
||||
},
|
||||
"start": 0
|
||||
})
|
||||
|
||||
items = result.get("items")
|
||||
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertEqual(items[0].get("item_code"), "Test Web Item-L")
|
||||
|
||||
def test_product_list_with_variants(self):
|
||||
pass
|
||||
"Test if variants are hideen on hiding variants in settings."
|
||||
create_variant_web_item()
|
||||
|
||||
setup_e_commerce_settings({
|
||||
"enable_attribute_filters": 0,
|
||||
"hide_variants": 1
|
||||
})
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
attribute_filters = {"Test Size": ["Large"]}
|
||||
engine = ProductQuery()
|
||||
result = engine.query(
|
||||
attributes=attribute_filters,
|
||||
fields={},
|
||||
search_term=None,
|
||||
start=0,
|
||||
item_group=None
|
||||
)
|
||||
items = result.get("items")
|
||||
|
||||
# check if any variants are fetched even though published variant exists
|
||||
self.assertEqual(len(items), 0)
|
||||
|
||||
# tear down
|
||||
setup_e_commerce_settings({
|
||||
"enable_attribute_filters": 1,
|
||||
"hide_variants": 0
|
||||
})
|
||||
|
||||
def create_variant_web_item():
|
||||
"Create Variant and Template Website Items."
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.controllers.item_variant import create_variant
|
||||
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
||||
|
||||
make_item("Test Web Item", {
|
||||
"has_variant": 1,
|
||||
"variant_based_on": "Item Attribute",
|
||||
"attributes": [
|
||||
{
|
||||
"attribute": "Test Size"
|
||||
}
|
||||
]
|
||||
})
|
||||
if not frappe.db.exists("Item", "Test Web Item-L"):
|
||||
variant = create_variant("Test Web Item", {"Test Size": "Large"})
|
||||
variant.save()
|
||||
|
||||
if not frappe.db.exists("Website Item", {"variant_of": "Test Web Item"}):
|
||||
make_website_item(variant, save=True)
|
10
erpnext/e_commerce/variant_selector/test_variant_selector.py
Normal file
10
erpnext/e_commerce/variant_selector/test_variant_selector.py
Normal file
@ -0,0 +1,10 @@
|
||||
# import frappe
|
||||
import unittest
|
||||
# from erpnext.e_commerce.product_data_engine.query import ProductQuery
|
||||
# from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
||||
|
||||
test_dependencies = ["Item"]
|
||||
|
||||
class TestVariantSelector(unittest.TestCase):
|
||||
# TODO: Variant Selector Tests
|
||||
pass
|
@ -1,7 +1,6 @@
|
||||
import frappe
|
||||
from frappe.utils import cint
|
||||
|
||||
from erpnext.e_commerce.product_configurator.item_variants_cache import ItemVariantsCacheManager
|
||||
from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager
|
||||
|
||||
def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
|
||||
items = []
|
@ -502,12 +502,7 @@ body.product-page {
|
||||
}
|
||||
|
||||
.item-configurator-dialog {
|
||||
.modal-header {
|
||||
padding: var(--padding-md) var(--padding-xl);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0 var(--padding-xl);
|
||||
padding-bottom: var(--padding-xl);
|
||||
|
||||
.status-area {
|
||||
@ -1292,13 +1287,10 @@ body.product-page {
|
||||
font-size: 72px;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background-color: var(--gray-100);
|
||||
height: 100%;
|
||||
[data-path="cart"] {
|
||||
.modal-backdrop {
|
||||
background-color: var(--gray-50); // lighter backdrop only on cart freeze
|
||||
}
|
||||
}
|
||||
|
||||
.item-thumb {
|
||||
|
@ -104,16 +104,23 @@ class ItemGroup(NestedSet, WebsiteGenerator):
|
||||
def delete_child_item_groups_key(self):
|
||||
frappe.cache().hdel("child_item_groups", self.name)
|
||||
|
||||
def validate_item_group_defaults(self):
|
||||
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(item_group_name):
|
||||
def get_child_groups_for_website(item_group_name, immediate=False):
|
||||
"""Returns child item groups *excluding* passed group."""
|
||||
item_group = frappe.get_doc("Item Group", item_group_name)
|
||||
return frappe.db.sql("""select name, route
|
||||
from `tabItem Group` where lft>%(lft)s and rgt<%(rgt)s
|
||||
and show_in_website = 1""", {"lft": item_group.lft, "rgt": item_group.rgt}, as_dict=1)
|
||||
item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1)
|
||||
filters = {
|
||||
"lft": [">", item_group.lft],
|
||||
"rgt": ["<", item_group.rgt],
|
||||
"show_in_website": 1
|
||||
}
|
||||
|
||||
if immediate:
|
||||
filters["parent_item_group"] = item_group_name
|
||||
|
||||
return frappe.get_all(
|
||||
"Item Group",
|
||||
filters=filters,
|
||||
fields=["name", "route"]
|
||||
)
|
||||
|
||||
def get_child_item_groups(item_group_name):
|
||||
item_group = frappe.get_cached_value("Item Group",
|
||||
|
@ -914,7 +914,7 @@ def invalidate_cache_for_item(doc):
|
||||
|
||||
def invalidate_item_variants_cache_for_website(doc):
|
||||
"""Rebuild ItemVariantsCacheManager via Item or Website Item."""
|
||||
from erpnext.e_commerce.product_configurator.item_variants_cache import ItemVariantsCacheManager
|
||||
from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager
|
||||
|
||||
item_code = None
|
||||
is_web_item = doc.get("published_in_website") or doc.get("published")
|
||||
|
@ -3,11 +3,11 @@
|
||||
|
||||
<div class="mt-5 mb-6">
|
||||
{% if cart_settings.enable_variants | int %}
|
||||
<button class="btn btn-primary-light btn-configure font-md"
|
||||
<button class="btn btn-primary-light btn-configure font-md mr-2"
|
||||
data-item-code="{{ doc.name }}"
|
||||
data-item-name="{{ doc.item_name }}"
|
||||
>
|
||||
{{ _('Configure') }}
|
||||
{{ _('Select Variant') }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if cart_settings.show_contact_us_button %}
|
||||
|
@ -29,7 +29,7 @@ class ItemConfigure {
|
||||
});
|
||||
|
||||
this.dialog = new frappe.ui.Dialog({
|
||||
title: __('Configure {0}', [this.item_name]),
|
||||
title: __('Select Variant for {0}', [this.item_name]),
|
||||
fields,
|
||||
on_hide: () => {
|
||||
set_continue_configuration();
|
||||
@ -280,14 +280,14 @@ class ItemConfigure {
|
||||
}
|
||||
|
||||
get_next_attribute_and_values(selected_attributes) {
|
||||
return this.call('erpnext.e_commerce.product_configurator.utils.get_next_attribute_and_values', {
|
||||
return this.call('erpnext.e_commerce.variant_selector.utils.get_next_attribute_and_values', {
|
||||
item_code: this.item_code,
|
||||
selected_attributes
|
||||
});
|
||||
}
|
||||
|
||||
get_attributes_and_values() {
|
||||
return this.call('erpnext.e_commerce.product_configurator.utils.get_attributes_and_values', {
|
||||
return this.call('erpnext.e_commerce.variant_selector.utils.get_attributes_and_values', {
|
||||
item_code: this.item_code
|
||||
});
|
||||
}
|
||||
@ -311,9 +311,9 @@ function set_continue_configuration() {
|
||||
const { itemCode } = $btn_configure.data();
|
||||
|
||||
if (localStorage.getItem(`configure:${itemCode}`)) {
|
||||
$btn_configure.text(__('Continue Configuration'));
|
||||
$btn_configure.text(__('Continue Selection'));
|
||||
} else {
|
||||
$btn_configure.text(__('Configure'));
|
||||
$btn_configure.text(__('Select Variant'));
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user