diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js index 131a5e439dc..6302d260e0a 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js @@ -25,9 +25,9 @@ frappe.ui.form.on("E Commerce Settings", { } frappe.model.with_doctype("Item", () => { - const item_meta = frappe.get_meta('Item'); + const web_item_meta = frappe.get_meta('Website Item'); - const valid_fields = item_meta.fields.filter( + const valid_fields = web_item_meta.fields.filter( df => ["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden ).map(df => ({ label: df.label, value: df.fieldname })); diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json index 67b4a3d7f82..d5fb9697f89 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json @@ -91,7 +91,7 @@ "depends_on": "enable_field_filters", "fieldname": "filter_fields", "fieldtype": "Table", - "label": "Item Fields", + "label": "Website Item Fields", "options": "Website Filter Field" }, { @@ -370,7 +370,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-08-31 12:23:06.187619", + "modified": "2021-09-02 14:02:44.785824", "modified_by": "Administrator", "module": "E-commerce", "name": "E Commerce Settings", diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json index c33cb51ea3c..245042addb8 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.json +++ b/erpnext/e_commerce/doctype/website_item/website_item.json @@ -29,11 +29,14 @@ "column_break_13", "slideshow", "thumbnail", + "stock_information_section", + "website_warehouse", + "column_break_24", + "on_backorder", "section_break_17", "short_description", "web_long_description", "column_break_27", - "website_warehouse", "website_specifications", "copy_from_item_group", "display_additional_information_section", @@ -326,13 +329,29 @@ "fieldtype": "Table", "label": "Recommended/Similar Items", "options": "Recommended Items" + }, + { + "fieldname": "stock_information_section", + "fieldtype": "Section Break", + "label": "Stock Information" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Indicate that Item is available on backorder and not usually pre-stocked", + "fieldname": "on_backorder", + "fieldtype": "Check", + "label": "On Backorder" } ], "has_web_view": 1, "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-07-12 21:00:04.065803", + "modified": "2021-09-02 13:08:41.942726", "modified_by": "Administrator", "module": "E-commerce", "name": "Website Item", diff --git a/erpnext/e_commerce/product_data_engine/query.py b/erpnext/e_commerce/product_data_engine/query.py index 0ac90906e5d..fefb5b33bfa 100644 --- a/erpnext/e_commerce/product_data_engine/query.py +++ b/erpnext/e_commerce/product_data_engine/query.py @@ -26,9 +26,11 @@ class ProductQuery: self.or_filters = [] self.filters = [["published", "=", 1]] - self.fields = ['web_item_name', 'name', 'item_name', 'item_code', 'website_image', - 'variant_of', 'has_variants', 'item_group', 'image', 'web_long_description', - 'short_description', 'route', 'website_warehouse', 'ranking'] + self.fields = [ + "web_item_name", "name", "item_name", "item_code", "website_image", + "variant_of", "has_variants", "item_group", "image", "web_long_description", + "short_description", "route", "website_warehouse", "ranking", "on_backorder" + ] def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None): """ @@ -239,6 +241,9 @@ class ProductQuery: warehouse = item.get("website_warehouse") is_stock_item = frappe.get_cached_value("Item", item.item_code, "is_stock_item") + if item.get("on_backorder"): + return + if not is_stock_item: if warehouse: # product bundle case diff --git a/erpnext/e_commerce/product_ui/grid.js b/erpnext/e_commerce/product_ui/grid.js index 51a13b0e0bb..9eb1d45d5f5 100644 --- a/erpnext/e_commerce/product_ui/grid.js +++ b/erpnext/e_commerce/product_ui/grid.js @@ -142,9 +142,22 @@ erpnext.ProductGrid = class { } get_stock_availability(item, settings) { - if (settings.show_stock_availability && !item.has_variants && !item.in_stock) { - return `${ __("Out of stock") }`; + if (settings.show_stock_availability && !item.has_variants) { + if (item.on_backorder) { + return ` + + ${ __("Available on backorder") } + + `; + } else if (!item.in_stock) { + return ` + + ${ __("Out of stock") } + + `; + } } + return ``; } @@ -168,7 +181,7 @@ erpnext.ProductGrid = class { - ${ __('Add to Cart') } + ${ settings.enable_checkout ? __('Add to Cart') : __('Add to Quote') } @@ -177,7 +190,7 @@ erpnext.ProductGrid = class { w-100 mt-4 go-to-cart-grid ${ item.in_cart ? '' : 'hidden' }" data-item-code="${ item.item_code }"> - ${ __('Go to Cart') } + ${ settings.enable_checkout ? __('Go to Cart') : __('Go to Quote') } `; diff --git a/erpnext/e_commerce/product_ui/list.js b/erpnext/e_commerce/product_ui/list.js index 7056a1af8c3..691cd4d9de1 100644 --- a/erpnext/e_commerce/product_ui/list.js +++ b/erpnext/e_commerce/product_ui/list.js @@ -125,11 +125,20 @@ erpnext.ProductList = class { } get_stock_availability(item, settings) { - if (settings.show_stock_availability && !item.has_variants && !item.in_stock) { - return ` -
- ${ __("Out of stock") } - `; + if (settings.show_stock_availability && !item.has_variants) { + if (item.on_backorder) { + return ` +
+ + ${ __("Available on backorder") } + + `; + } else if (!item.in_stock) { + return ` +
+ ${ __("Out of stock") } + `; + } } return ``; } @@ -169,7 +178,7 @@ erpnext.ProductList = class { - ${ __('Add to Cart') } + ${ settings.enable_checkout ? __('Add to Cart') : __('Add to Quote') }
@@ -183,7 +192,7 @@ erpnext.ProductList = class { ${ item.in_cart ? '' : 'hidden' }" data-item-code="${ item.item_code }" style="padding: 0.25rem 1rem; min-width: 135px;"> - ${ __('Go to Cart') } + ${ settings.enable_checkout ? __('Go to Cart') : __('Go to Quote') }
`; diff --git a/erpnext/e_commerce/redisearch.py b/erpnext/e_commerce/redisearch.py index f66d2ef0e85..9dac40fea2e 100644 --- a/erpnext/e_commerce/redisearch.py +++ b/erpnext/e_commerce/redisearch.py @@ -10,15 +10,17 @@ WEBSITE_ITEM_KEY_PREFIX = 'website_item:' WEBSITE_ITEM_NAME_AUTOCOMPLETE = 'website_items_name_dict' WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = 'website_items_category_dict' -ALLOWED_INDEXABLE_FIELDS_SET = { - 'web_item_name', - 'item_code', - 'item_name', - 'item_group', - 'brand', - 'description', - 'web_long_description' -} +def get_indexable_web_fields(): + "Return valid fields from Website Item that can be searched for." + web_item_meta = frappe.get_meta("Website Item", cached=True) + valid_fields = filter( + lambda df: df.fieldtype in ("Link", "Table MultiSelect", "Data", "Small Text", "Text Editor"), + web_item_meta.fields) + + return [df.fieldname for df in valid_fields] + +ALLOWED_INDEXABLE_FIELDS_SET = get_indexable_web_fields() + def is_search_module_loaded(): cache = frappe.cache() @@ -30,8 +32,8 @@ def is_search_module_loaded(): return "search" in parsed_output -# Decorator for checking wether Redisearch is there or not def if_redisearch_loaded(function): + "Decorator to check if Redisearch is loaded." def wrapper(*args, **kwargs): if is_search_module_loaded(): func = function(*args, **kwargs) @@ -45,7 +47,8 @@ def make_key(key): @if_redisearch_loaded def create_website_items_index(): - '''Creates Index Definition''' + "Creates Index Definition." + # CREATE index client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache()) @@ -197,7 +200,7 @@ def get_fields_indexed(): ) fields_to_index = fields_to_index.split(',') if fields_to_index else [] - mandatory_fields = ['name', 'web_item_name', 'route', 'thumbnail'] + mandatory_fields = ['name', 'web_item_name', 'route', 'thumbnail', 'ranking'] fields_to_index = fields_to_index + mandatory_fields return fields_to_index diff --git a/erpnext/e_commerce/shopping_cart/product_info.py b/erpnext/e_commerce/shopping_cart/product_info.py index 3ebf6d2f8d9..f54d3b3a1df 100644 --- a/erpnext/e_commerce/shopping_cart/product_info.py +++ b/erpnext/e_commerce/shopping_cart/product_info.py @@ -38,8 +38,13 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False): ) stock_status = None + if cart_settings.show_stock_availability: - stock_status = get_web_item_qty_in_stock(item_code, "website_warehouse") + on_backorder = frappe.get_cached_value("Website Item", {"item_code": item_code}, "on_backorder") + if on_backorder: + stock_status = frappe._dict({"on_backorder": True}) + else: + stock_status = get_web_item_qty_in_stock(item_code, "website_warehouse") product_info = { "price": price, @@ -49,9 +54,12 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False): } if stock_status: - product_info["stock_qty"] = stock_status.stock_qty - product_info["in_stock"] = stock_status.in_stock if stock_status.is_stock_item else get_non_stock_item_status(item_code, "website_warehouse") - product_info["show_stock_qty"] = show_quantity_in_website() + if stock_status.on_backorder: + product_info["on_backorder"] = True + else: + product_info["stock_qty"] = stock_status.stock_qty + product_info["in_stock"] = stock_status.in_stock if stock_status.is_stock_item else get_non_stock_item_status(item_code, "website_warehouse") + product_info["show_stock_qty"] = show_quantity_in_website() if product_info["price"]: if frappe.session.user != "Guest": diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 4697ab78dbb..e07bcbd28ec 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -1354,8 +1354,12 @@ body.product-page { font-weight: 500; } +.has-stock { + font-weight: 400 !important; +} + .out-of-stock { - font-weight: 500; + font-weight: 400; font-size: 14px; line-height: 20px; color: #F47A7A; diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html index 77ee9014c5f..8000a2446be 100644 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ b/erpnext/templates/generators/item/item_add_to_cart.html @@ -34,17 +34,21 @@ {% if cart_settings.show_stock_availability %}
- {% if product_info.in_stock == 0 %} - - {{ _('Out of stock') }} - + {% if product_info.get("on_backorder") %} + + {{ _('Available on backorder') }} + + {% elif product_info.in_stock == 0 %} + + {{ _('Out of stock') }} + {% elif product_info.in_stock == 1 %} - - {{ _('In stock') }} - {% if product_info.show_stock_qty and product_info.stock_qty %} - ({{ product_info.stock_qty[0][0] }}) - {% endif %} - + + {{ _('In stock') }} + {% if product_info.show_stock_qty and product_info.stock_qty %} + ({{ product_info.stock_qty[0][0] }}) + {% endif %} + {% endif %}
{% endif %} @@ -88,17 +92,21 @@
{% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %} - - + + {% endif %} diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py index bfee7939b53..c005f082424 100644 --- a/erpnext/templates/pages/product_search.py +++ b/erpnext/templates/pages/product_search.py @@ -52,7 +52,7 @@ def get_product_data(search=None, start=0, limit=12): search = "%" + cstr(search) + "%" # order by - query += """ ORDER BY ranking asc, modified desc limit %s, %s""" % (cint(start), cint(limit)) + query += """ ORDER BY ranking desc, modified desc limit %s, %s""" % (cint(start), cint(limit)) return frappe.db.sql(query, { "search": search @@ -102,6 +102,7 @@ def product_search(query, limit=10, fuzzy_search=True): results = client.search(q) search_results['results'] = list(map(convert_to_dict, results.docs)) + search_results['results'] = sorted(search_results['results'], key=lambda k: frappe.utils.cint(k['ranking']), reverse=True) return search_results