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 232a0612cce..8eeaf530150 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 @@ -27,6 +27,8 @@ "enable_wishlist", "column_break_22", "enable_reviews", + "column_break_23", + "enable_recommendations", "section_break_18", "company", "price_list", @@ -367,12 +369,22 @@ { "fieldname": "column_break_22", "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_23", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "enable_recommendations", + "fieldtype": "Check", + "label": "Enable Recommendations" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-07-07 21:32:17.363276", + "modified": "2021-07-13 16:30:14.715949", "modified_by": "Administrator", "module": "E-commerce", "name": "E Commerce Settings", diff --git a/erpnext/e_commerce/doctype/recommended_items/__init__.py b/erpnext/e_commerce/doctype/recommended_items/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/e_commerce/doctype/recommended_items/recommended_items.json b/erpnext/e_commerce/doctype/recommended_items/recommended_items.json new file mode 100644 index 00000000000..06ac3dc03b7 --- /dev/null +++ b/erpnext/e_commerce/doctype/recommended_items/recommended_items.json @@ -0,0 +1,87 @@ +{ + "actions": [], + "creation": "2021-07-12 20:52:12.503470", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "website_item", + "website_item_name", + "column_break_2", + "item_code", + "more_information_section", + "route", + "column_break_6", + "website_item_image", + "website_item_thumbnail" + ], + "fields": [ + { + "fieldname": "website_item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Website Item", + "options": "Website Item" + }, + { + "fetch_from": "website_item.web_item_name", + "fieldname": "website_item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Website Item Name", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "more_information_section", + "fieldtype": "Section Break", + "label": "More Information" + }, + { + "fetch_from": "website_item.route", + "fieldname": "route", + "fieldtype": "Small Text", + "label": "Route", + "read_only": 1 + }, + { + "fetch_from": "website_item.image", + "fieldname": "website_item_image", + "fieldtype": "Attach", + "label": "Website Item Image", + "read_only": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fetch_from": "website_item.thumbnail", + "fieldname": "website_item_thumbnail", + "fieldtype": "Data", + "label": "Website Item Thumbnail", + "read_only": 1 + }, + { + "fetch_from": "website_item.item_code", + "fieldname": "item_code", + "fieldtype": "Data", + "label": "Item Code" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-07-13 21:02:19.031652", + "modified_by": "Administrator", + "module": "E-commerce", + "name": "Recommended Items", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/recommended_items/recommended_items.py b/erpnext/e_commerce/doctype/recommended_items/recommended_items.py new file mode 100644 index 00000000000..9782abdec63 --- /dev/null +++ b/erpnext/e_commerce/doctype/recommended_items/recommended_items.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class RecommendedItems(Document): + pass diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json index a321584c784..c33cb51ea3c 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.json +++ b/erpnext/e_commerce/doctype/website_item/website_item.json @@ -39,6 +39,8 @@ "display_additional_information_section", "show_tabbed_section", "tabs", + "recommended_items_section", + "recommended_items", "offers_section", "offers", "section_break_6", @@ -312,13 +314,25 @@ "fieldname": "short_description", "fieldtype": "Small Text", "label": "Short Website Description" + }, + { + "collapsible": 1, + "fieldname": "recommended_items_section", + "fieldtype": "Section Break", + "label": "Recommended Items" + }, + { + "fieldname": "recommended_items", + "fieldtype": "Table", + "label": "Recommended/Similar Items", + "options": "Recommended Items" } ], "has_web_view": 1, "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-07-11 20:49:45.415421", + "modified": "2021-07-12 21:00:04.065803", "modified_by": "Administrator", "module": "E-commerce", "name": "Website Item", @@ -373,10 +387,10 @@ "write": 1 } ], - "search_fields": "item_code, item_name ,item_group", + "search_fields": "web_item_name, item_code, item_group", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "item_name", + "title_field": "web_item_name", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py index 915421f158d..3d6d3100c96 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -13,6 +13,8 @@ from frappe.website.doctype.website_slideshow.website_slideshow import get_slide from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for) from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews +from erpnext.e_commerce.shopping_cart.cart import _set_price_list +from erpnext.utilities.product import get_price # SEARCH from erpnext.e_commerce.website_item_indexing import ( @@ -199,6 +201,12 @@ class WebsiteItem(WebsiteGenerator): context.wished = True context.user_is_customer = check_if_user_is_customer() + + context.recommended_items = None + settings = context.shopping_cart.cart_settings + if settings.enable_recommendations: + context.recommended_items = self.get_recommended_items(settings) + return context def set_variant_context(self, context): @@ -380,6 +388,38 @@ class WebsiteItem(WebsiteGenerator): return tab_values + def get_recommended_items(self, settings): + items = frappe.db.sql(f""" + select + ri.website_item_thumbnail, ri.website_item_name, + ri.route, ri.item_code + from + `tabRecommended Items` ri, `tabWebsite Item` wi + where + ri.item_code = wi.item_code + and ri.parent = '{self.name}' + and wi.published = 1 + order by ri.idx + """, as_dict=1) + + if settings.show_price: + is_guest = frappe.session.user == "Guest" + # Show Price if logged in. + # If not logged in and price is hidden for guest, skip price fetch. + if is_guest and settings.hide_price_for_guest: + return items + + selling_price_list = _set_price_list(settings, None) + for item in items: + item.price_info = get_price( + item.item_code, + selling_price_list, + settings.default_customer_group, + settings.company + ) + + return items + def invalidate_cache_for_web_item(doc): """Invalidate Website Item Group cache and rebuild ItemVariantsCacheManager.""" from erpnext.stock.doctype.item.item import invalidate_item_variants_cache_for_website diff --git a/erpnext/e_commerce/website_item_indexing.py b/erpnext/e_commerce/website_item_indexing.py index 18dac93ad31..f66d2ef0e85 100644 --- a/erpnext/e_commerce/website_item_indexing.py +++ b/erpnext/e_commerce/website_item_indexing.py @@ -11,6 +11,7 @@ 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', diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js index be0c21f0ce1..4c134e2dff9 100644 --- a/erpnext/public/js/shopping_cart.js +++ b/erpnext/public/js/shopping_cart.js @@ -176,6 +176,16 @@ $.extend(shopping_cart, { const $btn = $(e.currentTarget); $btn.prop('disabled', true); + if (frappe.session.user==="Guest") { + if (localStorage) { + localStorage.setItem("last_visited", window.location.pathname); + } + frappe.call('erpnext.e_commerce.api.get_guest_redirect_on_action').then((res) => { + window.location.href = res.message || "/login"; + }); + return; + } + $btn.addClass('hidden'); $btn.parent().find('.go-to-cart').removeClass('hidden'); $btn.parent().find('.go-to-cart-grid').removeClass('hidden'); diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js index 4333587dc6e..3c8d842bea9 100644 --- a/erpnext/public/js/wishlist.js +++ b/erpnext/public/js/wishlist.js @@ -81,53 +81,55 @@ $.extend(wishlist, { // 'wish'('like') or 'unwish' item in product listing $('.page_content').on('click', '.like-action, .like-action-list', (e) => { const $btn = $(e.currentTarget); - const $wish_icon = $btn.find('.wish-icon'); - let me = this; - - if (frappe.session.user==="Guest") { - if (localStorage) { - localStorage.setItem("last_visited", window.location.pathname); - } - - this.redirect_guest(); - - return; - } - - let success_action = function() { - e_commerce.wishlist.set_wishlist_count(); - }; - - if ($wish_icon.hasClass('wished')) { - // un-wish item - $btn.removeClass("like-animate"); - $btn.addClass("like-action-wished"); - this.toggle_button_class($wish_icon, 'wished', 'not-wished'); - - let args = { item_code: $btn.data('item-code') }; - let failure_action = function() { - me.toggle_button_class($wish_icon, 'not-wished', 'wished'); - }; - this.add_remove_from_wishlist("remove", args, success_action, failure_action); - } else { - // wish item - $btn.addClass("like-animate"); - $btn.addClass("like-action-wished"); - this.toggle_button_class($wish_icon, 'not-wished', 'wished'); - - let args = { - item_code: $btn.data('item-code'), - price: $btn.data('price'), - formatted_price: $btn.data('formatted-price') - }; - let failure_action = function() { - me.toggle_button_class($wish_icon, 'wished', 'not-wished'); - }; - this.add_remove_from_wishlist("add", args, success_action, failure_action); - } + this.wishlist_action($btn); }); }, + wishlist_action(btn) { + const $wish_icon = btn.find('.wish-icon'); + let me = this; + + if (frappe.session.user==="Guest") { + if (localStorage) { + localStorage.setItem("last_visited", window.location.pathname); + } + this.redirect_guest(); + return; + } + + let success_action = function() { + e_commerce.wishlist.set_wishlist_count(); + }; + + if ($wish_icon.hasClass('wished')) { + // un-wish item + btn.removeClass("like-animate"); + btn.addClass("like-action-wished"); + this.toggle_button_class($wish_icon, 'wished', 'not-wished'); + + let args = { item_code: btn.data('item-code') }; + let failure_action = function() { + me.toggle_button_class($wish_icon, 'not-wished', 'wished'); + }; + this.add_remove_from_wishlist("remove", args, success_action, failure_action); + } else { + // wish item + btn.addClass("like-animate"); + btn.addClass("like-action-wished"); + this.toggle_button_class($wish_icon, 'not-wished', 'wished'); + + let args = { + item_code: btn.data('item-code'), + price: btn.data('price'), + formatted_price: btn.data('formatted-price') + }; + let failure_action = function() { + me.toggle_button_class($wish_icon, 'wished', 'not-wished'); + }; + this.add_remove_from_wishlist("add", args, success_action, failure_action); + } + }, + toggle_button_class(button, remove, add) { button.removeClass(remove); button.addClass(add); diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index f217d672a55..05a69aea916 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -2,6 +2,7 @@ :root { --green-info: #38A160; + --product-bg-color: white; } body.product-page { @@ -279,6 +280,7 @@ body.product-page { .product-container { @include card($padding: var(--padding-md)); + background-color: var(--product-bg-color) !important; min-height: 70vh; .product-details { @@ -289,6 +291,12 @@ body.product-page { } } + &.item-main { + .product-image { + width: 100%; + } + } + .expand { max-width: 100% !important; // expand in absence of slideshow } @@ -317,9 +325,10 @@ body.product-page { } .product-title { - font-size: 24px; + font-size: 16px; font-weight: 600; color: var(--text-color); + padding: 0 !important; } .product-description { @@ -375,7 +384,7 @@ body.product-page { .item-cart { .product-price { - font-size: 20px; + font-size: 22px; color: var(--text-color); font-weight: 600; @@ -388,12 +397,94 @@ body.product-page { .no-stock { font-size: var(--text-base); } + + .offers-heading { + font-size: 16px !important; + color: var(--text-color); + .tag-icon { + --icon-stroke: var(--gray-500); + } + } + + .w-30-40 { + width: 30%; + + @media (max-width: 992px) { + width: 40%; + } + } + } + + .tab-content { + font-size: 14px; + } +} + +// Item Recommendations +.recommended-item-section { + padding-right: 0; + + .recommendation-header { + font-size: 16px; + font-weight: 500 + } + + .recommendation-container { + padding: .5rem; + min-height: 0px; + + .r-item-image { + width: 40%; + + .no-image-r-item { + display: flex; justify-content: center; + background-color: var(--gray-200); + align-items: center; + color: var(--gray-400); + margin-top: .15rem; + border-radius: 6px; + height: 100%; + font-size: 24px; + } + } + + .r-item-info { + font-size: 14px; + padding-left: 8px; + padding-right: 0; + width: 60%; + + a { + color: var(--gray-800); + font-weight: 400; + } + + .item-price { + font-size: 15px; + font-weight: 600; + color: var(--text-color); + } + + .striked-item-price { + font-weight: 500; + color: var(--gray-500); + } + } } } .product-code { + padding: .5rem 0; color: var(--text-muted); font-size: 14px; + .product-item-group { + padding-right: .25rem; + border-right: solid 1px var(--text-muted); + } + + .product-item-code { + padding-left: .5rem; + } } .item-configurator-dialog { @@ -784,6 +875,12 @@ body.product-page { } } +.like-action-item-fp { + visibility: visible !important; + position: unset; + float: right; +} + .like-animate { animation: expand cubic-bezier(0.04, 0.4, 0.5, 0.95) 1.6s forwards 1; } @@ -909,16 +1006,63 @@ body.product-page { .item-website-specification { font-size: .875rem; + .product-title { + font-size: 18px; + } + + .table { + width: 70%; + } + + td { + border: none !important; + } + + .spec-label { + color: var(--gray-600); + } + + .spec-content { + color: var(--gray-800); + } +} + +.reviews-full-page { + padding: 1rem 2rem; } .ratings-reviews-section { border-top: 1px solid #E2E6E9; + padding: .5rem 1rem; } .reviews-header { font-size: 20px; font-weight: 600; color: var(--gray-800); + display: flex; + align-items: center; + padding: 0; +} + +.btn-write-review { + float: right; + padding: .5rem 1rem; + font-size: 14px; + font-weight: 400; + border: none !important; + box-shadow: none; + + color: var(--gray-900); + background-color: var(--gray-100); + + &:hover { + box-shadow: var(--btn-shadow); + } +} + +.rating-summary-section { + display: flex; } .rating-summary-title { @@ -926,9 +1070,17 @@ body.product-page { font-size: 18px; } +.rating-summary-numbers { + display: flex; + flex-direction: column; + align-items: center; + + border-right: solid 1px var(--gray-100); +} + .user-review-title { margin-top: 0.15rem; - font-size: 16px; + font-size: 15px; font-weight: 600; } @@ -942,6 +1094,12 @@ body.product-page { } } +.ratings-pill { + background-color: var(--gray-100); + padding: .5rem 1rem; + border-radius: 66px; +} + .review { max-width: 80%; line-height: 1.6; @@ -951,21 +1109,18 @@ body.product-page { .review-signature { display: flex; - font-size: 14px; + font-size: 13px; color: var(--gray-500); font-weight: 400; .reviewer { padding-right: 8px; - margin-right: 8px; - border-right: 1px solid var(--gray-400); + color: var(--gray-600); } } .rating-progress-bar-section { padding-bottom: 2rem; - border-bottom: 1px solid #E2E6E9; - margin-right: -10px; .rating-bar-title { margin-left: -15px; @@ -975,14 +1130,15 @@ body.product-page { margin-bottom: 4px; height: 7px; margin-top: 6px; + + .progress-bar-cosmetic { + background-color: var(--gray-600); + border-radius: var(--border-radius); + } } } .offer-container { - border: 1px solid var(--gray-300); - border-style: dashed; - border-radius: 4px; - padding: 6px; font-size: 14px; } @@ -1064,4 +1220,13 @@ body.product-page { .font-md { font-size: 14px !important; +} + +.in-green { + color: var(--green-info) !important; + font-weight: 500; +} + +.mt-minus-2 { + margin-top: -2rem; } \ No newline at end of file diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html index bfcb4f340eb..eb7eafd3fd0 100644 --- a/erpnext/templates/generators/item/item.html +++ b/erpnext/templates/generators/item/item.html @@ -1,4 +1,5 @@ {% extends "templates/web.html" %} +{% from "erpnext/templates/includes/macros.html" import recommended_item_row %} {% block title %} {{ title }} {% endblock %} @@ -9,7 +10,7 @@ {% endblock %} {% block page_content %} -