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 75f9c31ce78..805a530d8d3 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 @@ -20,6 +20,10 @@ "show_quantity_in_website", "show_apply_coupon_code_in_website", "allow_items_not_in_stock", + "add_ons_section", + "enable_wishlist", + "column_break_18", + "enable_reviews", "section_break_18", "company", "price_list", @@ -274,12 +278,34 @@ "fieldtype": "Link", "label": "Slideshow", "options": "Website Slideshow" + }, + { + "collapsible": 1, + "fieldname": "add_ons_section", + "fieldtype": "Section Break", + "label": "Add-ons" + }, + { + "default": "0", + "fieldname": "enable_wishlist", + "fieldtype": "Check", + "label": "Enable Wishlist" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "enable_reviews", + "fieldtype": "Check", + "label": "Enable Reviews and Ratings" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-01 20:24:56.548673", + "modified": "2021-03-23 17:15:01.956630", "modified_by": "Administrator", "module": "E-commerce", "name": "E Commerce Settings", diff --git a/erpnext/e_commerce/doctype/item_review/__init__.py b/erpnext/e_commerce/doctype/item_review/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/e_commerce/doctype/item_review/item_review.js b/erpnext/e_commerce/doctype/item_review/item_review.js new file mode 100644 index 00000000000..a57c370287b --- /dev/null +++ b/erpnext/e_commerce/doctype/item_review/item_review.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Item Review', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/e_commerce/doctype/item_review/item_review.json b/erpnext/e_commerce/doctype/item_review/item_review.json new file mode 100644 index 00000000000..918f4339356 --- /dev/null +++ b/erpnext/e_commerce/doctype/item_review/item_review.json @@ -0,0 +1,112 @@ +{ + "actions": [], + "beta": 1, + "creation": "2021-03-23 16:47:26.542226", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "website_item", + "user", + "customer", + "column_break_3", + "item", + "published_on", + "reviews_section", + "review_title", + "rating", + "comment" + ], + "fields": [ + { + "fieldname": "website_item", + "fieldtype": "Link", + "label": "Website Item", + "options": "Website Item" + }, + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "website_item.item_code", + "fieldname": "item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item", + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "reviews_section", + "fieldtype": "Section Break", + "label": "Reviews" + }, + { + "fieldname": "rating", + "fieldtype": "Rating", + "in_list_view": 1, + "label": "Rating" + }, + { + "fieldname": "comment", + "fieldtype": "Small Text", + "label": "Comment" + }, + { + "fieldname": "review_title", + "fieldtype": "Data", + "label": "Review Title" + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, + { + "fieldname": "published_on", + "fieldtype": "Data", + "label": "Published on" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-03-24 22:27:28.094535", + "modified_by": "Administrator", + "module": "E-commerce", + "name": "Item Review", + "owner": "Administrator", + "permissions": [ + { + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1 + }, + { + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Website Manager", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/item_review/item_review.py b/erpnext/e_commerce/doctype/item_review/item_review.py new file mode 100644 index 00000000000..bbb85b3d5d3 --- /dev/null +++ b/erpnext/e_commerce/doctype/item_review/item_review.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from datetime import datetime +import frappe +from frappe.model.document import Document + +from frappe.contacts.doctype.contact.contact import get_contact_name + +class ItemReview(Document): + pass + +@frappe.whitelist() +def add_item_review(web_item, title, rating, comment=None): + """ Add an Item Review by a user if non-existent. """ + if not frappe.db.exists("Item Review", {"user": frappe.session.user, "website_item": web_item}): + doc = frappe.get_doc({ + "doctype": "Item Review", + "user": frappe.session.user, + "customer": get_customer(), + "website_item": web_item, + "item": frappe.db.get_value("Website Item", web_item, "item_code"), + "review_title": title, + "rating": rating, + "comment": comment + }) + doc.published_on = datetime.today().strftime("%d %B %Y") + doc.insert() + +def get_customer(): + user = frappe.session.user + contact_name = get_contact_name(user) + customer = None + + if contact_name: + contact = frappe.get_doc('Contact', contact_name) + for link in contact.links: + if link.link_doctype == "Customer": + customer = link.link_name + break + + if customer: + return frappe.db.get_value("Customer", customer) + else: + frappe.throw("You are not verified to write a review yet. Please contact us for verification.") \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/item_review/test_item_review.py b/erpnext/e_commerce/doctype/item_review/test_item_review.py new file mode 100644 index 00000000000..5e6d24989e6 --- /dev/null +++ b/erpnext/e_commerce/doctype/item_review/test_item_review.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestItemReview(unittest.TestCase): + pass diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py index 7424bc9fe35..d8ebffcca20 100644 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -176,6 +176,7 @@ class WebsiteItem(WebsiteGenerator): self.set_metatags(context) self.set_shopping_cart_data(context) self.get_product_details_section(context) + self.get_reviews(context) context.wished = False if frappe.db.exists("Wishlist Items", {"item_code": self.item_code, "parent": frappe.session.user}): @@ -340,7 +341,7 @@ class WebsiteItem(WebsiteGenerator): def get_product_details_section(self, context): """ Get section with tabs or website specifications. """ context.show_tabs = self.show_tabbed_section - if self.show_tabbed_section and self.tabs: + if self.show_tabbed_section and (self.tabs or self.website_specifications): context.tabs = self.get_tabs() else: context.website_specifications = self.website_specifications @@ -361,6 +362,28 @@ class WebsiteItem(WebsiteGenerator): return tab_values + def get_reviews(self, context): + if context.shopping_cart.cart_settings.enable_reviews: + context.reviews = frappe.db.get_all("Item Review", filters={"item": self.item_code}, + fields=["*"], limit=4) + + rating_data = frappe.db.get_all("Item Review", filters={"item": self.item_code}, + fields=["avg(rating) as average, count(*) as total"])[0] + context.average_rating = rating_data.average + context.average_whole_rating = flt(context.average_rating, 0) + + # get % of reviews per rating + reviews_per_rating = [] + for i in range(1,6): + count = frappe.db.get_all("Item Review", filters={"item": self.item_code, "rating": i}, + fields=["count(*) as count"])[0].count + + percent = flt((count / rating_data.total or 1) * 100, 0) if count else 0 + reviews_per_rating.append(percent) + + context.reviews_per_rating = reviews_per_rating + context.total_reviews = rating_data.total + 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/doctype/wishlist/wishlist.json b/erpnext/e_commerce/doctype/wishlist/wishlist.json index 653c656fcc7..ae24207d5f0 100644 --- a/erpnext/e_commerce/doctype/wishlist/wishlist.json +++ b/erpnext/e_commerce/doctype/wishlist/wishlist.json @@ -34,7 +34,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-03-18 16:10:29.534522", + "modified": "2021-03-24 20:42:58.402031", "modified_by": "Administrator", "module": "E-commerce", "name": "Wishlist", @@ -55,7 +55,7 @@ "print": 1, "read": 1, "report": 1, - "role": "Stock Manager", + "role": "Website Manager", "share": 1 } ], diff --git a/erpnext/e_commerce/product_query.py b/erpnext/e_commerce/product_query.py index c37f8fb6b2b..6ffab562297 100644 --- a/erpnext/e_commerce/product_query.py +++ b/erpnext/e_commerce/product_query.py @@ -69,16 +69,19 @@ class ProductQuery: item.formatted_price = (product_info.get('price') or {}).get('formatted_price') item.price = product_info['price'].get('price_list_rate') - if self.settings.show_stock_availability and item.get("website_warehouse"): - stock_qty = frappe.utils.flt( - frappe.db.get_value("Bin", - { - "item_code": item.item_code, - "warehouse": item.get("website_warehouse") - }, - "actual_qty") - ) - item.in_stock = "green" if stock_qty else "red" + if self.settings.show_stock_availability: + if item.get("website_warehouse"): + stock_qty = frappe.utils.flt( + frappe.db.get_value("Bin", + { + "item_code": item.item_code, + "warehouse": item.get("website_warehouse") + }, + "actual_qty") + ) + item.in_stock = "green" if stock_qty else "red" + elif not frappe.db.get_value("Item", item.item_code, "is_stock_item"): + item.in_stock = "green" # non-stock item will always be available item.wished = False if frappe.db.exists("Wishlist Items", {"item_code": item.item_code, "parent": frappe.session.user}): diff --git a/erpnext/e_commerce/web_template/item_card_group/item_card_group.html b/erpnext/e_commerce/web_template/item_card_group/item_card_group.html index 889a2281684..33d7bccc23a 100644 --- a/erpnext/e_commerce/web_template/item_card_group/item_card_group.html +++ b/erpnext/e_commerce/web_template/item_card_group/item_card_group.html @@ -26,7 +26,7 @@ {%- set item = frappe.get_doc("Item", item) -%} {{ item_card( item, is_featured=values['card_' + index + '_featured'], - True, "Center" + is_full_width=True, align="Center" ) }} {%- endif -%} {%- endfor -%} diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 0279f2285c9..8d7f59de277 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -135,7 +135,7 @@ body.product-page { .item-card { padding: var(--padding-sm); - min-width: 250px; + min-width: 260px; } } @@ -702,3 +702,71 @@ body.product-page { .item-website-specification { font-size: .875rem; } + +.ratings-reviews-section { + border-top: 1px solid #E2E6E9; + display: flex; +} + +.reviews-header { + font-size: 20px; + font-weight: 600; + color: var(--gray-800); +} + +.rating-summary-title { + margin-top: 0.15rem; + font-size: 18px; +} + +.user-review-title { + margin-top: 0.15rem; + font-size: 16px; + font-weight: 600; +} + +.rating { + --star-fill: var(--gray-300); + .star-hover { + --star-fill: var(--yellow-100); + } + .star-click { + --star-fill: var(--yellow-300); + } +} + +.review { + max-width: 80%; + line-height: 1.6; + padding-bottom: 0.5rem; + border-bottom: 1px solid #E2E6E9; +} + +.review-signature { + display: flex; + font-size: 14px; + color: var(--gray-500); + font-weight: 400; + + .reviewer { + padding-right: 8px; + margin-right: 8px; + border-right: 1px solid var(--gray-400); + } +} + +.rating-progress-bar-section { + padding-bottom: 2rem; + border-bottom: 1px solid #E2E6E9; + margin-right: -10px; + + .rating-bar-title { + margin-left: -15px; + } + + .rating-progress-bar { + margin-bottom: 4px; + height: 7px; + margin-top: 6px; + } +} diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index c9924e19e2b..a9e36799ad8 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -72,6 +72,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): def get_context(self, context): context.show_search=True context.page_length = cint(frappe.db.get_single_value('E Commerce Settings', 'products_per_page')) or 6 + context.e_commerce_settings = frappe.get_cached_doc('E Commerce Settings', 'E Commerce Settings') context.search_link = '/product_search' if frappe.form_dict: diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html index eebfb9247d8..5f027f7363b 100644 --- a/erpnext/templates/generators/item/item.html +++ b/erpnext/templates/generators/item/item.html @@ -37,6 +37,11 @@ {{ doc.website_content or '' }} + + + {% if shopping_cart.cart_settings.enable_reviews %} + {% include "templates/generators/item/item_reviews.html"%} + {% endif %} diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html index c7713c1219e..3af360f253f 100644 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ b/erpnext/templates/generators/item/item_add_to_cart.html @@ -57,34 +57,37 @@ {% endif %} - + {% if cart_settings.enable_wishlist %} + - {% set price = product_info.get("price") or {} %} - + + + + {{ _("Add to Wishlist") }} + + {% endif %} + {% if cart_settings.show_contact_us_button %} {% include "templates/generators/item/item_inquiry.html" %} {% endif %} diff --git a/erpnext/templates/generators/item/item_reviews.html b/erpnext/templates/generators/item/item_reviews.html new file mode 100644 index 00000000000..c271fdb808d --- /dev/null +++ b/erpnext/templates/generators/item/item_reviews.html @@ -0,0 +1,127 @@ +{% from "erpnext/templates/includes/macros.html" import ratings_with_title %} + +
+ + diff --git a/erpnext/templates/generators/item/item_specifications.html b/erpnext/templates/generators/item/item_specifications.html index 1dccff9f92f..f3957610e95 100644 --- a/erpnext/templates/generators/item/item_specifications.html +++ b/erpnext/templates/generators/item/item_specifications.html @@ -1,8 +1,9 @@ -{% if website_specifications -%} -