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 fc2a56567b..67b4a3d7f8 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 @@ -6,6 +6,11 @@ "engine": "InnoDB", "field_order": [ "products_per_page", + "filter_categories_section", + "enable_field_filters", + "filter_fields", + "enable_attribute_filters", + "filter_attributes", "display_settings_section", "hide_variants", "enable_variants", @@ -18,15 +23,6 @@ "show_apply_coupon_code_in_website", "show_contact_us_button", "show_attachments", - "guest_display_settings_section", - "hide_price_for_guest", - "redirect_on_action", - "add_ons_section", - "enable_wishlist", - "column_break_22", - "enable_reviews", - "column_break_23", - "enable_recommendations", "section_break_18", "company", "price_list", @@ -42,19 +38,22 @@ "save_quotations_as_draft", "payment_gateway_account", "payment_success_url", - "filter_categories_section", - "enable_field_filters", - "filter_fields", - "enable_attribute_filters", - "filter_attributes", - "shop_by_category_section", - "slideshow", + "add_ons_section", + "enable_wishlist", + "column_break_22", + "enable_reviews", + "column_break_23", + "enable_recommendations", "item_search_settings_section", "redisearch_warning", "search_index_fields", "show_categories_in_search_autocomplete", - "show_brand_line", - "is_redisearch_loaded" + "is_redisearch_loaded", + "shop_by_category_section", + "slideshow", + "guest_display_settings_section", + "hide_price_for_guest", + "redirect_on_action" ], "fields": [ { @@ -309,14 +308,6 @@ "label": "Show Categories in Search Autocomplete", "read_only_depends_on": "eval:!doc.is_redisearch_loaded" }, - { - "default": "0", - "description": "e.g. \"iPhone 12 by Apple\"", - "fieldname": "show_brand_line", - "fieldtype": "Check", - "label": "Show Brand Line", - "read_only_depends_on": "eval:!doc.is_redisearch_loaded" - }, { "default": "0", "fieldname": "is_redisearch_loaded", @@ -379,7 +370,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-08-24 21:10:45.669526", + "modified": "2021-08-31 12:23:06.187619", "modified_by": "Administrator", "module": "E-commerce", "name": "E Commerce Settings", diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py index cade54d106..ece60d4ba6 100644 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py @@ -3,7 +3,7 @@ # For license information, please see license.txt import frappe -from frappe.utils import comma_and +from frappe.utils import comma_and, flt from frappe import _, msgprint from frappe.model.document import Document from frappe.utils import unique @@ -20,11 +20,10 @@ class ECommerceSettings(Document): self.validate_field_filters() self.validate_attribute_filters() self.validate_checkout() - self.validate_brand_check() self.validate_search_index_fields() if self.enabled: - self.validate_exchange_rates_exist() + self.validate_price_list_exchange_rate() frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings") @@ -70,48 +69,33 @@ class ECommerceSettings(Document): self.search_index_fields = ','.join(fields) - def validate_brand_check(self): - if self.show_brand_line and not ("brand" in self.search_index_fields): - self.search_index_fields += ",brand" + def validate_price_list_exchange_rate(self): + "Check if exchange rate exists for Price List currency (to Company's currency)." + from erpnext.setup.utils import get_exchange_rate + + if not self.enabled or not self.company or not self.price_list: + return # this function is also called from hooks, check values again + + company_currency = frappe.get_cached_value("Company", self.company, "default_currency") + price_list_currency = frappe.db.get_value("Price List", self.price_list, "currency") - def validate_exchange_rates_exist(self): - """check if exchange rates exist for all Price List currencies (to company's currency)""" - company_currency = frappe.get_cached_value('Company', self.company, "default_currency") if not company_currency: - msgprint(_("Please specify currency in Company") + ": " + self.company, - raise_exception=ShoppingCartSetupError) + msg = f"Please specify currency in Company {self.company}" + frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError) - price_list_currency_map = frappe.db.get_values("Price List", - [self.price_list], "currency") + if not price_list_currency: + msg = f"Please specify currency in Price List {frappe.bold(self.price_list)}" + frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError) - price_list_currency_map = dict(price_list_currency_map) + if price_list_currency != company_currency: + from_currency, to_currency = price_list_currency, company_currency - # check if all price lists have a currency - for price_list, currency in price_list_currency_map.items(): - if not currency: - frappe.throw(_("Currency is required for Price List {0}").format(price_list)) + # Get exchange rate checks Currency Exchange Records too + exchange_rate = get_exchange_rate(from_currency, to_currency, args="for_selling") - expected_to_exist = [currency + "-" + company_currency - for currency in price_list_currency_map.values() - if currency != company_currency] - - # manqala 20/09/2016: set up selection parameters for query from tabCurrency Exchange - from_currency = [currency for currency in price_list_currency_map.values() if currency != company_currency] - to_currency = company_currency - # manqala end - - if expected_to_exist: - # manqala 20/09/2016: modify query so that it uses date in the selection from Currency Exchange. - # exchange rates defined with date less than the date on which this document is being saved will be selected - exists = frappe.db.sql_list("""select CONCAT(from_currency,'-',to_currency) from `tabCurrency Exchange` - where from_currency in (%s) and to_currency = "%s" and date <= curdate()""" % (", ".join(["%s"]*len(from_currency)), to_currency), tuple(from_currency)) - # manqala end - - missing = list(set(expected_to_exist).difference(exists)) - - if missing: - msgprint(_("Missing Currency Exchange Rates for {0}").format(comma_and(missing)), - raise_exception=ShoppingCartSetupError) + if not flt(exchange_rate): + msg = f"Missing Currency Exchange Rates for {from_currency}-{to_currency}" + frappe.throw(_(msg), title=_("Missing"), exc=ShoppingCartSetupError) def validate_tax_rule(self): if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"): @@ -136,7 +120,7 @@ class ECommerceSettings(Document): if not (new_fields == old_fields): create_website_items_index() -def validate_cart_settings(doc, method): +def validate_cart_settings(doc=None, method=None): frappe.get_doc("E Commerce Settings", "E Commerce Settings").run_method("validate") def get_shopping_cart_settings(): diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py index 59a1eb6191..5eb6184e57 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -187,12 +187,17 @@ class WebsiteItem(WebsiteGenerator): def get_context(self, context): context.show_search = True - context.search_link = '/search' + context.search_link = "/search" + context.body_class = "product-page" context.parents = get_parent_item_groups(self.item_group, from_item=True) # breadcumbs self.attributes = frappe.get_all("Item Variant Attribute", fields=["attribute", "attribute_value"], filters={"parent": self.item_code}) + + if self.slideshow: + context.update(get_slideshow(self)) + self.set_variant_context(context) self.set_attribute_context(context) self.set_disabled_attributes(context) @@ -254,11 +259,9 @@ class WebsiteItem(WebsiteGenerator): context[fieldname] = value - if self.slideshow: - if context.variant and context.variant.slideshow: - context.update(get_slideshow(context.variant)) - else: - context.update(get_slideshow(self)) + if self.slideshow and context.variant and context.variant.slideshow: + context.update(get_slideshow(context.variant)) + def set_attribute_context(self, context): if not self.has_variants: diff --git a/erpnext/e_commerce/product_data_engine/filters.py b/erpnext/e_commerce/product_data_engine/filters.py index 7ae87eba86..daf679f6a7 100644 --- a/erpnext/e_commerce/product_data_engine/filters.py +++ b/erpnext/e_commerce/product_data_engine/filters.py @@ -129,11 +129,15 @@ class ProductFiltersBuilder: min_discount, max_discount = discounts[0], discounts[1] # [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 + min_range = (min_range + 10) if min_range != min_range_absolute else min_range # 30 (upper limit of 25.89 in range of 10) + max_range = (max_range + 10) if max_range != max_range_absolute else max_range # 60 + for discount in range(min_range, (max_range + 1), 10): - label = f"{discount}% and above" + label = f"{discount}% and below" discount_filters.append([discount, label]) return discount_filters diff --git a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py index f6ff2d1a5c..577c11be6b 100644 --- a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py +++ b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py @@ -226,7 +226,7 @@ class TestProductDataEngine(unittest.TestCase): self.assertEqual(len(discount_filters[0]), 2) self.assertEqual(discount_filters[0][0], 10) - self.assertEqual(discount_filters[0][1], "10% and above") + self.assertEqual(discount_filters[0][1], "10% and below") def test_product_list_with_discount_filters(self): "Test if discount filters are applied correctly." @@ -261,7 +261,7 @@ class TestProductDataEngine(unittest.TestCase): ) items = result.get("items") - # check if only product with 10% and above discount are fetched in the right order + # check if only product with 10% and below 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") diff --git a/erpnext/e_commerce/product_ui/grid.js b/erpnext/e_commerce/product_ui/grid.js index d0b0e3b701..51a13b0e0b 100644 --- a/erpnext/e_commerce/product_ui/grid.js +++ b/erpnext/e_commerce/product_ui/grid.js @@ -143,7 +143,7 @@ erpnext.ProductGrid = class { get_stock_availability(item, settings) { if (settings.show_stock_availability && !item.has_variants && !item.in_stock) { - return `${ __("Out of stock") }`; + return `${ __("Out of stock") }`; } return ``; } @@ -161,7 +161,7 @@ erpnext.ProductGrid = class { return `
diff --git a/erpnext/e_commerce/product_ui/list.js b/erpnext/e_commerce/product_ui/list.js index e5791bb36d..7056a1af8c 100644 --- a/erpnext/e_commerce/product_ui/list.js +++ b/erpnext/e_commerce/product_ui/list.js @@ -24,7 +24,7 @@ erpnext.ProductList = class { let title = item.web_item_name || item.item_name || item.item_code || ""; title = title.length > 200 ? title.substr(0, 200) + "..." : title; - html += `
`; + html += `
`; html += me.get_image_html(item, title, me.settings); html += me.get_row_body_html(item, title, me.settings); html += `
`; @@ -86,7 +86,7 @@ erpnext.ProductList = class { `; if (settings.enabled) { - title_html += `
`; + title_html += `
`; title_html += this.get_primary_button(item, settings); title_html += `
`; } @@ -151,9 +151,7 @@ erpnext.ProductList = class { if (item.has_variants) { return ` -
+
${ __('Explore') }
@@ -161,10 +159,10 @@ erpnext.ProductList = class { } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) { return `
@@ -174,14 +172,14 @@ erpnext.ProductList = class { ${ __('Add to Cart') }
-
+
1
diff --git a/erpnext/e_commerce/product_ui/search.js b/erpnext/e_commerce/product_ui/search.js index ebe007624a..9bae1c10bc 100644 --- a/erpnext/e_commerce/product_ui/search.js +++ b/erpnext/e_commerce/product_ui/search.js @@ -38,39 +38,37 @@ erpnext.ProductSearch = class { let query = e.target.value; if (query.length == 0) { - me.populateResults([]); - me.populateCategoriesList([]); + me.populateResults(null); + me.populateCategoriesList(null); } if (query.length < 3 || !query.length) return; - // Populate recent search chips - me.setRecentSearches(query); - - // Fetch and populate product results frappe.call({ method: "erpnext.templates.pages.product_search.search", args: { query: query }, callback: (data) => { - me.populateResults(data); + let product_results = null, category_results = null; + + // Populate product results + product_results = data.message ? data.message.product_results : null; + me.populateResults(product_results); + + // Populate categories + if (me.category_container) { + category_results = data.message ? data.message.category_results : null; + me.populateCategoriesList(category_results); + } + + // Populate recent search chips only on successful queries + if (!$.isEmptyObject(product_results) || !$.isEmptyObject(category_results)) { + me.setRecentSearches(query); + } } }); - // Populate categories - if (me.category_container) { - frappe.call({ - method: "erpnext.templates.pages.product_search.get_category_suggestions", - args: { - query: query - }, - callback: (data) => { - me.populateCategoriesList(data); - } - }); - } - this.search_dropdown.removeClass("hidden"); }); } @@ -186,17 +184,16 @@ erpnext.ProductSearch = class { this.attachEventListenersToChips(); } - populateResults(data) { - if (data.length === 0 || data.message.results.length === 0) { + populateResults(product_results) { + if (!product_results || product_results.length === 0) { let empty_html = ``; this.products_container.html(empty_html); return; } let html = ""; - let search_results = data.message.results; - search_results.forEach((res) => { + product_results.forEach((res) => { let thumbnail = res.thumbnail || '/assets/erpnext/images/ui-states/cart-empty-state.png'; html += `