f913e0dd05
- Since table multiselect fields were not handled, the query tried searching for this child field in item master - This broke the query - On trying to reload or go back to all-products page with field filters that are table mutiselect, page breaks
160 lines
4.9 KiB
Python
160 lines
4.9 KiB
Python
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# License: GNU General Public License v3. See license.txt
|
|
|
|
import frappe
|
|
from erpnext.shopping_cart.product_info import get_product_info_for_website
|
|
|
|
class ProductQuery:
|
|
"""Query engine for product listing
|
|
|
|
Attributes:
|
|
cart_settings (Document): Settings for Cart
|
|
fields (list): Fields to fetch in query
|
|
filters (TYPE): Description
|
|
or_filters (list): Description
|
|
page_length (Int): Length of page for the query
|
|
settings (Document): Products Settings DocType
|
|
filters (list)
|
|
or_filters (list)
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.settings = frappe.get_doc("Products Settings")
|
|
self.cart_settings = frappe.get_doc("Shopping Cart Settings")
|
|
self.page_length = self.settings.products_per_page or 20
|
|
self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants',
|
|
'item_group', 'image', 'web_long_description', 'description', 'route', 'weightage']
|
|
self.filters = []
|
|
self.or_filters = [['show_in_website', '=', 1]]
|
|
if not self.settings.get('hide_variants'):
|
|
self.or_filters.append(['show_variant_in_website', '=', 1])
|
|
|
|
def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
|
|
"""Summary
|
|
|
|
Args:
|
|
attributes (dict, optional): Item Attribute filters
|
|
fields (dict, optional): Field level filters
|
|
search_term (str, optional): Search term to lookup
|
|
start (int, optional): Page start
|
|
|
|
Returns:
|
|
list: List of results with set fields
|
|
"""
|
|
if fields: self.build_fields_filters(fields)
|
|
if search_term: self.build_search_filters(search_term)
|
|
|
|
result = []
|
|
website_item_groups = []
|
|
|
|
# if from item group page consider website item group table
|
|
if item_group:
|
|
website_item_groups = frappe.db.get_all(
|
|
"Item",
|
|
fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"],
|
|
filters=[["Website Item Group", "item_group", "=", item_group]]
|
|
)
|
|
|
|
if attributes:
|
|
all_items = []
|
|
for attribute, values in attributes.items():
|
|
if not isinstance(values, list):
|
|
values = [values]
|
|
|
|
items = frappe.get_all(
|
|
"Item",
|
|
fields=self.fields,
|
|
filters=[
|
|
*self.filters,
|
|
["Item Variant Attribute", "attribute", "=", attribute],
|
|
["Item Variant Attribute", "attribute_value", "in", values],
|
|
],
|
|
or_filters=self.or_filters,
|
|
start=start,
|
|
limit=self.page_length
|
|
)
|
|
|
|
items_dict = {item.name: item for item in items}
|
|
|
|
all_items.append(set(items_dict.keys()))
|
|
|
|
result = [items_dict.get(item) for item in list(set.intersection(*all_items))]
|
|
else:
|
|
result = frappe.get_all(
|
|
"Item",
|
|
fields=self.fields,
|
|
filters=self.filters,
|
|
or_filters=self.or_filters,
|
|
start=start,
|
|
limit=self.page_length
|
|
)
|
|
|
|
# Combine results having 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)
|
|
|
|
result = sorted(result, key=lambda x: x.get("weightage"), reverse=True)
|
|
|
|
for item in result:
|
|
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
|
|
if product_info:
|
|
item.formatted_price = (product_info.get('price') or {}).get('formatted_price')
|
|
|
|
return result
|
|
|
|
def build_fields_filters(self, filters):
|
|
"""Build filters for field values
|
|
|
|
Args:
|
|
filters (dict): Filters
|
|
"""
|
|
for field, values in filters.items():
|
|
if not values:
|
|
continue
|
|
|
|
# handle multiselect fields in filter addition
|
|
meta = frappe.get_meta('Item', cached=True)
|
|
df = meta.get_field(field)
|
|
if df.fieldtype == 'Table MultiSelect':
|
|
child_doctype = df.options
|
|
child_meta = frappe.get_meta(child_doctype, cached=True)
|
|
fields = child_meta.get("fields", { "fieldtype": "Link", "in_list_view": 1 })
|
|
if fields:
|
|
self.filters.append([child_doctype, fields[0].fieldname, 'IN', values])
|
|
continue
|
|
|
|
if isinstance(values, list):
|
|
# If value is a list use `IN` query
|
|
self.filters.append([field, 'IN', values])
|
|
else:
|
|
# `=` will be faster than `IN` for most cases
|
|
self.filters.append([field, '=', values])
|
|
|
|
def build_search_filters(self, search_term):
|
|
"""Query search term in specified fields
|
|
|
|
Args:
|
|
search_term (str): Search candidate
|
|
"""
|
|
# Default fields to search from
|
|
default_fields = {'name', 'item_name', 'description', 'item_group'}
|
|
|
|
# Get meta search fields
|
|
meta = frappe.get_meta("Item")
|
|
meta_fields = set(meta.get_search_fields())
|
|
|
|
# Join the meta fields and default fields set
|
|
search_fields = default_fields.union(meta_fields)
|
|
try:
|
|
if frappe.db.count('Item', cache=True) > 50000:
|
|
search_fields.remove('description')
|
|
except KeyError:
|
|
pass
|
|
|
|
# Build or filters for query
|
|
search = '%{}%'.format(search_term)
|
|
self.or_filters += [[field, 'like', search] for field in search_fields]
|