From b2755f6fdddd3e1b0a305b57c18651c98fee8f7e Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 7 Mar 2022 13:02:08 +0530 Subject: [PATCH] feat: Include child item group products in Item Group Page & cleanup - Added 'Include descendants' checkbox, which will pull child item group products too - Build item group filters in query engine file - Include logic in filter engine - Clean up Website section of Item Group page (UX) - Add util to fetch child item groups including self --- erpnext/e_commerce/api.py | 1 - .../e_commerce/product_data_engine/filters.py | 22 ++++++--- .../e_commerce/product_data_engine/query.py | 48 ++++++++----------- .../setup/doctype/item_group/item_group.json | 31 +++++++++--- .../setup/doctype/item_group/item_group.py | 8 +++- 5 files changed, 69 insertions(+), 41 deletions(-) diff --git a/erpnext/e_commerce/api.py b/erpnext/e_commerce/api.py index 43cb36ca2e..84554ae7d0 100644 --- a/erpnext/e_commerce/api.py +++ b/erpnext/e_commerce/api.py @@ -48,7 +48,6 @@ def get_product_filter_data(query_args=None): 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 c4a3cb9fbe..89d4ffdff4 100644 --- a/erpnext/e_commerce/product_data_engine/filters.py +++ b/erpnext/e_commerce/product_data_engine/filters.py @@ -14,6 +14,8 @@ class ProductFiltersBuilder: 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 @@ class ProductFiltersBuilder: 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 cfc3c7b357..5c92e3d98a 100644 --- a/erpnext/e_commerce/product_data_engine/query.py +++ b/erpnext/e_commerce/product_data_engine/query.py @@ -46,10 +46,10 @@ class ProductQuery: 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 @@ class ProductQuery: 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 @@ class ProductQuery: # `=` 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 @@ class ProductQuery: 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 @@ class ProductQuery: 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/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json index 3e0680f4f5..a090c8d76c 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,21 @@ "options": "Website Attribute" }, { + "depends_on": "show_in_website", "fieldname": "website_title", "fieldtype": "Data", "label": "Title" + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Include Website Items belonging to child Item Groups", + "fieldname": "include_descendants", + "fieldtype": "Check", + "label": "Include Descendants" } ], "icon": "fa fa-sitemap", @@ -214,11 +231,12 @@ "is_tree": 1, "links": [], "max_attachments": 3, - "modified": "2021-02-18 13:40:30.049650", + "modified": "2022-03-07 09:44:47.561532", "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 +303,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 4f92240c84..5c7194baf6 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -111,7 +111,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): 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 @@ def get_child_groups_for_website(item_group_name, immediate=False): 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,