247 lines
7.4 KiB
Python
247 lines
7.4 KiB
Python
import frappe
|
|
from frappe.utils import cint, flt
|
|
|
|
from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
|
|
get_shopping_cart_settings,
|
|
)
|
|
from erpnext.e_commerce.shopping_cart.cart import _set_price_list
|
|
from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager
|
|
from erpnext.utilities.product import get_price
|
|
|
|
|
|
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)]) # nosemgrep
|
|
items.append(item_codes)
|
|
|
|
res = list(set.intersection(*items))
|
|
|
|
return res
|
|
|
|
|
|
@frappe.whitelist(allow_guest=True)
|
|
def get_attributes_and_values(item_code):
|
|
"""Build a list of attributes and their possible values.
|
|
This will ignore the values upon selection of which there cannot exist one item.
|
|
"""
|
|
item_cache = ItemVariantsCacheManager(item_code)
|
|
item_variants_data = item_cache.get_item_variants_data()
|
|
|
|
attributes = get_item_attributes(item_code)
|
|
attribute_list = [a.attribute for a in attributes]
|
|
|
|
valid_options = {}
|
|
for item_code, attribute, attribute_value in item_variants_data:
|
|
if attribute in attribute_list:
|
|
valid_options.setdefault(attribute, set()).add(attribute_value)
|
|
|
|
item_attribute_values = frappe.db.get_all(
|
|
"Item Attribute Value", ["parent", "attribute_value", "idx"], order_by="parent asc, idx asc"
|
|
)
|
|
ordered_attribute_value_map = frappe._dict()
|
|
for iv in item_attribute_values:
|
|
ordered_attribute_value_map.setdefault(iv.parent, []).append(iv.attribute_value)
|
|
|
|
# build attribute values in idx order
|
|
for attr in attributes:
|
|
valid_attribute_values = valid_options.get(attr.attribute, [])
|
|
ordered_values = ordered_attribute_value_map.get(attr.attribute, [])
|
|
attr["values"] = [v for v in ordered_values if v in valid_attribute_values]
|
|
|
|
return attributes
|
|
|
|
|
|
@frappe.whitelist(allow_guest=True)
|
|
def get_next_attribute_and_values(item_code, selected_attributes):
|
|
"""Find the count of Items that match the selected attributes.
|
|
Also, find the attribute values that are not applicable for further searching.
|
|
If less than equal to 10 items are found, return item_codes of those items.
|
|
If one item is matched exactly, return item_code of that item.
|
|
"""
|
|
selected_attributes = frappe.parse_json(selected_attributes)
|
|
|
|
item_cache = ItemVariantsCacheManager(item_code)
|
|
item_variants_data = item_cache.get_item_variants_data()
|
|
|
|
attributes = get_item_attributes(item_code)
|
|
attribute_list = [a.attribute for a in attributes]
|
|
filtered_items = get_items_with_selected_attributes(item_code, selected_attributes)
|
|
|
|
next_attribute = None
|
|
|
|
for attribute in attribute_list:
|
|
if attribute not in selected_attributes:
|
|
next_attribute = attribute
|
|
break
|
|
|
|
valid_options_for_attributes = frappe._dict()
|
|
|
|
for a in attribute_list:
|
|
valid_options_for_attributes[a] = set()
|
|
|
|
selected_attribute = selected_attributes.get(a, None)
|
|
if selected_attribute:
|
|
# already selected attribute values are valid options
|
|
valid_options_for_attributes[a].add(selected_attribute)
|
|
|
|
for row in item_variants_data:
|
|
item_code, attribute, attribute_value = row
|
|
if (
|
|
item_code in filtered_items
|
|
and attribute not in selected_attributes
|
|
and attribute in attribute_list
|
|
):
|
|
valid_options_for_attributes[attribute].add(attribute_value)
|
|
|
|
optional_attributes = item_cache.get_optional_attributes()
|
|
exact_match = []
|
|
# search for exact match if all selected attributes are required attributes
|
|
if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)):
|
|
item_attribute_value_map = item_cache.get_item_attribute_value_map()
|
|
for item_code, attr_dict in item_attribute_value_map.items():
|
|
if item_code in filtered_items and set(attr_dict.keys()) == set(selected_attributes.keys()):
|
|
exact_match.append(item_code)
|
|
|
|
filtered_items_count = len(filtered_items)
|
|
|
|
# get product info if exact match
|
|
# from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
|
|
if exact_match:
|
|
cart_settings = get_shopping_cart_settings()
|
|
product_info = get_item_variant_price_dict(exact_match[0], cart_settings)
|
|
|
|
if product_info:
|
|
product_info["is_stock_item"] = frappe.get_cached_value("Item", exact_match[0], "is_stock_item")
|
|
product_info["allow_items_not_in_stock"] = cint(cart_settings.allow_items_not_in_stock)
|
|
else:
|
|
product_info = None
|
|
|
|
product_id = ""
|
|
website_warehouse = ""
|
|
if exact_match or filtered_items:
|
|
if exact_match and len(exact_match) == 1:
|
|
product_id = exact_match[0]
|
|
elif filtered_items_count == 1:
|
|
product_id = list(filtered_items)[0]
|
|
|
|
if product_id:
|
|
website_warehouse = frappe.get_cached_value(
|
|
"Website Item", {"item_code": product_id}, "website_warehouse"
|
|
)
|
|
|
|
available_qty = 0.0
|
|
if website_warehouse:
|
|
available_qty = flt(
|
|
frappe.db.get_value(
|
|
"Bin", {"item_code": product_id, "warehouse": website_warehouse}, "actual_qty"
|
|
)
|
|
)
|
|
|
|
return {
|
|
"next_attribute": next_attribute,
|
|
"valid_options_for_attributes": valid_options_for_attributes,
|
|
"filtered_items_count": filtered_items_count,
|
|
"filtered_items": filtered_items if filtered_items_count < 10 else [],
|
|
"exact_match": exact_match,
|
|
"product_info": product_info,
|
|
"available_qty": available_qty,
|
|
}
|
|
|
|
|
|
def get_items_with_selected_attributes(item_code, selected_attributes):
|
|
item_cache = ItemVariantsCacheManager(item_code)
|
|
attribute_value_item_map = item_cache.get_attribute_value_item_map()
|
|
|
|
items = []
|
|
for attribute, value in selected_attributes.items():
|
|
filtered_items = attribute_value_item_map.get((attribute, value), [])
|
|
items.append(set(filtered_items))
|
|
|
|
return set.intersection(*items)
|
|
|
|
|
|
# utilities
|
|
|
|
|
|
def get_item_attributes(item_code):
|
|
attributes = frappe.db.get_all(
|
|
"Item Variant Attribute",
|
|
fields=["attribute"],
|
|
filters={"parenttype": "Item", "parent": item_code},
|
|
order_by="idx asc",
|
|
)
|
|
|
|
optional_attributes = ItemVariantsCacheManager(item_code).get_optional_attributes()
|
|
|
|
for a in attributes:
|
|
if a.attribute in optional_attributes:
|
|
a.optional = True
|
|
|
|
return attributes
|
|
|
|
|
|
def get_item_variant_price_dict(item_code, cart_settings):
|
|
if cart_settings.enabled and cart_settings.show_price:
|
|
is_guest = frappe.session.user == "Guest"
|
|
# Show Price if logged in.
|
|
# If not logged in, check if price is hidden for guest.
|
|
if not is_guest or not cart_settings.hide_price_for_guest:
|
|
price_list = _set_price_list(cart_settings, None)
|
|
price = get_price(
|
|
item_code, price_list, cart_settings.default_customer_group, cart_settings.company
|
|
)
|
|
return {"price": price}
|
|
|
|
return None
|