From 939b0dd67d8619ca3dc4ae60f3f7a95565dd3f45 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 10 Feb 2021 19:44:10 +0530 Subject: [PATCH] feat: E-commerce Refactor - Created "Website Item" to handle website related information - Publishing Item new flow - Created "E Commerce Settings" - Removed Products Settings --- .../__init__.py | 0 erpnext/e_commerce/doctype/__init__.py | 0 .../doctype/e_commerce_settings/__init__.py | 0 .../e_commerce_settings.js} | 4 +- .../e_commerce_settings.json | 104 +++++ .../e_commerce_settings.py} | 18 +- .../test_e_commerce_settings.py | 10 + .../doctype/website_item/__init__.py | 0 .../website_item/templates/website_item.html | 7 + .../templates/website_item_row.html | 4 + .../doctype/website_item/test_website_item.py | 10 + .../doctype/website_item/website_item.js | 8 + .../doctype/website_item/website_item.json | 279 +++++++++++++ .../doctype/website_item/website_item.py | 155 +++++++ .../doctype/website_item/website_item_list.js | 20 + erpnext/hooks.py | 2 +- erpnext/modules.txt | 1 + .../products_settings/products_settings.json | 389 ------------------ .../test_products_settings.py | 10 - .../test_product_configurator.py | 10 +- erpnext/portal/product_configurator/utils.py | 20 +- .../setup/doctype/item_group/item_group.py | 4 +- erpnext/shopping_cart/filters.py | 4 +- erpnext/shopping_cart/product_query.py | 4 +- erpnext/stock/doctype/item/item.js | 25 +- erpnext/stock/doctype/item/item.json | 19 +- erpnext/www/all-products/index.py | 10 +- 27 files changed, 672 insertions(+), 445 deletions(-) rename erpnext/{portal/doctype/products_settings => e_commerce}/__init__.py (100%) create mode 100644 erpnext/e_commerce/doctype/__init__.py create mode 100644 erpnext/e_commerce/doctype/e_commerce_settings/__init__.py rename erpnext/{portal/doctype/products_settings/products_settings.js => e_commerce/doctype/e_commerce_settings/e_commerce_settings.js} (84%) create mode 100644 erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json rename erpnext/{portal/doctype/products_settings/products_settings.py => e_commerce/doctype/e_commerce_settings/e_commerce_settings.py} (66%) create mode 100644 erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py create mode 100644 erpnext/e_commerce/doctype/website_item/__init__.py create mode 100644 erpnext/e_commerce/doctype/website_item/templates/website_item.html create mode 100644 erpnext/e_commerce/doctype/website_item/templates/website_item_row.html create mode 100644 erpnext/e_commerce/doctype/website_item/test_website_item.py create mode 100644 erpnext/e_commerce/doctype/website_item/website_item.js create mode 100644 erpnext/e_commerce/doctype/website_item/website_item.json create mode 100644 erpnext/e_commerce/doctype/website_item/website_item.py create mode 100644 erpnext/e_commerce/doctype/website_item/website_item_list.js delete mode 100644 erpnext/portal/doctype/products_settings/products_settings.json delete mode 100644 erpnext/portal/doctype/products_settings/test_products_settings.py diff --git a/erpnext/portal/doctype/products_settings/__init__.py b/erpnext/e_commerce/__init__.py similarity index 100% rename from erpnext/portal/doctype/products_settings/__init__.py rename to erpnext/e_commerce/__init__.py diff --git a/erpnext/e_commerce/doctype/__init__.py b/erpnext/e_commerce/doctype/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/__init__.py b/erpnext/e_commerce/doctype/e_commerce_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/portal/doctype/products_settings/products_settings.js b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js similarity index 84% rename from erpnext/portal/doctype/products_settings/products_settings.js rename to erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js index 2f8b037164..d970f041be 100644 --- a/erpnext/portal/doctype/products_settings/products_settings.js +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js @@ -1,7 +1,7 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('Products Settings', { +frappe.ui.form.on('E Commerce Settings', { refresh: function(frm) { frappe.model.with_doctype('Item', () => { const item_meta = frappe.get_meta('Item'); 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 new file mode 100644 index 0000000000..1a45adf6cd --- /dev/null +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json @@ -0,0 +1,104 @@ +{ + "actions": [], + "creation": "2021-02-10 17:13:39.139103", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "home_page_is_products", + "show_availability_status", + "hide_variants", + "column_break_4", + "products_per_page", + "filter_categories_section", + "enable_field_filters", + "filter_fields", + "enable_attribute_filters", + "filter_attributes" + ], + "fields": [ + { + "default": "0", + "description": "If checked, the Home page will be the default Item Group for the website", + "fieldname": "home_page_is_products", + "fieldtype": "Check", + "label": "Home Page is Products" + }, + { + "default": "0", + "fieldname": "show_availability_status", + "fieldtype": "Check", + "label": "Show Availability Status" + }, + { + "default": "6", + "fieldname": "products_per_page", + "fieldtype": "Int", + "label": "Products per Page" + }, + { + "fieldname": "filter_categories_section", + "fieldtype": "Section Break", + "label": "Filters" + }, + { + "default": "0", + "fieldname": "hide_variants", + "fieldtype": "Check", + "label": "Hide Variants" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "enable_field_filters", + "fieldtype": "Check", + "label": "Enable Field Filters" + }, + { + "default": "0", + "fieldname": "enable_attribute_filters", + "fieldtype": "Check", + "label": "Enable Attribute Filters" + }, + { + "depends_on": "enable_field_filters", + "fieldname": "filter_fields", + "fieldtype": "Table", + "label": "Item Fields", + "options": "Website Filter Field" + }, + { + "depends_on": "enable_attribute_filters", + "fieldname": "filter_attributes", + "fieldtype": "Table", + "label": "Attributes", + "options": "Website Attribute" + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2021-02-10 19:22:47.154104", + "modified_by": "Administrator", + "module": "E-commerce", + "name": "E Commerce Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/portal/doctype/products_settings/products_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py similarity index 66% rename from erpnext/portal/doctype/products_settings/products_settings.py rename to erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py index d4f09b9c8c..90596d6b6f 100644 --- a/erpnext/portal/doctype/products_settings/products_settings.py +++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt from __future__ import unicode_literals @@ -10,7 +10,7 @@ from frappe.model.document import Document from frappe.utils import cint -class ProductsSettings(Document): +class ECommerceSettings(Document): def validate(self): if self.home_page_is_products: frappe.db.set_value("Website Settings", None, "home_page", "products") @@ -19,17 +19,17 @@ class ProductsSettings(Document): self.validate_field_filters() self.validate_attribute_filters() - frappe.clear_document_cache("Product Settings", "Product Settings") + frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings") def validate_field_filters(self): if not (self.enable_field_filters and self.filter_fields): return - item_meta = frappe.get_meta('Item') - valid_fields = [df.fieldname for df in item_meta.fields if df.fieldtype in ['Link', 'Table MultiSelect']] + item_meta = frappe.get_meta("Item") + valid_fields = [df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]] for f in self.filter_fields: if f.fieldname not in valid_fields: - frappe.throw(_('Filter Fields Row #{0}: Fieldname {1} must be of type "Link" or "Table MultiSelect"').format(f.idx, f.fieldname)) + frappe.throw(_("Filter Fields Row #{0}: Fieldname {1} must be of type 'Link' or 'Table MultiSelect'").format(f.idx, f.fieldname)) def validate_attribute_filters(self): if not (self.enable_attribute_filters and self.filter_attributes): return @@ -39,7 +39,7 @@ class ProductsSettings(Document): def home_page_is_products(doc, method): - '''Called on saving Website Settings''' - home_page_is_products = cint(frappe.db.get_single_value('Products Settings', 'home_page_is_products')) + """Called on saving Website Settings.""" + home_page_is_products = cint(frappe.db.get_single_value("E Commerce Settings", "home_page_is_products")) if home_page_is_products: - doc.home_page = 'products' + doc.home_page = "products" \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py new file mode 100644 index 0000000000..cf23266a29 --- /dev/null +++ b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.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 TestECommerceSettings(unittest.TestCase): + pass diff --git a/erpnext/e_commerce/doctype/website_item/__init__.py b/erpnext/e_commerce/doctype/website_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/e_commerce/doctype/website_item/templates/website_item.html b/erpnext/e_commerce/doctype/website_item/templates/website_item.html new file mode 100644 index 0000000000..db123090aa --- /dev/null +++ b/erpnext/e_commerce/doctype/website_item/templates/website_item.html @@ -0,0 +1,7 @@ +{% extends "templates/web.html" %} + +{% block page_content %} +

{{ title }}

+{% endblock %} + + \ No newline at end of file diff --git a/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html b/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html new file mode 100644 index 0000000000..d7014b453a --- /dev/null +++ b/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html @@ -0,0 +1,4 @@ +
+ {{ doc.title or doc.name }} +
+ diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py new file mode 100644 index 0000000000..e4386a30db --- /dev/null +++ b/erpnext/e_commerce/doctype/website_item/test_website_item.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 TestWebsiteItem(unittest.TestCase): + pass diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js new file mode 100644 index 0000000000..ecea74bc78 --- /dev/null +++ b/erpnext/e_commerce/doctype/website_item/website_item.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('Website Item', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json new file mode 100644 index 0000000000..85a83e6d6e --- /dev/null +++ b/erpnext/e_commerce/doctype/website_item/website_item.json @@ -0,0 +1,279 @@ +{ + "actions": [], + "allow_guest_to_view": 1, + "allow_import": 1, + "autoname": "field:item_code", + "creation": "2021-02-09 21:06:14.441698", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "web_item_name", + "route", + "has_variants", + "variant_of", + "published", + "column_break_3", + "item_code", + "item_name", + "item_group", + "stock_uom", + "brand", + "image", + "display_section", + "website_image", + "website_image_alt", + "column_break_13", + "slideshow", + "thumbnail", + "section_break_17", + "website_warehouse", + "website_specifications", + "copy_from_item_group", + "column_break_27", + "web_long_description", + "section_break_6", + "ranking", + "set_meta_tags", + "column_break_22", + "website_item_groups", + "advanced_display_section", + "website_content" + ], + "fields": [ + { + "description": "Website display name", + "fetch_from": "item_code.item_name", + "fetch_if_empty": 1, + "fieldname": "web_item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Website Item Name", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "label": "Item Code", + "options": "Item", + "reqd": 1, + "unique": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "label": "Search and SEO" + }, + { + "fieldname": "route", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Route", + "no_copy": 1 + }, + { + "description": "Items with higher ranking will be shown higher", + "fieldname": "ranking", + "fieldtype": "Int", + "label": "Ranking" + }, + { + "description": "Show a slideshow at the top of the page", + "fieldname": "slideshow", + "fieldtype": "Link", + "label": "Slideshow", + "options": "Website Slideshow" + }, + { + "description": "Item Image (if not slideshow)", + "fieldname": "website_image", + "fieldtype": "Attach", + "label": "Website Image" + }, + { + "description": "Image Alternative Text", + "fieldname": "website_image_alt", + "fieldtype": "Data", + "label": "Image Description" + }, + { + "fieldname": "thumbnail", + "fieldtype": "Data", + "label": "Thumbnail", + "read_only": 1 + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "description": "Show Stock availability based on this warehouse.", + "fieldname": "website_warehouse", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Website Warehouse", + "options": "Warehouse" + }, + { + "description": "List this Item in multiple groups on the website.", + "fieldname": "website_item_groups", + "fieldtype": "Table", + "label": "Website Item Groups", + "options": "Website Item Group" + }, + { + "fieldname": "set_meta_tags", + "fieldtype": "Button", + "label": "Set Meta Tags" + }, + { + "fieldname": "section_break_17", + "fieldtype": "Section Break", + "label": "Display Information" + }, + { + "fieldname": "copy_from_item_group", + "fieldtype": "Button", + "label": "Copy From Item Group" + }, + { + "fieldname": "website_specifications", + "fieldtype": "Table", + "label": "Website Specifications", + "options": "Item Website Specification" + }, + { + "fieldname": "web_long_description", + "fieldtype": "Text Editor", + "label": "Website Description" + }, + { + "description": "You can use any valid Bootstrap 4 markup in this field. It will be shown on your Item Page.", + "fieldname": "website_content", + "fieldtype": "HTML Editor", + "label": "Website Content" + }, + { + "fetch_from": "item_code.item_group", + "fieldname": "item_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Group", + "options": "Item Group", + "read_only": 1 + }, + { + "fetch_from": "item_code.image", + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "in_preview": 1, + "label": "Image", + "print_hide": 1 + }, + { + "default": "1", + "fieldname": "published", + "fieldtype": "Check", + "label": "Published" + }, + { + "default": "0", + "depends_on": "has_variants", + "fetch_from": "item_code.has_variants", + "fieldname": "has_variants", + "fieldtype": "Check", + "in_standard_filter": 1, + "label": "Has Variants", + "no_copy": 1, + "read_only": 1 + }, + { + "depends_on": "variant_of", + "fetch_from": "item_code.variant_of", + "fieldname": "variant_of", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Variant Of", + "options": "Item", + "read_only": 1, + "search_index": 1, + "set_only_once": 1 + }, + { + "fetch_from": "item_code.stock_uom", + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + }, + { + "depends_on": "brand", + "fetch_from": "item_code.brand", + "fieldname": "brand", + "fieldtype": "Link", + "label": "Brand", + "options": "Brand" + }, + { + "collapsible": 1, + "fieldname": "advanced_display_section", + "fieldtype": "Section Break", + "label": "Advanced Display" + }, + { + "fieldname": "display_section", + "fieldtype": "Section Break", + "label": "Display Images" + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + } + ], + "has_web_view": 1, + "image_field": "image", + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-02-10 14:22:41.628232", + "modified_by": "Administrator", + "module": "E-commerce", + "name": "Website Item", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "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 new file mode 100644 index 0000000000..1e0b12b4d6 --- /dev/null +++ b/erpnext/e_commerce/doctype/website_item/website_item.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import json +from frappe.website.doctype.website_slideshow.website_slideshow import \ + get_slideshow + +from frappe.website.render import clear_cache +from frappe.website.website_generator import WebsiteGenerator + +from frappe.utils import cstr, random_string + +class WebsiteItem(WebsiteGenerator): + website = frappe._dict( + page_title_field="web_item_name", + condition_field="published", + template="templates/generators/item/item.html", + no_cache=1 + ) + + def onload(self): + super(WebsiteItem, self).onload() + + def validate(self): + super(WebsiteItem, self).validate() + + if not self.item_code: + frappe.throw(_("Item Code is required"), title=_("Mandatory")) + + self.validate_website_image() + self.make_thumbnail() + self.publish_unpublish_desk_item(publish=True) + + def on_trash(self): + self.publish_unpublish_desk_item(publish=False) + + def publish_unpublish_desk_item(self, publish=True): + if frappe.db.get_value("Item", self.item_code, "published_in_website") and publish: + return # if already published don't publish again + frappe.db.set_value("Item", self.item_code, "published_in_website", publish) + + def make_route(self): + """Called from set_route in WebsiteGenerator.""" + if not self.route: + return cstr(frappe.db.get_value('Item Group', self.item_group, + 'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5)) + + def validate_website_image(self): + if frappe.flags.in_import: + return + + """Validate if the website image is a public file""" + auto_set_website_image = False + if not self.website_image and self.image: + auto_set_website_image = True + self.website_image = self.image + + if not self.website_image: + return + + # find if website image url exists as public + file_doc = frappe.get_all("File", filters={ + "file_url": self.website_image + }, fields=["name", "is_private"], order_by="is_private asc", limit_page_length=1) + + if file_doc: + file_doc = file_doc[0] + + if not file_doc: + if not auto_set_website_image: + frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found").format(self.website_image, self.name)) + + self.website_image = None + + elif file_doc.is_private: + if not auto_set_website_image: + frappe.msgprint(_("Website Image should be a public file or website URL")) + + self.website_image = None + + def make_thumbnail(self): + if frappe.flags.in_import: + return + + """Make a thumbnail of `website_image`""" + import requests.exceptions + + if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"): + self.thumbnail = None + + if self.website_image and not self.thumbnail: + file_doc = None + + try: + file_doc = frappe.get_doc("File", { + "file_url": self.website_image, + "attached_to_doctype": "Item", + "attached_to_name": self.name + }) + except frappe.DoesNotExistError: + pass + # cleanup + frappe.local.message_log.pop() + + except requests.exceptions.HTTPError: + frappe.msgprint(_("Warning: Invalid attachment {0}").format(self.website_image)) + self.website_image = None + + except requests.exceptions.SSLError: + frappe.msgprint( + _("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image)) + self.website_image = None + + # for CSV import + if self.website_image and not file_doc: + try: + file_doc = frappe.get_doc({ + "doctype": "File", + "file_url": self.website_image, + "attached_to_doctype": "Item", + "attached_to_name": self.name + }).save() + + except IOError: + self.website_image = None + + if file_doc: + if not file_doc.thumbnail_url: + file_doc.make_thumbnail() + + self.thumbnail = file_doc.thumbnail_url + +@frappe.whitelist() +def make_website_item(doc): + if not doc: + return + doc = json.loads(doc) + + if frappe.db.exists("Website Item", {"item_code": doc.get("item_code")}): + message = _("Website Item already exists against {0}").format(frappe.bold(doc.get("item_code"))) + frappe.throw(message, title=_("Already Published"), indicator="blue") + + website_item = frappe.new_doc("Website Item") + website_item.web_item_name = doc.get("item_name") + + fields_to_map = ["item_code", "item_name", "item_group", "stock_uom", "brand", "image", + "has_variants", "variant_of"] + for field in fields_to_map: + website_item.update({field: doc.get(field)}) + + website_item.save() + return [website_item.name, website_item.web_item_name] diff --git a/erpnext/e_commerce/doctype/website_item/website_item_list.js b/erpnext/e_commerce/doctype/website_item/website_item_list.js new file mode 100644 index 0000000000..21be9428eb --- /dev/null +++ b/erpnext/e_commerce/doctype/website_item/website_item_list.js @@ -0,0 +1,20 @@ +frappe.listview_settings['Website Item'] = { + add_fields: ["item_name", "web_item_name", "published", "image", "has_variants", "variant_of"], + filters: [["published", "=", "1"]], + + get_indicator: function(doc) { + if (doc.has_variants && doc.published) { + return [__("Template"), "orange", "has_variants,=,Yes|published,=,1"]; + } else if (doc.has_variants && !doc.published) { + return [__("Template"), "grey", "has_variants,=,Yes|published,=,0"]; + } else if (doc.variant_of && doc.published) { + return [__("Variant"), "blue", "published,=,1|variant_of,=," + doc.variant_of]; + } else if (doc.variant_of && !doc.published) { + return [__("Variant"), "grey", "published,=,0|variant_of,=," + doc.variant_of]; + } else if (doc.published) { + return [__("Published"), "green", "published,=,1"]; + } else { + return [__("Not Published"), "grey", "published,=,0"]; + } + } +}; \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 05f07f515c..684b13ab65 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -244,7 +244,7 @@ doc_events = { "on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings" }, "Website Settings": { - "validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products" + "validate": "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.home_page_is_products" }, "Tax Category": { "validate": "erpnext.regional.india.utils.validate_tax_category" diff --git a/erpnext/modules.txt b/erpnext/modules.txt index a9f94ce133..1c02db27ba 100644 --- a/erpnext/modules.txt +++ b/erpnext/modules.txt @@ -26,3 +26,4 @@ Communication Loan Management Payroll Telephony +E-commerce diff --git a/erpnext/portal/doctype/products_settings/products_settings.json b/erpnext/portal/doctype/products_settings/products_settings.json deleted file mode 100644 index 2cf8431497..0000000000 --- a/erpnext/portal/doctype/products_settings/products_settings.json +++ /dev/null @@ -1,389 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-04-22 09:11:55.272398", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "If checked, the Home page will be the default Item Group for the website", - "fieldname": "home_page_is_products", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Home Page is Products", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "show_availability_status", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Availability Status", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Product Page", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "6", - "fieldname": "products_per_page", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Products per Page", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "enable_field_filters", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Enable Field Filters", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "enable_field_filters", - "fieldname": "filter_fields", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Item Fields", - "length": 0, - "no_copy": 0, - "options": "Website Filter Field", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "enable_attribute_filters", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Enable Attribute Filters", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "enable_attribute_filters", - "fieldname": "filter_attributes", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Attributes", - "length": 0, - "no_copy": 0, - "options": "Website Attribute", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hide_variants", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hide Variants", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2019-03-07 19:18:31.822309", - "modified_by": "Administrator", - "module": "Portal", - "name": "Products Settings", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Website Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/portal/doctype/products_settings/test_products_settings.py b/erpnext/portal/doctype/products_settings/test_products_settings.py deleted file mode 100644 index 5495cc9d96..0000000000 --- a/erpnext/portal/doctype/products_settings/test_products_settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestProductsSettings(unittest.TestCase): - pass diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py index 5db74f2c40..27adcef91b 100644 --- a/erpnext/portal/product_configurator/test_product_configurator.py +++ b/erpnext/portal/product_configurator/test_product_configurator.py @@ -72,11 +72,11 @@ class TestProductConfigurator(unittest.TestCase): template_items = frappe.get_all('Item', {'show_in_website': 1}) variant_items = frappe.get_all('Item', {'show_variant_in_website': 1}) - products_settings = frappe.get_doc('Products Settings') - products_settings.enable_field_filters = 1 - products_settings.append('filter_fields', {'fieldname': 'item_group'}) - products_settings.append('filter_fields', {'fieldname': 'stock_uom'}) - products_settings.save() + e_commerce_settings = frappe.get_doc('E Commerce Settings') + e_commerce_settings.enable_field_filters = 1 + e_commerce_settings.append('filter_fields', {'fieldname': 'item_group'}) + e_commerce_settings.append('filter_fields', {'fieldname': 'stock_uom'}) + e_commerce_settings.save() html = get_html_for_route('all-products') diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index cf623c8d42..b0c5c3754b 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -7,8 +7,8 @@ from erpnext.shopping_cart.product_info import get_product_info_for_website def get_field_filter_data(): - product_settings = get_product_settings() - filter_fields = [row.fieldname for row in product_settings.filter_fields] + e_commerce_settings = get_e_commerce_settings() + filter_fields = [row.fieldname for row in e_commerce_settings.filter_fields] meta = frappe.get_meta('Item') fields = [df for df in meta.fields if df.fieldname in filter_fields] @@ -34,8 +34,8 @@ def get_field_filter_data(): def get_attribute_filter_data(): - product_settings = get_product_settings() - attributes = [row.attribute for row in product_settings.filter_attributes] + e_commerce_settings = get_e_commerce_settings() + attributes = [row.attribute for row in e_commerce_settings.filter_attributes] attribute_docs = [ frappe.get_doc('Item Attribute', attribute) for attribute in attributes ] @@ -306,9 +306,9 @@ def get_items_by_fields(field_filters): def get_items(filters=None, search=None): - start = frappe.form_dict.get('start', 0) - products_settings = get_product_settings() - page_length = products_settings.products_per_page + start = frappe.form_dict.start or 0 + e_commerce_settings = get_e_commerce_settings() + page_length = e_commerce_settings.products_per_page filters = filters or [] # convert to list of filters @@ -318,7 +318,7 @@ def get_items(filters=None, search=None): enabled_items_filter = get_conditions({ 'disabled': 0 }, 'and') show_in_website_condition = '' - if products_settings.hide_variants: + if e_commerce_settings.hide_variants: show_in_website_condition = get_conditions({'show_in_website': 1 }, 'and') else: show_in_website_condition = get_conditions([ @@ -440,7 +440,7 @@ def get_html_for_items(items): })) return html -def get_product_settings(): - doc = frappe.get_cached_doc('Products Settings') +def get_e_commerce_settings(): + doc = frappe.get_cached_doc('E Commerce Settings') doc.products_per_page = doc.products_per_page or 20 return doc diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index ab50a58c4f..2f03ee7183 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -74,7 +74,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): def get_context(self, context): context.show_search=True - context.page_length = cint(frappe.db.get_single_value('Products Settings', 'products_per_page')) or 6 + context.page_length = cint(frappe.db.get_single_value('E Commerce Settings', 'products_per_page')) or 6 context.search_link = '/product_search' if frappe.form_dict: @@ -239,7 +239,7 @@ def get_item_for_list_in_html(context): if (context.get("website_image") or "").startswith("files/"): context["website_image"] = "/" + quote(context["website_image"]) - context["show_availability_status"] = cint(frappe.db.get_single_value('Products Settings', + context["show_availability_status"] = cint(frappe.db.get_single_value('E Commerce Settings', 'show_availability_status')) products_template = 'templates/includes/products_as_list.html' diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py index 4787ae534c..ed32db2105 100644 --- a/erpnext/shopping_cart/filters.py +++ b/erpnext/shopping_cart/filters.py @@ -8,8 +8,8 @@ import frappe class ProductFiltersBuilder: def __init__(self, item_group=None): - if not item_group or item_group == "Products Settings": - self.doc = frappe.get_doc("Products Settings") + if not item_group or item_group == "E Commerce Settings": + self.doc = frappe.get_doc("E Commerce Settings") else: self.doc = frappe.get_doc("Item Group", item_group) diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py index 5cc0505aed..d105ab898b 100644 --- a/erpnext/shopping_cart/product_query.py +++ b/erpnext/shopping_cart/product_query.py @@ -15,13 +15,13 @@ class ProductQuery: filters (TYPE): Description or_filters (list): Description page_length (Int): Length of page for the query - settings (Document): Products Settings DocType + settings (Document): E Commerce Settings DocType filters (list) or_filters (list) """ def __init__(self): - self.settings = frappe.get_doc("Products Settings") + self.settings = frappe.get_doc("E Commerce Settings") self.cart_settings = frappe.get_doc("Shopping Cart Settings") self.page_length = self.settings.products_per_page or 20 self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants', diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 752a1fe732..fb2ccef48f 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -91,6 +91,29 @@ frappe.ui.form.on("Item", { erpnext.toggle_naming_series(); } + if(!frm.doc.published_in_website) { + frm.add_custom_button(__("Publish in Website"), function() { + frappe.call({ + method: "erpnext.e_commerce.doctype.website_item.website_item.make_website_item", + args: {doc: frm.doc}, + freeze: true, + freeze_message: __("Publishing Item ..."), + callback: function(result) { + frappe.msgprint({ + message: __("Website Item {0} has been created.", + [repl('%(item)s', { + item_encoded: encodeURIComponent(result.message[0]), + item: result.message[1] + })] + ), + title: __("Published"), + indicator: "green" + }); + } + }); + }, __('Actions')); + } + erpnext.item.edit_prices_button(frm); erpnext.item.toggle_attributes(frm); @@ -393,7 +416,7 @@ $.extend(erpnext.item, { edit_prices_button: function(frm) { frm.add_custom_button(__("Add / Edit Prices"), function() { frappe.set_route("List", "Item Price", {"item_code": frm.doc.name}); - }, __("View")); + }, __("Actions")); }, weight_to_validate: function(frm){ diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index db5caf9164..39cc9c75e2 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -117,6 +117,7 @@ "default_item_manufacturer", "default_manufacturer_part_no", "website_section", + "published_in_website", "show_in_website", "show_variant_in_website", "route", @@ -124,7 +125,6 @@ "slideshow", "website_image", "website_image_alt", - "thumbnail", "cb72", "website_warehouse", "website_item_groups", @@ -922,12 +922,6 @@ "fieldtype": "Attach", "label": "Website Image" }, - { - "fieldname": "thumbnail", - "fieldtype": "Data", - "label": "Thumbnail", - "read_only": 1 - }, { "fieldname": "cb72", "fieldtype": "Column Break" @@ -1069,6 +1063,15 @@ "fieldname": "website_image_alt", "fieldtype": "Data", "label": "Image Description" + }, + { + "default": "0", + "fieldname": "published_in_website", + "fieldtype": "Check", + "label": "Published in Website", + "no_copy": 1, + "read_only": 1, + "search_index": 1 } ], "has_web_view": 1, @@ -1078,7 +1081,7 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 1, - "modified": "2021-08-26 12:23:07.277077", + "modified": "2021-08-27 12:23:07.277077", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py index df5258b238..67d51ca161 100644 --- a/erpnext/www/all-products/index.py +++ b/erpnext/www/all-products/index.py @@ -1,6 +1,7 @@ import frappe -from erpnext.portal.product_configurator.utils import get_product_settings +from erpnext.portal.product_configurator.utils import (get_products_for_website, get_e_commerce_settings, + get_field_filter_data, get_attribute_filter_data) from erpnext.shopping_cart.filters import ProductFiltersBuilder from erpnext.shopping_cart.product_query import ProductQuery @@ -23,14 +24,15 @@ def get_context(context): # Add homepage as parent context.parents = [{"name": frappe._("Home"), "route":"/"}] - product_settings = get_product_settings() + e_commerce_settings = get_e_commerce_settings() filter_engine = ProductFiltersBuilder() context.field_filters = filter_engine.get_field_filters() context.attribute_filters = filter_engine.get_attribute_filters() - context.product_settings = product_settings + context.e_commerce_settings = e_commerce_settings context.body_class = "product-page" - context.page_length = product_settings.products_per_page or 20 + context.page_length = e_commerce_settings.products_per_page or 20 context.no_cache = 1 + print(context)