diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index ce15bcff8138..5f0b434c701f 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -1,13 +1,9 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - import json import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import flt, get_url, nowdate +from frappe.utils import flt, nowdate from frappe.utils.background_jobs import enqueue from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( @@ -363,33 +359,6 @@ def make_communication_entry(self): def get_payment_success_url(self): return self.payment_success_url - def on_payment_authorized(self, status=None): - if not status: - return - - shopping_cart_settings = frappe.get_doc("E Commerce Settings") - - if status in ["Authorized", "Completed"]: - redirect_to = None - self.set_as_paid() - - # if shopping cart enabled and in session - if ( - shopping_cart_settings.enabled - and hasattr(frappe.local, "session") - and frappe.local.session.user != "Guest" - ) and self.payment_channel != "Phone": - - success_url = shopping_cart_settings.payment_success_url - if success_url: - redirect_to = ({"Orders": "/orders", "Invoices": "/invoices", "My Account": "/me"}).get( - success_url, "/me" - ) - else: - redirect_to = get_url("/orders/{0}".format(self.reference_name)) - - return redirect_to - def create_subscription(self, payment_provider, gateway_controller, data): if payment_provider == "stripe": with payment_app_import_guard(): @@ -546,13 +515,12 @@ def get_existing_payment_request_amount(ref_dt, ref_dn): def get_gateway_details(args): # nosemgrep - """return gateway and payment account of default payment gateway""" - if args.get("payment_gateway_account"): - return get_payment_gateway_account(args.get("payment_gateway_account")) - - if args.order_type == "Shopping Cart": - payment_gateway_account = frappe.get_doc("E Commerce Settings").payment_gateway_account - return get_payment_gateway_account(payment_gateway_account) + """ + Return gateway and payment account of default payment gateway + """ + gateway_account = args.get("payment_gateway_account", {"is_default": 1}) + if gateway_account: + return get_payment_gateway_account(gateway_account) gateway_account = get_payment_gateway_account({"is_default": 1}) diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py index 75223c2ccca2..f6e5c56cceb9 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py @@ -22,7 +22,7 @@ def validate_interval_count(self): @frappe.whitelist() def get_plan_rate( - plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1 + plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1, party=None ): plan = frappe.get_doc("Subscription Plan", plan) if plan.price_determination == "Fixed Rate": @@ -40,6 +40,7 @@ def get_plan_rate( customer_group=customer_group, company=None, qty=quantity, + party=party, ) if not price: return 0 diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py index 87c5e6d588e1..ac0dd5123ace 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py @@ -8,7 +8,7 @@ from frappe import _ from frappe.contacts.doctype.address.address import get_default_address from frappe.model.document import Document -from frappe.utils import cint, cstr +from frappe.utils import cstr from frappe.utils.nestedset import get_root_of from erpnext.setup.doctype.customer_group.customer_group import get_parent_customer_groups @@ -34,7 +34,6 @@ def validate(self): self.validate_tax_template() self.validate_from_to_dates("from_date", "to_date") self.validate_filters() - self.validate_use_for_shopping_cart() def validate_tax_template(self): if self.tax_type == "Sales": @@ -106,21 +105,6 @@ def validate_filters(self): if tax_rule[0].priority == self.priority: frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule) - def validate_use_for_shopping_cart(self): - """If shopping cart is enabled and no tax rule exists for shopping cart, enable this one""" - if ( - not self.use_for_shopping_cart - and cint(frappe.db.get_single_value("E Commerce Settings", "enabled")) - and not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1, "name": ["!=", self.name]}) - ): - - self.use_for_shopping_cart = 1 - frappe.msgprint( - _( - "Enabling 'Use for Shopping Cart', as Shopping Cart is enabled and there should be at least one Tax Rule for Shopping Cart" - ) - ) - @frappe.whitelist() def get_party_details(party, party_type, args=None): diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index e68ee909d9f4..c8785a5a7226 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -9,6 +9,8 @@ from frappe import _ from frappe.utils import cstr, flt +from erpnext.utilities.product import get_item_codes_by_attributes + class ItemVariantExistsError(frappe.ValidationError): pass @@ -24,7 +26,8 @@ class ItemTemplateCannotHaveStock(frappe.ValidationError): @frappe.whitelist() def get_variant(template, args=None, variant=None, manufacturer=None, manufacturer_part_no=None): - """Validates Attributes and their Values, then looks for an exactly + """ + Validates Attributes and their Values, then looks for an exactly matching Item Variant :param item: Template Item @@ -34,13 +37,14 @@ def get_variant(template, args=None, variant=None, manufacturer=None, manufactur if item_template.variant_based_on == "Manufacturer" and manufacturer: return make_variant_based_on_manufacturer(item_template, manufacturer, manufacturer_part_no) - else: - if isinstance(args, str): - args = json.loads(args) - if not args: - frappe.throw(_("Please specify at least one attribute in the Attributes table")) - return find_variant(template, args, variant) + if isinstance(args, str): + args = json.loads(args) + + if not args: + frappe.throw(_("Please specify at least one attribute in the Attributes table")) + + return find_variant(template, args, variant) def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part_no): @@ -157,17 +161,6 @@ def get_attribute_values(item): def find_variant(template, args, variant_item_code=None): - conditions = [ - """(iv_attribute.attribute={0} and iv_attribute.attribute_value={1})""".format( - frappe.db.escape(key), frappe.db.escape(cstr(value)) - ) - for key, value in args.items() - ] - - conditions = " or ".join(conditions) - - from erpnext.e_commerce.variant_selector.utils import get_item_codes_by_attributes - possible_variants = [ i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code ] diff --git a/erpnext/e_commerce/__init__.py b/erpnext/e_commerce/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/api.py b/erpnext/e_commerce/api.py deleted file mode 100644 index bfada0faa7a9..000000000000 --- a/erpnext/e_commerce/api.py +++ /dev/null @@ -1,81 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import json - -import frappe -from frappe.utils import cint - -from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder -from erpnext.e_commerce.product_data_engine.query import ProductQuery -from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website - - -@frappe.whitelist(allow_guest=True) -def get_product_filter_data(query_args=None): - """ - Returns filtered products and discount filters. - :param query_args (dict): contains filters to get products list - - Query Args filters: - search (str): Search Term. - field_filters (dict): Keys include item_group, brand, etc. - attribute_filters(dict): Keys include Color, Size, etc. - start (int): Offset items by - item_group (str): Valid Item Group - from_filters (bool): Set as True to jump to page 1 - """ - if isinstance(query_args, str): - query_args = json.loads(query_args) - - query_args = frappe._dict(query_args) - if query_args: - search = query_args.get("search") - field_filters = query_args.get("field_filters", {}) - attribute_filters = query_args.get("attribute_filters", {}) - start = cint(query_args.start) if query_args.get("start") else 0 - item_group = query_args.get("item_group") - from_filters = query_args.get("from_filters") - else: - search, attribute_filters, item_group, from_filters = None, None, None, None - field_filters = {} - start = 0 - - # if new filter is checked, reset start to show filtered items from page 1 - if from_filters: - start = 0 - - sub_categories = [] - if item_group: - sub_categories = get_child_groups_for_website(item_group, immediate=True) - - engine = ProductQuery() - try: - result = engine.query( - attribute_filters, field_filters, search_term=search, start=start, item_group=item_group - ) - except Exception: - frappe.log_error("Product query with filter failed") - return {"exc": "Something went wrong!"} - - # discount filter data - filters = {} - discounts = result["discounts"] - - if discounts: - filter_engine = ProductFiltersBuilder() - filters["discount_filters"] = filter_engine.get_discount_filters(discounts) - - return { - "items": result["items"] or [], - "filters": filters, - "settings": engine.settings, - "sub_categories": sub_categories, - "items_count": result["items_count"], - } - - -@frappe.whitelist(allow_guest=True) -def get_guest_redirect_on_action(): - return frappe.db.get_single_value("E Commerce Settings", "redirect_on_action") diff --git a/erpnext/e_commerce/doctype/__init__.py b/erpnext/e_commerce/doctype/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/__init__.py b/erpnext/e_commerce/doctype/e_commerce_settings/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index c37fa2f6eae4..000000000000 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on("E Commerce Settings", { - onload: function(frm) { - if(frm.doc.__onload && frm.doc.__onload.quotation_series) { - frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series; - frm.refresh_field("quotation_series"); - } - - frm.set_query('payment_gateway_account', function() { - return { 'filters': { 'payment_channel': "Email" } }; - }); - }, - refresh: function(frm) { - if (frm.doc.enabled) { - frm.get_field('store_page_docs').$wrapper.removeClass('hide-control').html( - `
${__("Follow these steps to create a landing page for your store")}: - - docs/store-landing-page - -
` - ); - } - - frappe.model.with_doctype("Website Item", () => { - const web_item_meta = frappe.get_meta('Website Item'); - - 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 }) - ); - - frm.get_field("filter_fields").grid.update_docfield_property( - 'fieldname', 'options', valid_fields - ); - }); - }, - enabled: function(frm) { - if (frm.doc.enabled === 1) { - frm.set_value('enable_variants', 1); - } - else { - frm.set_value('company', ''); - frm.set_value('price_list', ''); - frm.set_value('default_customer_group', ''); - frm.set_value('quotation_series', ''); - } - }, - - enable_checkout: function(frm) { - if (frm.doc.enable_checkout) { - erpnext.utils.check_payments_app(); - } - } -}); 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 deleted file mode 100644 index e6f08f708a87..000000000000 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json +++ /dev/null @@ -1,395 +0,0 @@ -{ - "actions": [], - "creation": "2021-02-10 17:13:39.139103", - "doctype": "DocType", - "editable_grid": 1, - "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", - "show_price", - "column_break_9", - "show_stock_availability", - "show_quantity_in_website", - "allow_items_not_in_stock", - "column_break_13", - "show_apply_coupon_code_in_website", - "show_contact_us_button", - "show_attachments", - "section_break_18", - "company", - "price_list", - "enabled", - "store_page_docs", - "column_break_21", - "default_customer_group", - "quotation_series", - "checkout_settings_section", - "enable_checkout", - "show_price_in_quotation", - "column_break_27", - "save_quotations_as_draft", - "payment_gateway_account", - "payment_success_url", - "add_ons_section", - "enable_wishlist", - "column_break_22", - "enable_reviews", - "column_break_23", - "enable_recommendations", - "item_search_settings_section", - "redisearch_warning", - "search_index_fields", - "is_redisearch_enabled", - "is_redisearch_loaded", - "shop_by_category_section", - "slideshow", - "guest_display_settings_section", - "hide_price_for_guest", - "redirect_on_action" - ], - "fields": [ - { - "default": "6", - "fieldname": "products_per_page", - "fieldtype": "Int", - "label": "Products per Page" - }, - { - "collapsible": 1, - "fieldname": "filter_categories_section", - "fieldtype": "Section Break", - "label": "Filters and Categories" - }, - { - "default": "0", - "fieldname": "hide_variants", - "fieldtype": "Check", - "label": "Hide Variants" - }, - { - "default": "0", - "description": "The field filters will also work as categories in the Shop by Category page.", - "fieldname": "enable_field_filters", - "fieldtype": "Check", - "label": "Enable Field Filters (Categories)" - }, - { - "default": "0", - "fieldname": "enable_attribute_filters", - "fieldtype": "Check", - "label": "Enable Attribute Filters" - }, - { - "depends_on": "enable_field_filters", - "fieldname": "filter_fields", - "fieldtype": "Table", - "label": "Website Item Fields", - "options": "Website Filter Field" - }, - { - "depends_on": "enable_attribute_filters", - "fieldname": "filter_attributes", - "fieldtype": "Table", - "label": "Attributes", - "options": "Website Attribute" - }, - { - "default": "0", - "fieldname": "enabled", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Enable Shopping Cart" - }, - { - "depends_on": "doc.enabled", - "fieldname": "store_page_docs", - "fieldtype": "HTML" - }, - { - "fieldname": "display_settings_section", - "fieldtype": "Section Break", - "label": "Display Settings" - }, - { - "default": "0", - "fieldname": "show_attachments", - "fieldtype": "Check", - "label": "Show Public Attachments" - }, - { - "default": "0", - "fieldname": "show_price", - "fieldtype": "Check", - "label": "Show Price" - }, - { - "default": "0", - "fieldname": "show_stock_availability", - "fieldtype": "Check", - "label": "Show Stock Availability" - }, - { - "default": "0", - "fieldname": "enable_variants", - "fieldtype": "Check", - "label": "Enable Variant Selection" - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "show_contact_us_button", - "fieldtype": "Check", - "label": "Show Contact Us Button" - }, - { - "default": "0", - "depends_on": "show_stock_availability", - "fieldname": "show_quantity_in_website", - "fieldtype": "Check", - "label": "Show Stock Quantity" - }, - { - "default": "0", - "fieldname": "show_apply_coupon_code_in_website", - "fieldtype": "Check", - "label": "Show Apply Coupon Code" - }, - { - "default": "0", - "fieldname": "allow_items_not_in_stock", - "fieldtype": "Check", - "label": "Allow items not in stock to be added to cart" - }, - { - "fieldname": "section_break_18", - "fieldtype": "Section Break", - "label": "Shopping Cart" - }, - { - "depends_on": "enabled", - "fieldname": "company", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Company", - "mandatory_depends_on": "eval: doc.enabled === 1", - "options": "Company", - "remember_last_selected_value": 1 - }, - { - "depends_on": "enabled", - "description": "Prices will not be shown if Price List is not set", - "fieldname": "price_list", - "fieldtype": "Link", - "label": "Price List", - "mandatory_depends_on": "eval: doc.enabled === 1", - "options": "Price List" - }, - { - "fieldname": "column_break_21", - "fieldtype": "Column Break" - }, - { - "depends_on": "enabled", - "fieldname": "default_customer_group", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Default Customer Group", - "mandatory_depends_on": "eval: doc.enabled === 1", - "options": "Customer Group" - }, - { - "depends_on": "enabled", - "fieldname": "quotation_series", - "fieldtype": "Select", - "label": "Quotation Series", - "mandatory_depends_on": "eval: doc.enabled === 1" - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval:doc.enable_checkout", - "depends_on": "enabled", - "fieldname": "checkout_settings_section", - "fieldtype": "Section Break", - "label": "Checkout Settings" - }, - { - "default": "0", - "fieldname": "enable_checkout", - "fieldtype": "Check", - "label": "Enable Checkout" - }, - { - "default": "Orders", - "depends_on": "enable_checkout", - "description": "After payment completion redirect user to selected page.", - "fieldname": "payment_success_url", - "fieldtype": "Select", - "label": "Payment Success Url", - "mandatory_depends_on": "enable_checkout", - "options": "\nOrders\nInvoices\nMy Account" - }, - { - "fieldname": "column_break_27", - "fieldtype": "Column Break" - }, - { - "default": "0", - "depends_on": "eval: doc.enable_checkout == 0", - "fieldname": "save_quotations_as_draft", - "fieldtype": "Check", - "label": "Save Quotations as Draft" - }, - { - "depends_on": "enable_checkout", - "fieldname": "payment_gateway_account", - "fieldtype": "Link", - "label": "Payment Gateway Account", - "mandatory_depends_on": "enable_checkout", - "options": "Payment Gateway Account" - }, - { - "collapsible": 1, - "depends_on": "enable_field_filters", - "fieldname": "shop_by_category_section", - "fieldtype": "Section Break", - "label": "Shop by Category" - }, - { - "fieldname": "slideshow", - "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" - }, - { - "default": "0", - "fieldname": "enable_reviews", - "fieldtype": "Check", - "label": "Enable Reviews and Ratings" - }, - { - "fieldname": "search_index_fields", - "fieldtype": "Small Text", - "label": "Search Index Fields", - "mandatory_depends_on": "is_redisearch_enabled", - "read_only_depends_on": "eval:!doc.is_redisearch_loaded" - }, - { - "collapsible": 1, - "fieldname": "item_search_settings_section", - "fieldtype": "Section Break", - "label": "Item Search Settings" - }, - { - "default": "0", - "fieldname": "is_redisearch_loaded", - "fieldtype": "Check", - "hidden": 1, - "label": "Is Redisearch Loaded" - }, - { - "depends_on": "eval:!doc.is_redisearch_loaded", - "fieldname": "redisearch_warning", - "fieldtype": "HTML", - "label": "Redisearch Warning", - "options": "

Redisearch is not loaded. If you want to use the advanced product search feature, refer here.

" - }, - { - "default": "0", - "depends_on": "eval:doc.show_price", - "fieldname": "hide_price_for_guest", - "fieldtype": "Check", - "label": "Hide Price for Guest" - }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break" - }, - { - "collapsible": 1, - "fieldname": "guest_display_settings_section", - "fieldtype": "Section Break", - "label": "Guest Display Settings" - }, - { - "description": "Link to redirect Guest on actions that need login such as add to cart, wishlist, etc. E.g.: /login", - "fieldname": "redirect_on_action", - "fieldtype": "Data", - "label": "Redirect on Action" - }, - { - "fieldname": "column_break_22", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_23", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "enable_recommendations", - "fieldtype": "Check", - "label": "Enable Recommendations" - }, - { - "default": "0", - "depends_on": "eval: doc.enable_checkout == 0", - "fieldname": "show_price_in_quotation", - "fieldtype": "Check", - "label": "Show Price in Quotation" - }, - { - "default": "0", - "fieldname": "is_redisearch_enabled", - "fieldtype": "Check", - "label": "Enable Redisearch", - "read_only_depends_on": "eval:!doc.is_redisearch_loaded" - } - ], - "index_web_pages_for_search": 1, - "issingle": 1, - "links": [], - "modified": "2022-04-01 18:35:56.106756", - "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", - "states": [], - "track_changes": 1 -} \ No newline at end of file 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 deleted file mode 100644 index c27d29a62cd6..000000000000 --- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.utils import comma_and, flt, unique - -from erpnext.e_commerce.redisearch_utils import ( - create_website_items_index, - define_autocomplete_dictionary, - get_indexable_web_fields, - is_search_module_loaded, -) - - -class ShoppingCartSetupError(frappe.ValidationError): - pass - - -class ECommerceSettings(Document): - def onload(self): - self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series") - - # flag >> if redisearch is installed and loaded - self.is_redisearch_loaded = is_search_module_loaded() - - def validate(self): - self.validate_field_filters(self.filter_fields, self.enable_field_filters) - self.validate_attribute_filters() - self.validate_checkout() - self.validate_search_index_fields() - - if self.enabled: - self.validate_price_list_exchange_rate() - - frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings") - - self.is_redisearch_enabled_pre_save = frappe.db.get_single_value( - "E Commerce Settings", "is_redisearch_enabled" - ) - - def after_save(self): - self.create_redisearch_indexes() - - def create_redisearch_indexes(self): - # if redisearch is enabled (value changed) create indexes and dictionary - value_changed = self.is_redisearch_enabled != self.is_redisearch_enabled_pre_save - if self.is_redisearch_loaded and self.is_redisearch_enabled and value_changed: - define_autocomplete_dictionary() - create_website_items_index() - - @staticmethod - def validate_field_filters(filter_fields, enable_field_filters): - if not (enable_field_filters and filter_fields): - return - - web_item_meta = frappe.get_meta("Website Item") - valid_fields = [ - df.fieldname for df in web_item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"] - ] - - for row in filter_fields: - if row.fieldname not in valid_fields: - frappe.throw( - _( - "Filter Fields Row #{0}: Fieldname {1} must be of type 'Link' or 'Table MultiSelect'" - ).format(row.idx, frappe.bold(row.fieldname)) - ) - - def validate_attribute_filters(self): - if not (self.enable_attribute_filters and self.filter_attributes): - return - - # if attribute filters are enabled, hide_variants should be disabled - self.hide_variants = 0 - - def validate_checkout(self): - if self.enable_checkout and not self.payment_gateway_account: - self.enable_checkout = 0 - - def validate_search_index_fields(self): - if not self.search_index_fields: - return - - fields = self.search_index_fields.replace(" ", "") - fields = unique(fields.strip(",").split(",")) # Remove extra ',' and remove duplicates - - # All fields should be indexable - allowed_indexable_fields = get_indexable_web_fields() - - if not (set(fields).issubset(allowed_indexable_fields)): - invalid_fields = list(set(fields).difference(allowed_indexable_fields)) - num_invalid_fields = len(invalid_fields) - invalid_fields = comma_and(invalid_fields) - - if num_invalid_fields > 1: - frappe.throw( - _("{0} are not valid options for Search Index Field.").format(frappe.bold(invalid_fields)) - ) - else: - frappe.throw( - _("{0} is not a valid option for Search Index Field.").format(frappe.bold(invalid_fields)) - ) - - self.search_index_fields = ",".join(fields) - - 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") - - if not company_currency: - msg = f"Please specify currency in Company {self.company}" - frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError) - - 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) - - if price_list_currency != company_currency: - from_currency, to_currency = price_list_currency, company_currency - - # Get exchange rate checks Currency Exchange Records too - exchange_rate = get_exchange_rate(from_currency, to_currency, args="for_selling") - - 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"): - frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError) - - def get_tax_master(self, billing_territory): - tax_master = self.get_name_from_territory( - billing_territory, "sales_taxes_and_charges_masters", "sales_taxes_and_charges_master" - ) - return tax_master and tax_master[0] or None - - def get_shipping_rules(self, shipping_territory): - return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule") - - def on_change(self): - old_doc = self.get_doc_before_save() - - if old_doc: - old_fields = old_doc.search_index_fields - new_fields = self.search_index_fields - - # if search index fields get changed - if not (new_fields == old_fields): - create_website_items_index() - - -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(): - return frappe.get_cached_doc("E Commerce Settings") - - -@frappe.whitelist(allow_guest=True) -def is_cart_enabled(): - return get_shopping_cart_settings().enabled - - -def show_quantity_in_website(): - return get_shopping_cart_settings().show_quantity_in_website - - -def check_shopping_cart_enabled(): - if not get_shopping_cart_settings().enabled: - frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError) - - -def show_attachments(): - return get_shopping_cart_settings().show_attachments 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 deleted file mode 100644 index 662db4d7aee1..000000000000 --- a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -import unittest - -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - ShoppingCartSetupError, -) - - -class TestECommerceSettings(unittest.TestCase): - def tearDown(self): - frappe.db.rollback() - - def test_tax_rule_validation(self): - frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0") - frappe.db.commit() # nosemgrep - - cart_settings = frappe.get_doc("E Commerce Settings") - cart_settings.enabled = 1 - if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"): - self.assertRaises(ShoppingCartSetupError, cart_settings.validate_tax_rule) - - frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1") - - def test_invalid_filter_fields(self): - "Check if Item fields are blocked in E Commerce Settings filter fields." - from frappe.custom.doctype.custom_field.custom_field import create_custom_field - - setup_e_commerce_settings({"enable_field_filters": 1}) - - create_custom_field( - "Item", - dict(owner="Administrator", fieldname="test_data", label="Test", fieldtype="Data"), - ) - settings = frappe.get_doc("E Commerce Settings") - settings.append("filter_fields", {"fieldname": "test_data"}) - - self.assertRaises(frappe.ValidationError, settings.save) - - -def setup_e_commerce_settings(values_dict): - "Accepts a dict of values that updates E Commerce Settings." - if not values_dict: - return - - doc = frappe.get_doc("E Commerce Settings", "E Commerce Settings") - doc.update(values_dict) - doc.save() - - -test_dependencies = ["Tax Rule"] diff --git a/erpnext/e_commerce/doctype/item_review/__init__.py b/erpnext/e_commerce/doctype/item_review/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/doctype/item_review/item_review.js b/erpnext/e_commerce/doctype/item_review/item_review.js deleted file mode 100644 index a57c370287b4..000000000000 --- a/erpnext/e_commerce/doctype/item_review/item_review.js +++ /dev/null @@ -1,8 +0,0 @@ -// 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 deleted file mode 100644 index 57f719fc3c48..000000000000 --- a/erpnext/e_commerce/doctype/item_review/item_review.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "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", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "user", - "fieldtype": "Link", - "in_list_view": 1, - "label": "User", - "options": "User", - "read_only": 1 - }, - { - "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", - "read_only": 1 - }, - { - "fieldname": "comment", - "fieldtype": "Small Text", - "label": "Comment", - "read_only": 1 - }, - { - "fieldname": "review_title", - "fieldtype": "Data", - "label": "Review Title", - "read_only": 1 - }, - { - "fieldname": "customer", - "fieldtype": "Link", - "label": "Customer", - "options": "Customer", - "read_only": 1 - }, - { - "fieldname": "published_on", - "fieldtype": "Data", - "label": "Published on", - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-08-10 12:08:58.119691", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Item Review", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Website Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "report": 1, - "role": "Customer", - "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 deleted file mode 100644 index 3e540e38853f..000000000000 --- a/erpnext/e_commerce/doctype/item_review/item_review.py +++ /dev/null @@ -1,153 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from datetime import datetime - -import frappe -from frappe import _ -from frappe.contacts.doctype.contact.contact import get_contact_name -from frappe.model.document import Document -from frappe.utils import cint, flt - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) - - -class UnverifiedReviewer(frappe.ValidationError): - pass - - -class ItemReview(Document): - def after_insert(self): - # regenerate cache on review creation - reviews_dict = get_queried_reviews(self.website_item) - set_reviews_in_cache(self.website_item, reviews_dict) - - def after_delete(self): - # regenerate cache on review deletion - reviews_dict = get_queried_reviews(self.website_item) - set_reviews_in_cache(self.website_item, reviews_dict) - - -@frappe.whitelist() -def get_item_reviews(web_item, start=0, end=10, data=None): - "Get Website Item Review Data." - start, end = cint(start), cint(end) - settings = get_shopping_cart_settings() - - # Get cached reviews for first page (start=0) - # avoid cache when page is different - from_cache = not bool(start) - - if not data: - data = frappe._dict() - - if settings and settings.get("enable_reviews"): - reviews_cache = frappe.cache().hget("item_reviews", web_item) - if from_cache and reviews_cache: - data = reviews_cache - else: - data = get_queried_reviews(web_item, start, end, data) - if from_cache: - set_reviews_in_cache(web_item, data) - - return data - - -def get_queried_reviews(web_item, start=0, end=10, data=None): - """ - Query Website Item wise reviews and cache if needed. - Cache stores only first page of reviews i.e. 10 reviews maximum. - Returns: - dict: Containing reviews, average ratings, % of reviews per rating and total reviews. - """ - if not data: - data = frappe._dict() - - data.reviews = frappe.db.get_all( - "Item Review", - filters={"website_item": web_item}, - fields=["*"], - limit_start=start, - limit_page_length=end, - ) - - rating_data = frappe.db.get_all( - "Item Review", - filters={"website_item": web_item}, - fields=["avg(rating) as average, count(*) as total"], - )[0] - - data.average_rating = flt(rating_data.average, 1) - data.average_whole_rating = flt(data.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={"website_item": web_item, "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) - - data.reviews_per_rating = reviews_per_rating - data.total_reviews = rating_data.total - - return data - - -def set_reviews_in_cache(web_item, reviews_dict): - frappe.cache().hset("item_reviews", web_item, reviews_dict) - - -@frappe.whitelist() -def add_item_review(web_item, title, rating, comment=None): - """Add an Item Review by a user if non-existent.""" - if frappe.session.user == "Guest": - # guest user should not reach here ideally in the case they do via an API, throw error - frappe.throw(_("You are not verified to write a review yet."), exc=UnverifiedReviewer) - - 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(silent=False): - """ - silent: Return customer if exists else return nothing. Dont throw error. - """ - 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) - elif silent: - return None - else: - # should not reach here unless via an API - frappe.throw( - _("You are not a verified customer yet. Please contact us to proceed."), exc=UnverifiedReviewer - ) diff --git a/erpnext/e_commerce/doctype/item_review/test_item_review.py b/erpnext/e_commerce/doctype/item_review/test_item_review.py deleted file mode 100644 index 8a4befc800af..000000000000 --- a/erpnext/e_commerce/doctype/item_review/test_item_review.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -import unittest - -import frappe -from frappe.core.doctype.user_permission.test_user_permission import create_user - -from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import ( - setup_e_commerce_settings, -) -from erpnext.e_commerce.doctype.item_review.item_review import ( - UnverifiedReviewer, - add_item_review, - get_item_reviews, -) -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item -from erpnext.e_commerce.shopping_cart.cart import get_party -from erpnext.stock.doctype.item.test_item import make_item - - -class TestItemReview(unittest.TestCase): - def setUp(self): - item = make_item("Test Mobile Phone") - if not frappe.db.exists("Website Item", {"item_code": "Test Mobile Phone"}): - make_website_item(item, save=True) - - setup_e_commerce_settings({"enable_reviews": 1}) - frappe.local.shopping_cart_settings = None - - def tearDown(self): - frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete() - setup_e_commerce_settings({"enable_reviews": 0}) - - def test_add_and_get_item_reviews_from_customer(self): - "Add / Get Reviews from a User that is a valid customer (has added to cart or purchased in the past)" - # create user - web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"}) - test_user = create_user("test_reviewer@example.com", "Customer") - frappe.set_user(test_user.name) - - # create customer and contact against user - customer = get_party() - - # post review on "Test Mobile Phone" - try: - add_item_review(web_item, "Great Product", 3, "Would recommend this product") - review_name = frappe.db.get_value("Item Review", {"website_item": web_item}) - except Exception: - self.fail(f"Error while publishing review for {web_item}") - - review_data = get_item_reviews(web_item, 0, 10) - - self.assertEqual(len(review_data.reviews), 1) - self.assertEqual(review_data.average_rating, 3) - self.assertEqual(review_data.reviews_per_rating[2], 100) - - # tear down - frappe.set_user("Administrator") - frappe.delete_doc("Item Review", review_name) - customer.delete() - - def test_add_item_review_from_non_customer(self): - "Check if logged in user (who is not a customer yet) is blocked from posting reviews." - web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"}) - test_user = create_user("test_reviewer@example.com", "Customer") - frappe.set_user(test_user.name) - - with self.assertRaises(UnverifiedReviewer): - add_item_review(web_item, "Great Product", 3, "Would recommend this product") - - # tear down - frappe.set_user("Administrator") - - def test_add_item_reviews_from_guest_user(self): - "Check if Guest user is blocked from posting reviews." - web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"}) - frappe.set_user("Guest") - - with self.assertRaises(UnverifiedReviewer): - add_item_review(web_item, "Great Product", 3, "Would recommend this product") - - # tear down - frappe.set_user("Administrator") diff --git a/erpnext/e_commerce/doctype/recommended_items/__init__.py b/erpnext/e_commerce/doctype/recommended_items/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/doctype/recommended_items/recommended_items.json b/erpnext/e_commerce/doctype/recommended_items/recommended_items.json deleted file mode 100644 index 182153232315..000000000000 --- a/erpnext/e_commerce/doctype/recommended_items/recommended_items.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "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.website_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": "2022-06-28 16:44:24.718728", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Recommended Items", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "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 deleted file mode 100644 index 16b6e52047fc..000000000000 --- a/erpnext/e_commerce/doctype/recommended_items/recommended_items.py +++ /dev/null @@ -1,9 +0,0 @@ -# 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/__init__.py b/erpnext/e_commerce/doctype/website_item/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/doctype/website_item/templates/website_item.html b/erpnext/e_commerce/doctype/website_item/templates/website_item.html deleted file mode 100644 index db123090aaeb..000000000000 --- a/erpnext/e_commerce/doctype/website_item/templates/website_item.html +++ /dev/null @@ -1,7 +0,0 @@ -{% 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 deleted file mode 100644 index d7014b453ab9..000000000000 --- a/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html +++ /dev/null @@ -1,4 +0,0 @@ -
- {{ 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 deleted file mode 100644 index 2ba84c050070..000000000000 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ /dev/null @@ -1,564 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -import unittest - -import frappe - -from erpnext.controllers.item_variant import create_variant -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import ( - setup_e_commerce_settings, -) -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item -from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website -from erpnext.stock.doctype.item.item import DataValidationError -from erpnext.stock.doctype.item.test_item import make_item - -WEBITEM_DESK_TESTS = ("test_website_item_desk_item_sync", "test_publish_variant_and_template") -WEBITEM_PRICE_TESTS = ( - "test_website_item_price_for_logged_in_user", - "test_website_item_price_for_guest_user", -) - - -class TestWebsiteItem(unittest.TestCase): - @classmethod - def setUpClass(cls): - setup_e_commerce_settings( - { - "company": "_Test Company", - "enabled": 1, - "default_customer_group": "_Test Customer Group", - "price_list": "_Test Price List India", - } - ) - - @classmethod - def tearDownClass(cls): - frappe.db.rollback() - - def setUp(self): - if self._testMethodName in WEBITEM_DESK_TESTS: - make_item( - "Test Web Item", - { - "has_variant": 1, - "variant_based_on": "Item Attribute", - "attributes": [{"attribute": "Test Size"}], - }, - ) - elif self._testMethodName in WEBITEM_PRICE_TESTS: - create_user_and_customer_if_not_exists( - "test_contact_customer@example.com", "_Test Contact For _Test Customer" - ) - create_regular_web_item() - make_web_item_price(item_code="Test Mobile Phone") - - # Note: When testing web item pricing rule logged-in user pricing rule must differ from guest pricing rule or test will falsely pass. - # This is because make_web_pricing_rule creates a pricing rule "selling": 1, without specifying "applicable_for". Therefor, - # when testing for logged-in user the test will get the previous pricing rule because "selling" is still true. - # - # I've attempted to mitigate this by setting applicable_for=Customer, and customer=Guest however, this only results in PermissionError failing the test. - make_web_pricing_rule( - title="Test Pricing Rule for Test Mobile Phone", item_code="Test Mobile Phone", selling=1 - ) - make_web_pricing_rule( - title="Test Pricing Rule for Test Mobile Phone (Customer)", - item_code="Test Mobile Phone", - selling=1, - discount_percentage="25", - applicable_for="Customer", - customer="_Test Customer", - ) - - def test_index_creation(self): - "Check if index is getting created in db." - from erpnext.e_commerce.doctype.website_item.website_item import on_doctype_update - - on_doctype_update() - - indices = frappe.db.sql("show index from `tabWebsite Item`", as_dict=1) - expected_columns = {"route", "item_group", "brand"} - for index in indices: - expected_columns.discard(index.get("Column_name")) - - if expected_columns: - self.fail(f"Expected db index on these columns: {', '.join(expected_columns)}") - - def test_website_item_desk_item_sync(self): - "Check creation/updation/deletion of Website Item and its impact on Item master." - web_item = None - item = make_item("Test Web Item") # will return item if exists - try: - web_item = make_website_item(item, save=False) - web_item.save() - except Exception: - self.fail(f"Error while creating website item for {item}") - - # check if website item was created - self.assertTrue(bool(web_item)) - self.assertTrue(bool(web_item.route)) - - item.reload() - self.assertEqual(web_item.published, 1) - self.assertEqual(item.published_in_website, 1) # check if item was back updated - self.assertEqual(web_item.item_group, item.item_group) - - # check if changing item data changes it in website item - item.item_name = "Test Web Item 1" - item.stock_uom = "Unit" - item.save() - web_item.reload() - self.assertEqual(web_item.item_name, item.item_name) - self.assertEqual(web_item.stock_uom, item.stock_uom) - - # check if disabling item unpublished website item - item.disabled = 1 - item.save() - web_item.reload() - self.assertEqual(web_item.published, 0) - - # check if website item deletion, unpublishes desk item - web_item.delete() - item.reload() - self.assertEqual(item.published_in_website, 0) - - item.delete() - - def test_publish_variant_and_template(self): - "Check if template is published on publishing variant." - # template "Test Web Item" created on setUp - variant = create_variant("Test Web Item", {"Test Size": "Large"}) - variant.save() - - # check if template is not published - self.assertIsNone(frappe.db.exists("Website Item", {"item_code": variant.variant_of})) - - variant_web_item = make_website_item(variant, save=False) - variant_web_item.save() - - # check if template is published - try: - template_web_item = frappe.get_doc("Website Item", {"item_code": variant.variant_of}) - except frappe.DoesNotExistError: - self.fail(f"Template of {variant.item_code}, {variant.variant_of} not published") - - # teardown - variant_web_item.delete() - template_web_item.delete() - variant.delete() - - def test_impact_on_merging_items(self): - "Check if merging items is blocked if old and new items both have website items" - first_item = make_item("Test First Item") - second_item = make_item("Test Second Item") - - first_web_item = make_website_item(first_item, save=False) - first_web_item.save() - second_web_item = make_website_item(second_item, save=False) - second_web_item.save() - - with self.assertRaises(DataValidationError): - frappe.rename_doc("Item", "Test First Item", "Test Second Item", merge=True) - - # tear down - second_web_item.delete() - first_web_item.delete() - second_item.delete() - first_item.delete() - - # Website Item Portal Tests Begin - - def test_website_item_breadcrumbs(self): - """ - Check if breadcrumbs include homepage, product listing navigation page, - parent item group(s) and item group - """ - from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups - - item_code = "Test Breadcrumb Item" - item = make_item( - item_code, - { - "item_group": "_Test Item Group B - 1", - }, - ) - - if not frappe.db.exists("Website Item", {"item_code": item_code}): - web_item = make_website_item(item, save=False) - web_item.save() - else: - web_item = frappe.get_cached_doc("Website Item", {"item_code": item_code}) - - frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1) - frappe.db.set_value("Item Group", "_Test Item Group B", "show_in_website", 1) - - breadcrumbs = get_parent_item_groups(item.item_group) - - settings = frappe.get_cached_doc("E Commerce Settings") - if settings.enable_field_filters: - base_breadcrumb = "Shop by Category" - else: - base_breadcrumb = "All Products" - - self.assertEqual(breadcrumbs[0]["name"], "Home") - self.assertEqual(breadcrumbs[1]["name"], base_breadcrumb) - self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group - self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1") - - # tear down - web_item.delete() - item.delete() - - def test_website_item_price_for_logged_in_user(self): - "Check if price details are fetched correctly while logged in." - item_code = "Test Mobile Phone" - - # show price in e commerce settings - setup_e_commerce_settings({"show_price": 1}) - - # price and pricing rule added via setUp - - # login as customer with pricing rule - frappe.set_user("test_contact_customer@example.com") - - # check if price and slashed price is fetched correctly - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertTrue(bool(data.product_info["price"])) - - price_object = data.product_info["price"] - self.assertEqual(price_object.get("discount_percent"), 25.0) - self.assertEqual(price_object.get("price_list_rate"), 750) - self.assertEqual(price_object.get("formatted_mrp"), "₹ 1,000.00") - self.assertEqual(price_object.get("formatted_price"), "₹ 750.00") - self.assertEqual(price_object.get("formatted_discount_percent"), "25.0%") - - # switch to admin and disable show price - frappe.set_user("Administrator") - setup_e_commerce_settings({"show_price": 0}) - - # price should not be fetched for logged in user. - frappe.set_user("test_contact_customer@example.com") - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertFalse(bool(data.product_info["price"])) - - # tear down - frappe.set_user("Administrator") - - def test_website_item_price_for_guest_user(self): - "Check if price details are fetched correctly for guest user." - item_code = "Test Mobile Phone" - - # show price for guest user in e commerce settings - setup_e_commerce_settings({"show_price": 1, "hide_price_for_guest": 0}) - - # price and pricing rule added via setUp - - # switch to guest user - frappe.set_user("Guest") - - # price should be fetched - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertTrue(bool(data.product_info["price"])) - - price_object = data.product_info["price"] - self.assertEqual(price_object.get("discount_percent"), 10) - self.assertEqual(price_object.get("price_list_rate"), 900) - - # hide price for guest user - frappe.set_user("Administrator") - setup_e_commerce_settings({"hide_price_for_guest": 1}) - frappe.set_user("Guest") - - # price should not be fetched - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertFalse(bool(data.product_info["price"])) - - # tear down - frappe.set_user("Administrator") - - def test_website_item_stock_when_out_of_stock(self): - """ - Check if stock details are fetched correctly for empty inventory when: - 1) Showing stock availability enabled: - - Warehouse unset - - Warehouse set - 2) Showing stock availability disabled - """ - item_code = "Test Mobile Phone" - create_regular_web_item() - setup_e_commerce_settings({"show_stock_availability": 1}) - - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - - # check if stock details are fetched and item not in stock without warehouse set - self.assertFalse(bool(data.product_info["in_stock"])) - self.assertFalse(bool(data.product_info["stock_qty"])) - - # set warehouse - frappe.db.set_value( - "Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC" - ) - - # check if stock details are fetched and item not in stock with warehouse set - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertFalse(bool(data.product_info["in_stock"])) - self.assertEqual(data.product_info["stock_qty"], 0) - - # disable show stock availability - setup_e_commerce_settings({"show_stock_availability": 0}) - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - - # check if stock detail attributes are not fetched if stock availability is hidden - self.assertIsNone(data.product_info.get("in_stock")) - self.assertIsNone(data.product_info.get("stock_qty")) - self.assertIsNone(data.product_info.get("show_stock_qty")) - - # tear down - frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete() - - def test_website_item_stock_when_in_stock(self): - """ - Check if stock details are fetched correctly for available inventory when: - 1) Showing stock availability enabled: - - Warehouse set - - Warehouse unset - 2) Showing stock availability disabled - """ - from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry - - item_code = "Test Mobile Phone" - create_regular_web_item() - setup_e_commerce_settings({"show_stock_availability": 1}) - frappe.local.shopping_cart_settings = None - - # set warehouse - frappe.db.set_value( - "Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC" - ) - - # stock up item - stock_entry = make_stock_entry( - item_code=item_code, target="_Test Warehouse - _TC", qty=2, rate=100 - ) - - # check if stock details are fetched and item is in stock with warehouse set - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertTrue(bool(data.product_info["in_stock"])) - self.assertEqual(data.product_info["stock_qty"], 2) - - # unset warehouse - frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "") - - # check if stock details are fetched and item not in stock without warehouse set - # (even though it has stock in some warehouse) - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - self.assertFalse(bool(data.product_info["in_stock"])) - self.assertFalse(data.product_info["stock_qty"]) - - # disable show stock availability - setup_e_commerce_settings({"show_stock_availability": 0}) - frappe.local.shopping_cart_settings = None - data = get_product_info_for_website(item_code, skip_quotation_creation=True) - - # check if stock detail attributes are not fetched if stock availability is hidden - self.assertIsNone(data.product_info.get("in_stock")) - self.assertIsNone(data.product_info.get("stock_qty")) - self.assertIsNone(data.product_info.get("show_stock_qty")) - - # tear down - stock_entry.cancel() - frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete() - - def test_recommended_item(self): - "Check if added recommended items are fetched correctly." - item_code = "Test Mobile Phone" - web_item = create_regular_web_item(item_code) - - setup_e_commerce_settings({"enable_recommendations": 1, "show_price": 1}) - - # create recommended web item and price for it - recommended_web_item = create_regular_web_item("Test Mobile Phone 1") - make_web_item_price(item_code="Test Mobile Phone 1") - - # add recommended item to first web item - web_item.append("recommended_items", {"website_item": recommended_web_item.name}) - web_item.save() - - frappe.local.shopping_cart_settings = None - e_commerce_settings = get_shopping_cart_settings() - recommended_items = web_item.get_recommended_items(e_commerce_settings) - - # test results if show price is enabled - self.assertEqual(len(recommended_items), 1) - recomm_item = recommended_items[0] - self.assertEqual(recomm_item.get("website_item_name"), "Test Mobile Phone 1") - self.assertTrue(bool(recomm_item.get("price_info"))) # price fetched - - price_info = recomm_item.get("price_info") - self.assertEqual(price_info.get("price_list_rate"), 1000) - self.assertEqual(price_info.get("formatted_price"), "₹ 1,000.00") - - # test results if show price is disabled - setup_e_commerce_settings({"show_price": 0}) - - frappe.local.shopping_cart_settings = None - e_commerce_settings = get_shopping_cart_settings() - recommended_items = web_item.get_recommended_items(e_commerce_settings) - - self.assertEqual(len(recommended_items), 1) - self.assertFalse(bool(recommended_items[0].get("price_info"))) # price not fetched - - # tear down - web_item.delete() - recommended_web_item.delete() - frappe.get_cached_doc("Item", "Test Mobile Phone 1").delete() - - def test_recommended_item_for_guest_user(self): - "Check if added recommended items are fetched correctly for guest user." - item_code = "Test Mobile Phone" - web_item = create_regular_web_item(item_code) - - # price visible to guests - setup_e_commerce_settings( - {"enable_recommendations": 1, "show_price": 1, "hide_price_for_guest": 0} - ) - - # create recommended web item and price for it - recommended_web_item = create_regular_web_item("Test Mobile Phone 1") - make_web_item_price(item_code="Test Mobile Phone 1") - - # add recommended item to first web item - web_item.append("recommended_items", {"website_item": recommended_web_item.name}) - web_item.save() - - frappe.set_user("Guest") - - frappe.local.shopping_cart_settings = None - e_commerce_settings = get_shopping_cart_settings() - recommended_items = web_item.get_recommended_items(e_commerce_settings) - - # test results if show price is enabled - self.assertEqual(len(recommended_items), 1) - self.assertTrue(bool(recommended_items[0].get("price_info"))) # price fetched - - # price hidden from guests - frappe.set_user("Administrator") - setup_e_commerce_settings({"hide_price_for_guest": 1}) - frappe.set_user("Guest") - - frappe.local.shopping_cart_settings = None - e_commerce_settings = get_shopping_cart_settings() - recommended_items = web_item.get_recommended_items(e_commerce_settings) - - # test results if show price is enabled - self.assertEqual(len(recommended_items), 1) - self.assertFalse(bool(recommended_items[0].get("price_info"))) # price fetched - - # tear down - frappe.set_user("Administrator") - web_item.delete() - recommended_web_item.delete() - frappe.get_cached_doc("Item", "Test Mobile Phone 1").delete() - - -def create_regular_web_item(item_code=None, item_args=None, web_args=None): - "Create Regular Item and Website Item." - item_code = item_code or "Test Mobile Phone" - item = make_item(item_code, properties=item_args) - - if not frappe.db.exists("Website Item", {"item_code": item_code}): - web_item = make_website_item(item, save=False) - if web_args: - web_item.update(web_args) - web_item.save() - else: - web_item = frappe.get_cached_doc("Website Item", {"item_code": item_code}) - - return web_item - - -def make_web_item_price(**kwargs): - item_code = kwargs.get("item_code") - if not item_code: - return - - if not frappe.db.exists("Item Price", {"item_code": item_code}): - item_price = frappe.get_doc( - { - "doctype": "Item Price", - "item_code": item_code, - "price_list": kwargs.get("price_list") or "_Test Price List India", - "price_list_rate": kwargs.get("price_list_rate") or 1000, - } - ) - item_price.insert() - else: - item_price = frappe.get_cached_doc("Item Price", {"item_code": item_code}) - - return item_price - - -def make_web_pricing_rule(**kwargs): - title = kwargs.get("title") - if not title: - return - - if not frappe.db.exists("Pricing Rule", title): - pricing_rule = frappe.get_doc( - { - "doctype": "Pricing Rule", - "title": title, - "apply_on": kwargs.get("apply_on") or "Item Code", - "items": [{"item_code": kwargs.get("item_code")}], - "selling": kwargs.get("selling") or 0, - "buying": kwargs.get("buying") or 0, - "rate_or_discount": kwargs.get("rate_or_discount") or "Discount Percentage", - "discount_percentage": kwargs.get("discount_percentage") or 10, - "company": kwargs.get("company") or "_Test Company", - "currency": kwargs.get("currency") or "INR", - "for_price_list": kwargs.get("price_list") or "_Test Price List India", - "applicable_for": kwargs.get("applicable_for") or "", - "customer": kwargs.get("customer") or "", - } - ) - pricing_rule.insert() - else: - pricing_rule = frappe.get_doc("Pricing Rule", {"title": title}) - - return pricing_rule - - -def create_user_and_customer_if_not_exists(email, first_name=None): - if frappe.db.exists("User", email): - return - - frappe.get_doc( - { - "doctype": "User", - "user_type": "Website User", - "email": email, - "send_welcome_email": 0, - "first_name": first_name or email.split("@")[0], - } - ).insert(ignore_permissions=True) - - contact = frappe.get_last_doc("Contact", filters={"email_id": email}) - link = contact.append("links", {}) - link.link_doctype = "Customer" - link.link_name = "_Test Customer" - link.link_title = "_Test Customer" - contact.save() - - -test_dependencies = ["Price List", "Item Price", "Customer", "Contact", "Item"] diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js deleted file mode 100644 index b6595cce8a9c..000000000000 --- a/erpnext/e_commerce/doctype/website_item/website_item.js +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Website Item', { - onload: (frm) => { - // should never check Private - frm.fields_dict["website_image"].df.is_private = 0; - }, - - refresh: (frm) => { - frm.add_custom_button(__("Prices"), function() { - frappe.set_route("List", "Item Price", {"item_code": frm.doc.item_code}); - }, __("View")); - - frm.add_custom_button(__("Stock"), function() { - frappe.route_options = { - "item_code": frm.doc.item_code - }; - frappe.set_route("query-report", "Stock Balance"); - }, __("View")); - - frm.add_custom_button(__("E Commerce Settings"), function() { - frappe.set_route("Form", "E Commerce Settings"); - }, __("View")); - }, - - copy_from_item_group: (frm) => { - return frm.call({ - doc: frm.doc, - method: "copy_specification_from_item_group" - }); - }, - - set_meta_tags: (frm) => { - frappe.utils.set_meta_tag(frm.doc.route); - } -}); diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json deleted file mode 100644 index 6f551a0b42d6..000000000000 --- a/erpnext/e_commerce/doctype/website_item/website_item.json +++ /dev/null @@ -1,414 +0,0 @@ -{ - "actions": [], - "allow_guest_to_view": 1, - "allow_import": 1, - "autoname": "naming_series", - "creation": "2021-02-09 21:06:14.441698", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "naming_series", - "web_item_name", - "route", - "has_variants", - "variant_of", - "published", - "column_break_3", - "item_code", - "item_name", - "item_group", - "stock_uom", - "column_break_11", - "description", - "brand", - "display_section", - "website_image", - "website_image_alt", - "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_specifications", - "copy_from_item_group", - "display_additional_information_section", - "show_tabbed_section", - "tabs", - "recommended_items_section", - "recommended_items", - "offers_section", - "offers", - "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", - "read_only_depends_on": "eval:!doc.__islocal", - "reqd": 1 - }, - { - "fetch_from": "item_code.item_name", - "fieldname": "item_name", - "fieldtype": "Data", - "label": "Item Name", - "read_only": 1 - }, - { - "collapsible": 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 Image", - "hidden": 1, - "in_preview": 1, - "label": "Website Image", - "print_hide": 1 - }, - { - "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. If the parent warehouse is selected, then the system will display the consolidated available quantity of all child warehouses.", - "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, - "search_index": 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", - "search_index": 1 - }, - { - "collapsible": 1, - "fieldname": "advanced_display_section", - "fieldtype": "Section Break", - "label": "Advanced Display Content" - }, - { - "fieldname": "display_section", - "fieldtype": "Section Break", - "label": "Display Images" - }, - { - "fieldname": "column_break_27", - "fieldtype": "Column Break" - }, - { - "fieldname": "column_break_22", - "fieldtype": "Column Break" - }, - { - "fetch_from": "item_code.description", - "fieldname": "description", - "fieldtype": "Text Editor", - "label": "Item Description", - "read_only": 1 - }, - { - "default": "WEB-ITM-.####", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 1, - "label": "Naming Series", - "no_copy": 1, - "options": "WEB-ITM-.####", - "print_hide": 1 - }, - { - "fieldname": "display_additional_information_section", - "fieldtype": "Section Break", - "label": "Display Additional Information" - }, - { - "depends_on": "show_tabbed_section", - "fieldname": "tabs", - "fieldtype": "Table", - "label": "Tabs", - "options": "Website Item Tabbed Section" - }, - { - "default": "0", - "fieldname": "show_tabbed_section", - "fieldtype": "Check", - "label": "Add Section with Tabs" - }, - { - "collapsible": 1, - "fieldname": "offers_section", - "fieldtype": "Section Break", - "label": "Offers" - }, - { - "fieldname": "offers", - "fieldtype": "Table", - "label": "Offers to Display", - "options": "Website Offer" - }, - { - "fieldname": "column_break_11", - "fieldtype": "Column Break" - }, - { - "description": "Short Description for List View", - "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" - }, - { - "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": "website_image", - "index_web_pages_for_search": 1, - "links": [], - "make_attachments_public": 1, - "modified": "2023-09-12 14:19:22.822689", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Website Item", - "naming_rule": "Expression (old style)", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Website Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock Manager", - "share": 1, - "write": 1 - } - ], - "search_fields": "web_item_name, item_code, item_group", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "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 deleted file mode 100644 index 81b8ecab48e8..000000000000 --- a/erpnext/e_commerce/doctype/website_item/website_item.py +++ /dev/null @@ -1,469 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import json -from typing import TYPE_CHECKING, List, Union - -if TYPE_CHECKING: - from erpnext.stock.doctype.item.item import Item - -import frappe -from frappe import _ -from frappe.utils import cint, cstr, flt, random_string -from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow -from frappe.website.website_generator import WebsiteGenerator - -from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews -from erpnext.e_commerce.redisearch_utils import ( - delete_item_from_index, - insert_item_to_index, - update_index_for_item, -) -from erpnext.e_commerce.shopping_cart.cart import _set_price_list -from erpnext.setup.doctype.item_group.item_group import ( - get_parent_item_groups, - invalidate_cache_for, -) -from erpnext.utilities.product import get_price - - -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 autoname(self): - # use naming series to accomodate items with same name (different item code) - from frappe.model.naming import get_default_naming_series, make_autoname - - naming_series = get_default_naming_series("Website Item") - if not self.name and naming_series: - self.name = make_autoname(naming_series, doc=self) - - 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_duplicate_website_item() - self.validate_website_image() - self.make_thumbnail() - self.publish_unpublish_desk_item(publish=True) - - if not self.get("__islocal"): - wig = frappe.qb.DocType("Website Item Group") - query = ( - frappe.qb.from_(wig) - .select(wig.item_group) - .where( - (wig.parentfield == "website_item_groups") - & (wig.parenttype == "Website Item") - & (wig.parent == self.name) - ) - ) - result = query.run(as_list=True) - - self.old_website_item_groups = [x[0] for x in result] - - def on_update(self): - invalidate_cache_for_web_item(self) - self.update_template_item() - - def on_trash(self): - super(WebsiteItem, self).on_trash() - delete_item_from_index(self) - self.publish_unpublish_desk_item(publish=False) - - def validate_duplicate_website_item(self): - existing_web_item = frappe.db.exists("Website Item", {"item_code": self.item_code}) - if existing_web_item and existing_web_item != self.name: - message = _("Website Item already exists against Item {0}").format(frappe.bold(self.item_code)) - frappe.throw(message, title=_("Already Published")) - - 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 update_template_item(self): - """Publish Template Item if Variant is published.""" - if self.variant_of: - if self.published: - # show template - template_item = frappe.get_doc("Item", self.variant_of) - - if not template_item.published_in_website: - template_item.flags.ignore_permissions = True - make_website_item(template_item) - - def validate_website_image(self): - if frappe.flags.in_import: - return - - """Validate if the website image is a public file""" - 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: - 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: - frappe.msgprint(_("Website Image should be a public file or website URL")) - - self.website_image = None - - def make_thumbnail(self): - """Make a thumbnail of `website_image`""" - if frappe.flags.in_import or frappe.flags.in_migrate: - return - - import requests.exceptions - - db_website_image = frappe.db.get_value(self.doctype, self.name, "website_image") - if not self.is_new() and self.website_image != db_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": "Website 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": "Website 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 - - def get_context(self, context): - context.show_search = True - 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_metatags(context) - self.set_shopping_cart_data(context) - - settings = context.shopping_cart.cart_settings - - self.get_product_details_section(context) - - if settings.get("enable_reviews"): - reviews_data = get_item_reviews(self.name) - context.update(reviews_data) - context.reviews = context.reviews[:4] - - context.wished = False - if frappe.db.exists( - "Wishlist Item", {"item_code": self.item_code, "parent": frappe.session.user} - ): - context.wished = True - - context.user_is_customer = check_if_user_is_customer() - - context.recommended_items = None - if settings and settings.enable_recommendations: - context.recommended_items = self.get_recommended_items(settings) - - return context - - def set_selected_attributes(self, variants, context, attribute_values_available): - for variant in variants: - variant.attributes = frappe.get_all( - "Item Variant Attribute", - filters={"parent": variant.name}, - fields=["attribute", "attribute_value as value"], - ) - - # make an attribute-value map for easier access in templates - variant.attribute_map = frappe._dict( - {attr.attribute: attr.value for attr in variant.attributes} - ) - - for attr in variant.attributes: - values = attribute_values_available.setdefault(attr.attribute, []) - if attr.value not in values: - values.append(attr.value) - - if variant.name == context.variant.name: - context.selected_attributes[attr.attribute] = attr.value - - def set_attribute_values(self, attributes, context, attribute_values_available): - for attr in attributes: - values = context.attribute_values.setdefault(attr.attribute, []) - - if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")): - for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt): - values.append(val) - else: - # get list of values defined (for sequence) - for attr_value in frappe.db.get_all( - "Item Attribute Value", - fields=["attribute_value"], - filters={"parent": attr.attribute}, - order_by="idx asc", - ): - - if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []): - values.append(attr_value.attribute_value) - - def set_metatags(self, context): - context.metatags = frappe._dict({}) - - safe_description = frappe.utils.to_markdown(self.description) - - context.metatags.url = frappe.utils.get_url() + "/" + context.route - - if context.website_image: - if context.website_image.startswith("http"): - url = context.website_image - else: - url = frappe.utils.get_url() + context.website_image - context.metatags.image = url - - context.metatags.description = safe_description[:300] - - context.metatags.title = self.web_item_name or self.item_name or self.item_code - - context.metatags["og:type"] = "product" - context.metatags["og:site_name"] = "ERPNext" - - def set_shopping_cart_data(self, context): - from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website - - context.shopping_cart = get_product_info_for_website( - self.item_code, skip_quotation_creation=True - ) - - @frappe.whitelist() - def copy_specification_from_item_group(self): - self.set("website_specifications", []) - if self.item_group: - for label, desc in frappe.db.get_values( - "Item Website Specification", {"parent": self.item_group}, ["label", "description"] - ): - row = self.append("website_specifications") - row.label = label - row.description = desc - - 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 or self.website_specifications): - context.tabs = self.get_tabs() - else: - context.website_specifications = self.website_specifications - - def get_tabs(self): - tab_values = {} - tab_values["tab_1_title"] = "Product Details" - tab_values["tab_1_content"] = frappe.render_template( - "templates/generators/item/item_specifications.html", - {"website_specifications": self.website_specifications, "show_tabs": self.show_tabbed_section}, - ) - - for row in self.tabs: - tab_values[f"tab_{row.idx + 1}_title"] = _(row.label) - tab_values[f"tab_{row.idx + 1}_content"] = row.content - - return tab_values - - def get_recommended_items(self, settings): - ri = frappe.qb.DocType("Recommended Items") - wi = frappe.qb.DocType("Website Item") - - query = ( - frappe.qb.from_(ri) - .join(wi) - .on(ri.item_code == wi.item_code) - .select(ri.item_code, ri.route, ri.website_item_name, ri.website_item_thumbnail) - .where((ri.parent == self.name) & (wi.published == 1)) - .orderby(ri.idx) - ) - items = query.run(as_dict=True) - - 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 - - invalidate_cache_for(doc, doc.item_group) - - website_item_groups = list( - set( - (doc.get("old_website_item_groups") or []) - + [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group] - ) - ) - - for item_group in website_item_groups: - invalidate_cache_for(doc, item_group) - - # Update Search Cache - update_index_for_item(doc) - - invalidate_item_variants_cache_for_website(doc) - - -def on_doctype_update(): - # since route is a Text column, it needs a length for indexing - frappe.db.add_index("Website Item", ["route(500)"]) - - -def check_if_user_is_customer(user=None): - from frappe.contacts.doctype.contact.contact import get_contact_name - - if not user: - 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 - - return True if customer else False - - -@frappe.whitelist() -def make_website_item(doc: "Item", save: bool = True) -> Union["WebsiteItem", List[str]]: - "Make Website Item from Item. Used via Form UI or patch." - - if not doc: - return - - if isinstance(doc, str): - 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")) - - 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", - "has_variants", - "variant_of", - "description", - ] - for field in fields_to_map: - website_item.update({field: doc.get(field)}) - - # Needed for publishing/mapping via Form UI only - if not frappe.flags.in_migrate and (doc.get("image") and not website_item.website_image): - website_item.website_image = doc.get("image") - - if not save: - return website_item - - website_item.save() - - # Add to search cache - insert_item_to_index(website_item) - - 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 deleted file mode 100644 index b9dd9214a38f..000000000000 --- a/erpnext/e_commerce/doctype/website_item/website_item_list.js +++ /dev/null @@ -1,20 +0,0 @@ -frappe.listview_settings['Website Item'] = { - add_fields: ["item_name", "web_item_name", "published", "website_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/e_commerce/doctype/website_item_tabbed_section/__init__.py b/erpnext/e_commerce/doctype/website_item_tabbed_section/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json deleted file mode 100644 index 6601dd81f214..000000000000 --- a/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "actions": [], - "creation": "2021-03-18 20:32:15.321402", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "label", - "content" - ], - "fields": [ - { - "fieldname": "label", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Label" - }, - { - "fieldname": "content", - "fieldtype": "HTML Editor", - "in_list_view": 1, - "label": "Content" - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-03-18 20:35:26.991192", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Website Item Tabbed Section", - "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/website_item_tabbed_section/website_item_tabbed_section.py b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py deleted file mode 100644 index 91148b8b0480..000000000000 --- a/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-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 WebsiteItemTabbedSection(Document): - pass diff --git a/erpnext/e_commerce/doctype/website_offer/__init__.py b/erpnext/e_commerce/doctype/website_offer/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/doctype/website_offer/website_offer.json b/erpnext/e_commerce/doctype/website_offer/website_offer.json deleted file mode 100644 index 627d548146ba..000000000000 --- a/erpnext/e_commerce/doctype/website_offer/website_offer.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "actions": [], - "creation": "2021-04-21 13:37:14.162162", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "offer_title", - "offer_subtitle", - "offer_details" - ], - "fields": [ - { - "fieldname": "offer_title", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Offer Title" - }, - { - "fieldname": "offer_subtitle", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Offer Subtitle" - }, - { - "fieldname": "offer_details", - "fieldtype": "Text Editor", - "label": "Offer Details" - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-04-21 13:56:04.660331", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Website Offer", - "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/website_offer/website_offer.py b/erpnext/e_commerce/doctype/website_offer/website_offer.py deleted file mode 100644 index 8c92f75a1e9b..000000000000 --- a/erpnext/e_commerce/doctype/website_offer/website_offer.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-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 WebsiteOffer(Document): - pass - - -@frappe.whitelist(allow_guest=True) -def get_offer_details(offer_id): - return frappe.db.get_value("Website Offer", {"name": offer_id}, ["offer_details"]) diff --git a/erpnext/e_commerce/doctype/wishlist/__init__.py b/erpnext/e_commerce/doctype/wishlist/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/doctype/wishlist/test_wishlist.py b/erpnext/e_commerce/doctype/wishlist/test_wishlist.py deleted file mode 100644 index 9d27126fdb29..000000000000 --- a/erpnext/e_commerce/doctype/wishlist/test_wishlist.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -import unittest - -import frappe -from frappe.core.doctype.user_permission.test_user_permission import create_user - -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item -from erpnext.e_commerce.doctype.wishlist.wishlist import add_to_wishlist, remove_from_wishlist -from erpnext.stock.doctype.item.test_item import make_item - - -class TestWishlist(unittest.TestCase): - def setUp(self): - item = make_item("Test Phone Series X") - if not frappe.db.exists("Website Item", {"item_code": "Test Phone Series X"}): - make_website_item(item, save=True) - - item = make_item("Test Phone Series Y") - if not frappe.db.exists("Website Item", {"item_code": "Test Phone Series Y"}): - make_website_item(item, save=True) - - def tearDown(self): - frappe.get_cached_doc("Website Item", {"item_code": "Test Phone Series X"}).delete() - frappe.get_cached_doc("Website Item", {"item_code": "Test Phone Series Y"}).delete() - frappe.get_cached_doc("Item", "Test Phone Series X").delete() - frappe.get_cached_doc("Item", "Test Phone Series Y").delete() - - def test_add_remove_items_in_wishlist(self): - "Check if items are added and removed from user's wishlist." - # add first item - add_to_wishlist("Test Phone Series X") - - # check if wishlist was created and item was added - self.assertTrue(frappe.db.exists("Wishlist", {"user": frappe.session.user})) - self.assertTrue( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": frappe.session.user} - ) - ) - - # add second item to wishlist - add_to_wishlist("Test Phone Series Y") - wishlist_length = frappe.db.get_value( - "Wishlist Item", {"parent": frappe.session.user}, "count(*)" - ) - self.assertEqual(wishlist_length, 2) - - remove_from_wishlist("Test Phone Series X") - remove_from_wishlist("Test Phone Series Y") - - wishlist_length = frappe.db.get_value( - "Wishlist Item", {"parent": frappe.session.user}, "count(*)" - ) - self.assertIsNone(frappe.db.exists("Wishlist Item", {"parent": frappe.session.user})) - self.assertEqual(wishlist_length, 0) - - # tear down - frappe.get_doc("Wishlist", {"user": frappe.session.user}).delete() - - def test_add_remove_in_wishlist_multiple_users(self): - "Check if items are added and removed from the correct user's wishlist." - test_user = create_user("test_reviewer@example.com", "Customer") - test_user_1 = create_user("test_reviewer_1@example.com", "Customer") - - # add to wishlist for first user - frappe.set_user(test_user.name) - add_to_wishlist("Test Phone Series X") - - # add to wishlist for second user - frappe.set_user(test_user_1.name) - add_to_wishlist("Test Phone Series X") - - # check wishlist and its content for users - self.assertTrue(frappe.db.exists("Wishlist", {"user": test_user.name})) - self.assertTrue( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name} - ) - ) - - self.assertTrue(frappe.db.exists("Wishlist", {"user": test_user_1.name})) - self.assertTrue( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user_1.name} - ) - ) - - # remove item for second user - remove_from_wishlist("Test Phone Series X") - - # make sure item was removed for second user and not first - self.assertFalse( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user_1.name} - ) - ) - self.assertTrue( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name} - ) - ) - - # remove item for first user - frappe.set_user(test_user.name) - remove_from_wishlist("Test Phone Series X") - self.assertFalse( - frappe.db.exists( - "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name} - ) - ) - - # tear down - frappe.set_user("Administrator") - frappe.get_doc("Wishlist", {"user": test_user.name}).delete() - frappe.get_doc("Wishlist", {"user": test_user_1.name}).delete() diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.js b/erpnext/e_commerce/doctype/wishlist/wishlist.js deleted file mode 100644 index d96e552ecdb6..000000000000 --- a/erpnext/e_commerce/doctype/wishlist/wishlist.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Wishlist', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.json b/erpnext/e_commerce/doctype/wishlist/wishlist.json deleted file mode 100644 index 922924e53b04..000000000000 --- a/erpnext/e_commerce/doctype/wishlist/wishlist.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "actions": [], - "autoname": "field:user", - "creation": "2021-03-10 18:52:28.769126", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "user", - "section_break_2", - "items" - ], - "fields": [ - { - "fieldname": "user", - "fieldtype": "Link", - "in_list_view": 1, - "label": "User", - "options": "User", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "section_break_2", - "fieldtype": "Section Break" - }, - { - "fieldname": "items", - "fieldtype": "Table", - "label": "Items", - "options": "Wishlist Item" - } - ], - "in_create": 1, - "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-07-08 13:11:21.693956", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Wishlist", - "owner": "Administrator", - "permissions": [ - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 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/wishlist/wishlist.py b/erpnext/e_commerce/doctype/wishlist/wishlist.py deleted file mode 100644 index eb74027d77de..000000000000 --- a/erpnext/e_commerce/doctype/wishlist/wishlist.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-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 Wishlist(Document): - pass - - -@frappe.whitelist() -def add_to_wishlist(item_code): - """Insert Item into wishlist.""" - - if frappe.db.exists("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user}): - return - - web_item_data = frappe.db.get_value( - "Website Item", - {"item_code": item_code}, - [ - "website_image", - "website_warehouse", - "name", - "web_item_name", - "item_name", - "item_group", - "route", - ], - as_dict=1, - ) - - wished_item_dict = { - "item_code": item_code, - "item_name": web_item_data.get("item_name"), - "item_group": web_item_data.get("item_group"), - "website_item": web_item_data.get("name"), - "web_item_name": web_item_data.get("web_item_name"), - "image": web_item_data.get("website_image"), - "warehouse": web_item_data.get("website_warehouse"), - "route": web_item_data.get("route"), - } - - if not frappe.db.exists("Wishlist", frappe.session.user): - # initialise wishlist - wishlist = frappe.get_doc({"doctype": "Wishlist"}) - wishlist.user = frappe.session.user - wishlist.append("items", wished_item_dict) - wishlist.save(ignore_permissions=True) - else: - wishlist = frappe.get_doc("Wishlist", frappe.session.user) - item = wishlist.append("items", wished_item_dict) - item.db_insert() - - if hasattr(frappe.local, "cookie_manager"): - frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items))) - - -@frappe.whitelist() -def remove_from_wishlist(item_code): - if frappe.db.exists("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user}): - frappe.db.delete("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user}) - frappe.db.commit() # nosemgrep - - wishlist_items = frappe.db.get_values("Wishlist Item", filters={"parent": frappe.session.user}) - - if hasattr(frappe.local, "cookie_manager"): - frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist_items))) diff --git a/erpnext/e_commerce/doctype/wishlist_item/__init__.py b/erpnext/e_commerce/doctype/wishlist_item/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.json b/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.json deleted file mode 100644 index c0414a7f8ede..000000000000 --- a/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "actions": [], - "creation": "2021-03-10 19:03:00.662714", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "item_code", - "website_item", - "web_item_name", - "column_break_3", - "item_name", - "item_group", - "item_details_section", - "description", - "column_break_7", - "route", - "image", - "image_view", - "section_break_8", - "warehouse_section", - "warehouse" - ], - "fields": [ - { - "fetch_from": "website_item.item_code", - "fetch_if_empty": 1, - "fieldname": "item_code", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Code", - "options": "Item", - "reqd": 1 - }, - { - "fieldname": "website_item", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Website Item", - "options": "Website Item", - "read_only": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fetch_from": "item_code.item_name", - "fetch_if_empty": 1, - "fieldname": "item_name", - "fieldtype": "Data", - "label": "Item Name", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "item_details_section", - "fieldtype": "Section Break", - "label": "Item Details", - "read_only": 1 - }, - { - "fetch_from": "item_code.description", - "fetch_if_empty": 1, - "fieldname": "description", - "fieldtype": "Text Editor", - "label": "Description", - "read_only": 1 - }, - { - "fieldname": "column_break_7", - "fieldtype": "Column Break" - }, - { - "fetch_from": "item_code.image", - "fetch_if_empty": 1, - "fieldname": "image", - "fieldtype": "Attach", - "hidden": 1, - "label": "Image" - }, - { - "fetch_from": "item_code.image", - "fetch_if_empty": 1, - "fieldname": "image_view", - "fieldtype": "Image", - "hidden": 1, - "label": "Image View", - "options": "image", - "print_hide": 1 - }, - { - "fieldname": "warehouse_section", - "fieldtype": "Section Break", - "label": "Warehouse" - }, - { - "fieldname": "warehouse", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Warehouse", - "options": "Warehouse", - "read_only": 1 - }, - { - "fieldname": "section_break_8", - "fieldtype": "Section Break" - }, - { - "fetch_from": "item_code.item_group", - "fetch_if_empty": 1, - "fieldname": "item_group", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "read_only": 1 - }, - { - "fetch_from": "website_item.route", - "fetch_if_empty": 1, - "fieldname": "route", - "fieldtype": "Small Text", - "label": "Route", - "read_only": 1 - }, - { - "fetch_from": "website_item.web_item_name", - "fetch_if_empty": 1, - "fieldname": "web_item_name", - "fieldtype": "Data", - "label": "Website Item Name", - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-08-09 10:30:41.964802", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Wishlist Item", - "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/wishlist_item/wishlist_item.py b/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.py deleted file mode 100644 index 75ebccbc1b7c..000000000000 --- a/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-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 WishlistItem(Document): - pass diff --git a/erpnext/e_commerce/legacy_search.py b/erpnext/e_commerce/legacy_search.py deleted file mode 100644 index ef8e86d44282..000000000000 --- a/erpnext/e_commerce/legacy_search.py +++ /dev/null @@ -1,134 +0,0 @@ -import frappe -from frappe.search.full_text_search import FullTextSearch -from frappe.utils import strip_html_tags -from whoosh.analysis import StemmingAnalyzer -from whoosh.fields import ID, KEYWORD, TEXT, Schema -from whoosh.qparser import FieldsPlugin, MultifieldParser, WildcardPlugin -from whoosh.query import Prefix - -# TODO: Make obsolete -INDEX_NAME = "products" - - -class ProductSearch(FullTextSearch): - """Wrapper for WebsiteSearch""" - - def get_schema(self): - return Schema( - title=TEXT(stored=True, field_boost=1.5), - name=ID(stored=True), - path=ID(stored=True), - content=TEXT(stored=True, analyzer=StemmingAnalyzer()), - keywords=KEYWORD(stored=True, scorable=True, commas=True), - ) - - def get_id(self): - return "name" - - def get_items_to_index(self): - """Get all routes to be indexed, this includes the static pages - in www/ and routes from published documents - - Returns: - self (object): FullTextSearch Instance - """ - items = get_all_published_items() - documents = [self.get_document_to_index(item) for item in items] - return documents - - def get_document_to_index(self, item): - try: - item = frappe.get_doc("Item", item) - title = item.item_name - keywords = [item.item_group] - - if item.brand: - keywords.append(item.brand) - - if item.website_image_alt: - keywords.append(item.website_image_alt) - - if item.has_variants and item.variant_based_on == "Item Attribute": - keywords = keywords + [attr.attribute for attr in item.attributes] - - if item.web_long_description: - content = strip_html_tags(item.web_long_description) - elif item.description: - content = strip_html_tags(item.description) - - return frappe._dict( - title=title, - name=item.name, - path=item.route, - content=content, - keywords=", ".join(keywords), - ) - except Exception: - pass - - def search(self, text, scope=None, limit=20): - """Search from the current index - - Args: - text (str): String to search for - scope (str, optional): Scope to limit the search. Defaults to None. - limit (int, optional): Limit number of search results. Defaults to 20. - - Returns: - [List(_dict)]: Search results - """ - ix = self.get_index() - - results = None - out = [] - - with ix.searcher() as searcher: - parser = MultifieldParser(["title", "content", "keywords"], ix.schema) - parser.remove_plugin_class(FieldsPlugin) - parser.remove_plugin_class(WildcardPlugin) - query = parser.parse(text) - - filter_scoped = None - if scope: - filter_scoped = Prefix(self.id, scope) - results = searcher.search(query, limit=limit, filter=filter_scoped) - - for r in results: - out.append(self.parse_result(r)) - - return out - - def parse_result(self, result): - title_highlights = result.highlights("title") - content_highlights = result.highlights("content") - keyword_highlights = result.highlights("keywords") - - return frappe._dict( - title=result["title"], - path=result["path"], - keywords=result["keywords"], - title_highlights=title_highlights, - content_highlights=content_highlights, - keyword_highlights=keyword_highlights, - ) - - -def get_all_published_items(): - return frappe.get_all( - "Website Item", filters={"variant_of": "", "published": 1}, pluck="item_code" - ) - - -def update_index_for_path(path): - search = ProductSearch(INDEX_NAME) - return search.update_index_by_name(path) - - -def remove_document_from_index(path): - search = ProductSearch(INDEX_NAME) - return search.remove_document_from_index(path) - - -def build_index_for_all_routes(): - search = ProductSearch(INDEX_NAME) - return search.build() diff --git a/erpnext/e_commerce/product_data_engine/filters.py b/erpnext/e_commerce/product_data_engine/filters.py deleted file mode 100644 index e5e5e97f86af..000000000000 --- a/erpnext/e_commerce/product_data_engine/filters.py +++ /dev/null @@ -1,158 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -import frappe -from frappe.utils import floor - - -class ProductFiltersBuilder: - def __init__(self, item_group=None): - if not item_group: - self.doc = frappe.get_doc("E Commerce Settings") - else: - self.doc = frappe.get_doc("Item Group", item_group) - - self.item_group = item_group - - def get_field_filters(self): - from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website - - if not self.item_group and not self.doc.enable_field_filters: - return - - fields, filter_data = [], [] - filter_fields = [row.fieldname for row in self.doc.filter_fields] # fields in settings - - # filter valid field filters i.e. those that exist in Website Item - web_item_meta = frappe.get_meta("Website Item", cached=True) - fields = [ - web_item_meta.get_field(field) for field in filter_fields if web_item_meta.has_field(field) - ] - - for df in fields: - item_filters, item_or_filters = {"published": 1}, [] - link_doctype_values = self.get_filtered_link_doctype_records(df) - - if df.fieldtype == "Link": - if self.item_group: - include_child = frappe.db.get_value("Item Group", self.item_group, "include_descendants") - if include_child: - include_groups = get_child_groups_for_website(self.item_group, include_self=True) - include_groups = [x.name for x in include_groups] - item_or_filters.extend( - [ - ["item_group", "in", include_groups], - ["Website Item Group", "item_group", "=", self.item_group], # consider website item groups - ] - ) - else: - item_or_filters.extend( - [ - ["item_group", "=", self.item_group], - ["Website Item Group", "item_group", "=", self.item_group], # consider website item groups - ] - ) - - # exclude variants if mentioned in settings - if frappe.db.get_single_value("E Commerce Settings", "hide_variants"): - item_filters["variant_of"] = ["is", "not set"] - - # Get link field values attached to published items - item_values = frappe.get_all( - "Website Item", - fields=[df.fieldname], - filters=item_filters, - or_filters=item_or_filters, - distinct="True", - pluck=df.fieldname, - ) - - values = list(set(item_values) & link_doctype_values) # intersection of both - else: - # table multiselect - values = list(link_doctype_values) - - # Remove None - if None in values: - values.remove(None) - - if values: - filter_data.append([df, values]) - - return filter_data - - def get_filtered_link_doctype_records(self, field): - """ - Get valid link doctype records depending on filters. - Apply enable/disable/show_in_website filter. - Returns: - set: A set containing valid record names - """ - link_doctype = field.get_link_doctype() - meta = frappe.get_meta(link_doctype, cached=True) if link_doctype else None - if meta: - filters = self.get_link_doctype_filters(meta) - link_doctype_values = set(d.name for d in frappe.get_all(link_doctype, filters)) - - return link_doctype_values if meta else set() - - def get_link_doctype_filters(self, meta): - "Filters for Link Doctype eg. 'show_in_website'." - filters = {} - if not meta: - return filters - - if meta.has_field("enabled"): - filters["enabled"] = 1 - if meta.has_field("disabled"): - filters["disabled"] = 0 - if meta.has_field("show_in_website"): - filters["show_in_website"] = 1 - - return filters - - def get_attribute_filters(self): - if not self.item_group and not self.doc.enable_attribute_filters: - return - - attributes = [row.attribute for row in self.doc.filter_attributes] - - if not attributes: - return [] - - result = frappe.get_all( - "Item Variant Attribute", - filters={"attribute": ["in", attributes], "attribute_value": ["is", "set"]}, - fields=["attribute", "attribute_value"], - distinct=True, - ) - - attribute_value_map = {} - for d in result: - attribute_value_map.setdefault(d.attribute, []).append(d.attribute_value) - - out = [] - for name, values in attribute_value_map.items(): - out.append(frappe._dict(name=name, item_attribute_values=values)) - return out - - def get_discount_filters(self, discounts): - discount_filters = [] - - # [25.89, 60.5] min max - 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 below" - discount_filters.append([discount, label]) - - return discount_filters diff --git a/erpnext/e_commerce/product_data_engine/query.py b/erpnext/e_commerce/product_data_engine/query.py deleted file mode 100644 index 975f87608a63..000000000000 --- a/erpnext/e_commerce/product_data_engine/query.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -from frappe.utils import flt - -from erpnext.e_commerce.doctype.item_review.item_review import get_customer -from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website -from erpnext.utilities.product import get_non_stock_item_status - - -class ProductQuery: - """Query engine for product listing - - Attributes: - fields (list): Fields to fetch in query - conditions (string): Conditions for query building - or_conditions (string): Search conditions - page_length (Int): Length of page for the query - settings (Document): E Commerce Settings DocType - """ - - def __init__(self): - self.settings = frappe.get_doc("E Commerce Settings") - self.page_length = self.settings.products_per_page or 20 - - 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", - "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): - """ - Args: - attributes (dict, optional): Item Attribute filters - fields (dict, optional): Field level filters - search_term (str, optional): Search term to lookup - start (int, optional): Page start - - Returns: - dict: Dict containing items, item count & discount range - """ - # track if discounts included in field filters - self.filter_with_discount = bool(fields.get("discount")) - result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0 - - if fields: - self.build_fields_filters(fields) - if item_group: - self.build_item_group_filters(item_group) - if search_term: - self.build_search_filters(search_term) - if self.settings.hide_variants: - self.filters.append(["variant_of", "is", "not set"]) - - # query results - if attributes: - result, count = self.query_items_with_attributes(attributes, start) - else: - result, count = self.query_items(start=start) - - # sort combined results by ranking - result = sorted(result, key=lambda x: x.get("ranking"), reverse=True) - - if self.settings.enabled: - cart_items = self.get_cart_items() - - result, discount_list = self.add_display_details(result, discount_list, cart_items) - - discounts = [] - if discount_list: - discounts = [min(discount_list), max(discount_list)] - - result = self.filter_results_by_discount(fields, result) - - return {"items": result, "items_count": count, "discounts": discounts} - - def query_items(self, start=0): - """Build a query to fetch Website Items based on field filters.""" - # MySQL does not support offset without limit, - # frappe does not accept two parameters for limit - # https://dev.mysql.com/doc/refman/8.0/en/select.html#id4651989 - count_items = frappe.db.get_all( - "Website Item", - filters=self.filters, - or_filters=self.or_filters, - limit_page_length=184467440737095516, - limit_start=start, # get all items from this offset for total count ahead - order_by="ranking desc", - ) - count = len(count_items) - - # If discounts included, return all rows. - # Slice after filtering rows with discount (See `filter_results_by_discount`). - # Slicing before hand will miss discounted items on the 3rd or 4th page. - # Discounts are fetched on computing Pricing Rules so we cannot query them directly. - page_length = 184467440737095516 if self.filter_with_discount else self.page_length - - items = frappe.db.get_all( - "Website Item", - fields=self.fields, - filters=self.filters, - or_filters=self.or_filters, - limit_page_length=page_length, - limit_start=start, - order_by="ranking desc", - ) - - return items, count - - def query_items_with_attributes(self, attributes, start=0): - """Build a query to fetch Website Items based on field & attribute filters.""" - item_codes = [] - - for attribute, values in attributes.items(): - if not isinstance(values, list): - values = [values] - - # get items that have selected attribute & value - item_code_list = frappe.db.get_all( - "Item", - fields=["item_code"], - filters=[ - ["published_in_website", "=", 1], - ["Item Variant Attribute", "attribute", "=", attribute], - ["Item Variant Attribute", "attribute_value", "in", values], - ], - ) - item_codes.append({x.item_code for x in item_code_list}) - - if item_codes: - item_codes = list(set.intersection(*item_codes)) - self.filters.append(["item_code", "in", item_codes]) - - items, count = self.query_items(start=start) - - return items, count - - def build_fields_filters(self, filters): - """Build filters for field values - - Args: - filters (dict): Filters - """ - for field, values in filters.items(): - if not values or field == "discount": - continue - - # handle multiselect fields in filter addition - meta = frappe.get_meta("Website Item", cached=True) - df = meta.get_field(field) - if df.fieldtype == "Table MultiSelect": - child_doctype = df.options - child_meta = frappe.get_meta(child_doctype, cached=True) - fields = child_meta.get("fields") - if fields: - self.filters.append([child_doctype, fields[0].fieldname, "IN", values]) - elif isinstance(values, list): - # If value is a list use `IN` query - self.filters.append([field, "in", values]) - else: - # `=` will be faster than `IN` for most cases - self.filters.append([field, "=", values]) - - def build_item_group_filters(self, item_group): - "Add filters for Item group page and include Website Item Groups." - from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website - - item_group_filters = [] - - item_group_filters.append(["Website Item", "item_group", "=", item_group]) - # Consider Website Item Groups - item_group_filters.append(["Website Item Group", "item_group", "=", item_group]) - - if frappe.db.get_value("Item Group", item_group, "include_descendants"): - # include child item group's items as well - # eg. Group Node A, will show items of child 1 and child 2 as well - # on it's web page - include_groups = get_child_groups_for_website(item_group, include_self=True) - include_groups = [x.name for x in include_groups] - item_group_filters.append(["Website Item", "item_group", "in", include_groups]) - - self.or_filters.extend(item_group_filters) - - def build_search_filters(self, search_term): - """Query search term in specified fields - - Args: - search_term (str): Search candidate - """ - # Default fields to search from - default_fields = {"item_code", "item_name", "web_long_description", "item_group"} - - # Get meta search fields - meta = frappe.get_meta("Website Item") - meta_fields = set(meta.get_search_fields()) - - # Join the meta fields and default fields set - search_fields = default_fields.union(meta_fields) - if frappe.db.count("Website Item", cache=True) > 50000: - search_fields.discard("web_long_description") - - # Build or filters for query - search = "%{}%".format(search_term) - for field in search_fields: - self.or_filters.append([field, "like", search]) - - def add_display_details(self, result, discount_list, cart_items): - """Add price and availability details in result.""" - for item in result: - product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get( - "product_info" - ) - - if product_info and product_info["price"]: - # update/mutate item and discount_list objects - self.get_price_discount_info(item, product_info["price"], discount_list) - - if self.settings.show_stock_availability: - self.get_stock_availability(item) - - item.in_cart = item.item_code in cart_items - - item.wished = False - if frappe.db.exists( - "Wishlist Item", {"item_code": item.item_code, "parent": frappe.session.user} - ): - item.wished = True - - return result, discount_list - - def get_price_discount_info(self, item, price_object, discount_list): - """Modify item object and add price details.""" - fields = ["formatted_mrp", "formatted_price", "price_list_rate"] - for field in fields: - item[field] = price_object.get(field) - - if price_object.get("discount_percent"): - item.discount_percent = flt(price_object.discount_percent) - discount_list.append(price_object.discount_percent) - - if item.formatted_mrp: - item.discount = price_object.get("formatted_discount_percent") or price_object.get( - "formatted_discount_rate" - ) - - def get_stock_availability(self, item): - from erpnext.templates.pages.wishlist import ( - get_stock_availability as get_stock_availability_from_template, - ) - - """Modify item object and add stock details.""" - item.in_stock = False - 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 - item.in_stock = get_non_stock_item_status(item.item_code, "website_warehouse") - else: - item.in_stock = True - elif warehouse: - item.in_stock = get_stock_availability_from_template(item.item_code, warehouse) - - def get_cart_items(self): - customer = get_customer(silent=True) - if customer: - quotation = frappe.get_all( - "Quotation", - fields=["name"], - filters={ - "party_name": customer, - "contact_email": frappe.session.user, - "order_type": "Shopping Cart", - "docstatus": 0, - }, - order_by="modified desc", - limit_page_length=1, - ) - if quotation: - items = frappe.get_all( - "Quotation Item", fields=["item_code"], filters={"parent": quotation[0].get("name")} - ) - items = [row.item_code for row in items] - return items - - return [] - - def filter_results_by_discount(self, fields, result): - if fields and fields.get("discount"): - discount_percent = frappe.utils.flt(fields["discount"][0]) - result = [ - row - for row in result - if row.get("discount_percent") and row.discount_percent <= discount_percent - ] - - if self.filter_with_discount: - # no limit was added to results while querying - # slice results manually - result[: self.page_length] - - return result diff --git a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py deleted file mode 100644 index 45bc20ece6e0..000000000000 --- a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import unittest - -import frappe - -from erpnext.e_commerce.api import get_product_filter_data -from erpnext.e_commerce.doctype.website_item.test_website_item import create_regular_web_item - -test_dependencies = ["Item", "Item Group"] - - -class TestItemGroupProductDataEngine(unittest.TestCase): - "Test Products & Sub-Category Querying for Product Listing on Item Group Page." - - def setUp(self): - item_codes = [ - ("Test Mobile A", "_Test Item Group B"), - ("Test Mobile B", "_Test Item Group B"), - ("Test Mobile C", "_Test Item Group B - 1"), - ("Test Mobile D", "_Test Item Group B - 1"), - ("Test Mobile E", "_Test Item Group B - 2"), - ] - for item in item_codes: - item_code = item[0] - item_args = {"item_group": item[1]} - if not frappe.db.exists("Website Item", {"item_code": item_code}): - create_regular_web_item(item_code, item_args=item_args) - - frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1) - frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1) - - def tearDown(self): - frappe.db.rollback() - - def test_product_listing_in_item_group(self): - "Test if only products belonging to the Item Group are fetched." - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B", - } - ) - - items = result.get("items") - item_codes = [item.get("item_code") for item in items] - - self.assertEqual(len(items), 2) - self.assertIn("Test Mobile A", item_codes) - self.assertNotIn("Test Mobile C", item_codes) - - def test_products_in_multiple_item_groups(self): - """Test if product is visible on multiple item group pages barring its own.""" - website_item = frappe.get_doc("Website Item", {"item_code": "Test Mobile E"}) - - # show item belonging to '_Test Item Group B - 2' in '_Test Item Group B - 1' as well - website_item.append("website_item_groups", {"item_group": "_Test Item Group B - 1"}) - website_item.save() - - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B - 1", - } - ) - - items = result.get("items") - item_codes = [item.get("item_code") for item in items] - - self.assertEqual(len(items), 3) - self.assertIn("Test Mobile E", item_codes) # visible in other item groups - self.assertIn("Test Mobile C", item_codes) - self.assertIn("Test Mobile D", item_codes) - - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B - 2", - } - ) - - items = result.get("items") - - self.assertEqual(len(items), 1) - self.assertEqual(items[0].get("item_code"), "Test Mobile E") # visible in own item group - - def test_item_group_with_sub_groups(self): - "Test Valid Sub Item Groups in Item Group Page." - frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0) - - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B", - } - ) - - self.assertTrue(bool(result.get("sub_categories"))) - - child_groups = [d.name for d in result.get("sub_categories")] - # check if child group is fetched if shown in website - self.assertIn("_Test Item Group B - 1", child_groups) - - frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1) - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B", - } - ) - child_groups = [d.name for d in result.get("sub_categories")] - - # check if child group is fetched if shown in website - self.assertIn("_Test Item Group B - 1", child_groups) - self.assertIn("_Test Item Group B - 2", child_groups) - - def test_item_group_page_with_descendants_included(self): - """ - Test if 'include_descendants' pulls Items belonging to descendant Item Groups (Level 2 & 3). - > _Test Item Group B [Level 1] - > _Test Item Group B - 1 [Level 2] - > _Test Item Group B - 1 - 1 [Level 3] - """ - frappe.get_doc( - { # create Level 3 nested child group - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B - 1 - 1", - "parent_item_group": "_Test Item Group B - 1", - } - ).insert() - - create_regular_web_item( # create an item belonging to level 3 item group - "Test Mobile F", item_args={"item_group": "_Test Item Group B - 1 - 1"} - ) - - frappe.db.set_value("Item Group", "_Test Item Group B - 1 - 1", "show_in_website", 1) - - # enable 'include descendants' in Level 1 - frappe.db.set_value("Item Group", "_Test Item Group B", "include_descendants", 1) - - result = get_product_filter_data( - query_args={ - "field_filters": {}, - "attribute_filters": {}, - "start": 0, - "item_group": "_Test Item Group B", - } - ) - - items = result.get("items") - item_codes = [item.get("item_code") for item in items] - - # check if all sub groups' items are pulled - self.assertEqual(len(items), 6) - self.assertIn("Test Mobile A", item_codes) - self.assertIn("Test Mobile C", item_codes) - self.assertIn("Test Mobile E", item_codes) - self.assertIn("Test Mobile F", item_codes) 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 deleted file mode 100644 index c3b6ed5da253..000000000000 --- a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py +++ /dev/null @@ -1,348 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import unittest - -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import ( - setup_e_commerce_settings, -) -from erpnext.e_commerce.doctype.website_item.test_website_item import create_regular_web_item -from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder -from erpnext.e_commerce.product_data_engine.query import ProductQuery - -test_dependencies = ["Item", "Item Group"] - - -class TestProductDataEngine(unittest.TestCase): - "Test Products Querying and Filters for Product Listing." - - @classmethod - def setUpClass(cls): - item_codes = [ - ("Test 11I Laptop", "Products"), # rank 1 - ("Test 12I Laptop", "Products"), # rank 2 - ("Test 13I Laptop", "Products"), # rank 3 - ("Test 14I Laptop", "Raw Material"), # rank 4 - ("Test 15I Laptop", "Raw Material"), # rank 5 - ("Test 16I Laptop", "Raw Material"), # rank 6 - ("Test 17I Laptop", "Products"), # rank 7 - ] - for index, item in enumerate(item_codes, start=1): - item_code = item[0] - item_args = {"item_group": item[1]} - web_args = {"ranking": index} - if not frappe.db.exists("Website Item", {"item_code": item_code}): - create_regular_web_item(item_code, item_args=item_args, web_args=web_args) - - setup_e_commerce_settings( - { - "products_per_page": 4, - "enable_field_filters": 1, - "filter_fields": [{"fieldname": "item_group"}], - "enable_attribute_filters": 1, - "filter_attributes": [{"attribute": "Test Size"}], - "company": "_Test Company", - "enabled": 1, - "default_customer_group": "_Test Customer Group", - "price_list": "_Test Price List India", - } - ) - frappe.local.shopping_cart_settings = None - - @classmethod - def tearDownClass(cls): - frappe.db.rollback() - - def test_product_list_ordering_and_paging(self): - "Test if website items appear by ranking on different pages." - engine = ProductQuery() - result = engine.query(attributes={}, fields={}, search_term=None, start=0, item_group=None) - items = result.get("items") - - self.assertIsNotNone(items) - self.assertEqual(len(items), 4) - self.assertGreater(result.get("items_count"), 4) - - # check if items appear as per ranking set in setUpClass - self.assertEqual(items[0].get("item_code"), "Test 17I Laptop") - self.assertEqual(items[1].get("item_code"), "Test 16I Laptop") - self.assertEqual(items[2].get("item_code"), "Test 15I Laptop") - self.assertEqual(items[3].get("item_code"), "Test 14I Laptop") - - # check next page - result = engine.query(attributes={}, fields={}, search_term=None, start=4, item_group=None) - items = result.get("items") - - # check if items appear as per ranking set in setUpClass on next page - self.assertEqual(items[0].get("item_code"), "Test 13I Laptop") - self.assertEqual(items[1].get("item_code"), "Test 12I Laptop") - self.assertEqual(items[2].get("item_code"), "Test 11I Laptop") - - def test_change_product_ranking(self): - "Test if item on second page appear on first if ranking is changed." - item_code = "Test 12I Laptop" - old_ranking = frappe.db.get_value("Website Item", {"item_code": item_code}, "ranking") - - # low rank, appears on second page - self.assertEqual(old_ranking, 2) - - # set ranking as highest rank - frappe.db.set_value("Website Item", {"item_code": item_code}, "ranking", 10) - - engine = ProductQuery() - result = engine.query(attributes={}, fields={}, search_term=None, start=0, item_group=None) - items = result.get("items") - - # check if item is the first item on the first page - self.assertEqual(items[0].get("item_code"), item_code) - self.assertEqual(items[1].get("item_code"), "Test 17I Laptop") - - # tear down - frappe.db.set_value("Website Item", {"item_code": item_code}, "ranking", old_ranking) - - def test_product_list_field_filter_builder(self): - "Test if field filters are fetched correctly." - frappe.db.set_value("Item Group", "Raw Material", "show_in_website", 0) - - filter_engine = ProductFiltersBuilder() - field_filters = filter_engine.get_field_filters() - - # Web Items belonging to 'Products' and 'Raw Material' are available - # but only 'Products' has 'show_in_website' enabled - item_group_filters = field_filters[0] - docfield = item_group_filters[0] - valid_item_groups = item_group_filters[1] - - self.assertEqual(docfield.options, "Item Group") - self.assertIn("Products", valid_item_groups) - self.assertNotIn("Raw Material", valid_item_groups) - - frappe.db.set_value("Item Group", "Raw Material", "show_in_website", 1) - field_filters = filter_engine.get_field_filters() - - #'Products' and 'Raw Materials' both have 'show_in_website' enabled - item_group_filters = field_filters[0] - docfield = item_group_filters[0] - valid_item_groups = item_group_filters[1] - - self.assertEqual(docfield.options, "Item Group") - self.assertIn("Products", valid_item_groups) - self.assertIn("Raw Material", valid_item_groups) - - def test_product_list_with_field_filter(self): - "Test if field filters are applied correctly." - field_filters = {"item_group": "Raw Material"} - - engine = ProductQuery() - result = engine.query( - attributes={}, fields=field_filters, search_term=None, start=0, item_group=None - ) - items = result.get("items") - - # check if only 'Raw Material' are fetched in the right order - self.assertEqual(len(items), 3) - self.assertEqual(items[0].get("item_code"), "Test 16I Laptop") - self.assertEqual(items[1].get("item_code"), "Test 15I Laptop") - - # def test_product_list_with_field_filter_table_multiselect(self): - # TODO - # pass - - def test_product_list_attribute_filter_builder(self): - "Test if attribute filters are fetched correctly." - create_variant_web_item() - - filter_engine = ProductFiltersBuilder() - attribute_filter = filter_engine.get_attribute_filters()[0] - attribute_values = attribute_filter.item_attribute_values - - self.assertEqual(attribute_filter.name, "Test Size") - self.assertGreater(len(attribute_values), 0) - self.assertIn("Large", attribute_values) - - def test_product_list_with_attribute_filter(self): - "Test if attribute filters are applied correctly." - create_variant_web_item() - - attribute_filters = {"Test Size": ["Large"]} - engine = ProductQuery() - result = engine.query( - attributes=attribute_filters, fields={}, search_term=None, start=0, item_group=None - ) - items = result.get("items") - - # check if only items with Test Size 'Large' are fetched - self.assertEqual(len(items), 1) - self.assertEqual(items[0].get("item_code"), "Test Web Item-L") - - def test_product_list_discount_filter_builder(self): - "Test if discount filters are fetched correctly." - from erpnext.e_commerce.doctype.website_item.test_website_item import ( - make_web_item_price, - make_web_pricing_rule, - ) - - item_code = "Test 12I Laptop" - make_web_item_price(item_code=item_code) - make_web_pricing_rule(title=f"Test Pricing Rule for {item_code}", item_code=item_code, selling=1) - - setup_e_commerce_settings({"show_price": 1}) - frappe.local.shopping_cart_settings = None - - engine = ProductQuery() - result = engine.query(attributes={}, fields={}, search_term=None, start=4, item_group=None) - self.assertTrue(bool(result.get("discounts"))) - - filter_engine = ProductFiltersBuilder() - discount_filters = filter_engine.get_discount_filters(result["discounts"]) - - self.assertEqual(len(discount_filters[0]), 2) - self.assertEqual(discount_filters[0][0], 10) - self.assertEqual(discount_filters[0][1], "10% and below") - - def test_product_list_with_discount_filters(self): - "Test if discount filters are applied correctly." - from erpnext.e_commerce.doctype.website_item.test_website_item import ( - make_web_item_price, - make_web_pricing_rule, - ) - - field_filters = {"discount": [10]} - - make_web_item_price(item_code="Test 12I Laptop") - make_web_pricing_rule( - title="Test Pricing Rule for Test 12I Laptop", # 10% discount - item_code="Test 12I Laptop", - selling=1, - ) - make_web_item_price(item_code="Test 13I Laptop") - make_web_pricing_rule( - title="Test Pricing Rule for Test 13I Laptop", # 15% discount - item_code="Test 13I Laptop", - discount_percentage=15, - selling=1, - ) - - setup_e_commerce_settings({"show_price": 1}) - frappe.local.shopping_cart_settings = None - - engine = ProductQuery() - result = engine.query( - attributes={}, fields=field_filters, search_term=None, start=0, item_group=None - ) - items = result.get("items") - - # check if only product with 10% and below discount are fetched - self.assertEqual(len(items), 1) - self.assertEqual(items[0].get("item_code"), "Test 12I Laptop") - - def test_product_list_with_api(self): - "Test products listing using API." - from erpnext.e_commerce.api import get_product_filter_data - - create_variant_web_item() - - result = get_product_filter_data( - query_args={ - "field_filters": {"item_group": "Products"}, - "attribute_filters": {"Test Size": ["Large"]}, - "start": 0, - } - ) - - items = result.get("items") - - self.assertEqual(len(items), 1) - self.assertEqual(items[0].get("item_code"), "Test Web Item-L") - - def test_product_list_with_variants(self): - "Test if variants are hideen on hiding variants in settings." - create_variant_web_item() - - setup_e_commerce_settings({"enable_attribute_filters": 0, "hide_variants": 1}) - frappe.local.shopping_cart_settings = None - - attribute_filters = {"Test Size": ["Large"]} - engine = ProductQuery() - result = engine.query( - attributes=attribute_filters, fields={}, search_term=None, start=0, item_group=None - ) - items = result.get("items") - - # check if any variants are fetched even though published variant exists - self.assertEqual(len(items), 0) - - # tear down - setup_e_commerce_settings({"enable_attribute_filters": 1, "hide_variants": 0}) - - def test_custom_field_as_filter(self): - "Test if custom field functions as filter correctly." - from frappe.custom.doctype.custom_field.custom_field import create_custom_field - - create_custom_field( - "Website Item", - dict( - owner="Administrator", - fieldname="supplier", - label="Supplier", - fieldtype="Link", - options="Supplier", - insert_after="on_backorder", - ), - ) - - frappe.db.set_value( - "Website Item", {"item_code": "Test 11I Laptop"}, "supplier", "_Test Supplier" - ) - frappe.db.set_value( - "Website Item", {"item_code": "Test 12I Laptop"}, "supplier", "_Test Supplier 1" - ) - - settings = frappe.get_doc("E Commerce Settings") - settings.append("filter_fields", {"fieldname": "supplier"}) - settings.save() - - filter_engine = ProductFiltersBuilder() - field_filters = filter_engine.get_field_filters() - custom_filter = field_filters[1] - filter_values = custom_filter[1] - - self.assertEqual(custom_filter[0].options, "Supplier") - self.assertEqual(len(filter_values), 2) - self.assertIn("_Test Supplier", filter_values) - - # test if custom filter works in query - field_filters = {"supplier": "_Test Supplier 1"} - engine = ProductQuery() - result = engine.query( - attributes={}, fields=field_filters, search_term=None, start=0, item_group=None - ) - items = result.get("items") - - # check if only 'Raw Material' are fetched in the right order - self.assertEqual(len(items), 1) - self.assertEqual(items[0].get("item_code"), "Test 12I Laptop") - - -def create_variant_web_item(): - "Create Variant and Template Website Items." - from erpnext.controllers.item_variant import create_variant - from erpnext.e_commerce.doctype.website_item.website_item import make_website_item - from erpnext.stock.doctype.item.test_item import make_item - - make_item( - "Test Web Item", - { - "has_variant": 1, - "variant_based_on": "Item Attribute", - "attributes": [{"attribute": "Test Size"}], - }, - ) - if not frappe.db.exists("Item", "Test Web Item-L"): - variant = create_variant("Test Web Item", {"Test Size": "Large"}) - variant.save() - - if not frappe.db.exists("Website Item", {"variant_of": "Test Web Item"}): - make_website_item(variant, save=True) diff --git a/erpnext/e_commerce/product_ui/grid.js b/erpnext/e_commerce/product_ui/grid.js deleted file mode 100644 index 20a6c30b52bc..000000000000 --- a/erpnext/e_commerce/product_ui/grid.js +++ /dev/null @@ -1,201 +0,0 @@ -erpnext.ProductGrid = class { - /* Options: - - items: Items - - settings: E Commerce Settings - - products_section: Products Wrapper - - preference: If preference is not grid view, render but hide - */ - constructor(options) { - Object.assign(this, options); - - if (this.preference !== "Grid View") { - this.products_section.addClass("hidden"); - } - - this.products_section.empty(); - this.make(); - } - - make() { - let me = this; - let html = ``; - - this.items.forEach(item => { - let title = item.web_item_name || item.item_name || item.item_code || ""; - title = title.length > 90 ? title.substr(0, 90) + "..." : title; - - html += `
`; - html += me.get_image_html(item, title); - html += me.get_card_body_html(item, title, me.settings); - html += `
`; - }); - - let $product_wrapper = this.products_section; - $product_wrapper.append(html); - } - - get_image_html(item, title) { - let image = item.website_image; - - if (image) { - return ` -
- - ${ title } - -
- `; - } else { - return ` -
- -
- ${ frappe.get_abbr(title) } -
-
-
- `; - } - } - - get_card_body_html(item, title, settings) { - let body_html = ` -
-
- `; - body_html += this.get_title(item, title); - - // get floating elements - if (!item.has_variants) { - if (settings.enable_wishlist) { - body_html += this.get_wishlist_icon(item); - } - if (settings.enabled) { - body_html += this.get_cart_indicator(item); - } - - } - - body_html += `
`; - body_html += `
${ item.item_group || '' }
`; - - if (item.formatted_price) { - body_html += this.get_price_html(item); - } - - body_html += this.get_stock_availability(item, settings); - body_html += this.get_primary_button(item, settings); - body_html += `
`; // close div on line 49 - - return body_html; - } - - get_title(item, title) { - let title_html = ` - -
- ${ title || '' } -
-
- `; - return title_html; - } - - get_wishlist_icon(item) { - let icon_class = item.wished ? "wished" : "not-wished"; - return ` -
- - - -
- `; - } - - get_cart_indicator(item) { - return ` -
- 1 -
- `; - } - - get_price_html(item) { - let price_html = ` -
- ${ item.formatted_price || '' } - `; - - if (item.formatted_mrp) { - price_html += ` - - ${ item.formatted_mrp ? item.formatted_mrp.replace(/ +/g, "") : "" } - - - ${ item.discount } OFF - - `; - } - price_html += `
`; - return price_html; - } - - get_stock_availability(item, settings) { - 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 ``; - } - - get_primary_button(item, settings) { - if (item.has_variants) { - return ` - -
- ${ __('Explore') } -
-
- `; - } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) { - return ` -
- - - - - - ${ settings.enable_checkout ? __('Add to Cart') : __('Add to Quote') } -
- - -
- ${ settings.enable_checkout ? __('Go to Cart') : __('Go to Quote') } -
-
- `; - } else { - return ``; - } - } -}; \ No newline at end of file diff --git a/erpnext/e_commerce/product_ui/list.js b/erpnext/e_commerce/product_ui/list.js deleted file mode 100644 index c8fd7672c8e7..000000000000 --- a/erpnext/e_commerce/product_ui/list.js +++ /dev/null @@ -1,205 +0,0 @@ -erpnext.ProductList = class { - /* Options: - - items: Items - - settings: E Commerce Settings - - products_section: Products Wrapper - - preference: If preference is not list view, render but hide - */ - constructor(options) { - Object.assign(this, options); - - if (this.preference !== "List View") { - this.products_section.addClass("hidden"); - } - - this.products_section.empty(); - this.make(); - } - - make() { - let me = this; - let html = `

`; - - this.items.forEach(item => { - let title = item.web_item_name || item.item_name || item.item_code || ""; - title = title.length > 200 ? title.substr(0, 200) + "..." : title; - - html += `
`; - html += me.get_image_html(item, title, me.settings); - html += me.get_row_body_html(item, title, me.settings); - html += `
`; - }); - - let $product_wrapper = this.products_section; - $product_wrapper.append(html); - } - - get_image_html(item, title, settings) { - let image = item.website_image; - let wishlist_enabled = !item.has_variants && settings.enable_wishlist; - let image_html = ``; - - if (image) { - image_html += ` -
- - ${ title } - - ${ wishlist_enabled ? this.get_wishlist_icon(item): '' } -
- `; - } else { - image_html += ` -
- -
- ${ frappe.get_abbr(title) } -
-
- ${ wishlist_enabled ? this.get_wishlist_icon(item): '' } -
- `; - } - - return image_html; - } - - get_row_body_html(item, title, settings) { - let body_html = `
`; - body_html += this.get_title_html(item, title, settings); - body_html += this.get_item_details(item, settings); - body_html += `
`; - return body_html; - } - - get_title_html(item, title, settings) { - let title_html = `
`; - title_html += ` -
- -
- ${ title } -
-
-
- `; - - if (settings.enabled) { - title_html += `
`; - title_html += this.get_primary_button(item, settings); - title_html += `
`; - } - title_html += `
`; - - return title_html; - } - - get_item_details(item, settings) { - let details = ` -

- ${ item.item_group } | Item Code : ${ item.item_code } -

-
- ${ item.short_description || '' } -
-
- ${ item.formatted_price || '' } - `; - - if (item.formatted_mrp) { - details += ` - - ${ item.formatted_mrp ? item.formatted_mrp.replace(/ +/g, "") : "" } - - - ${ item.discount } OFF - - `; - } - - details += this.get_stock_availability(item, settings); - details += `
`; - - return details; - } - - get_stock_availability(item, settings) { - 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 ``; - } - - get_wishlist_icon(item) { - let icon_class = item.wished ? "wished" : "not-wished"; - - return ` -
- - - -
- `; - } - - get_primary_button(item, settings) { - if (item.has_variants) { - return ` - -
- ${ __('Explore') } -
-
- `; - } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) { - return ` -
- - - - - - ${ settings.enable_checkout ? __('Add to Cart') : __('Add to Quote') } -
- -
- 1 -
- - -
- ${ settings.enable_checkout ? __('Go to Cart') : __('Go to Quote') } -
-
- `; - } else { - return ``; - } - } - -}; diff --git a/erpnext/e_commerce/product_ui/search.js b/erpnext/e_commerce/product_ui/search.js deleted file mode 100644 index 1688cc1fb667..000000000000 --- a/erpnext/e_commerce/product_ui/search.js +++ /dev/null @@ -1,244 +0,0 @@ -erpnext.ProductSearch = class { - constructor(opts) { - /* Options: search_box_id (for custom search box) */ - $.extend(this, opts); - this.MAX_RECENT_SEARCHES = 4; - this.search_box_id = this.search_box_id || "#search-box"; - this.searchBox = $(this.search_box_id); - - this.setupSearchDropDown(); - this.bindSearchAction(); - } - - setupSearchDropDown() { - this.search_area = $("#dropdownMenuSearch"); - this.setupSearchResultContainer(); - this.populateRecentSearches(); - } - - bindSearchAction() { - let me = this; - - // Show Search dropdown - this.searchBox.on("focus", () => { - this.search_dropdown.removeClass("hidden"); - }); - - // If click occurs outside search input/results, hide results. - // Click can happen anywhere on the page - $("body").on("click", (e) => { - let searchEvent = $(e.target).closest(this.search_box_id).length; - let resultsEvent = $(e.target).closest('#search-results-container').length; - let isResultHidden = this.search_dropdown.hasClass("hidden"); - - if (!searchEvent && !resultsEvent && !isResultHidden) { - this.search_dropdown.addClass("hidden"); - } - }); - - // Process search input - this.searchBox.on("input", (e) => { - let query = e.target.value; - - if (query.length == 0) { - me.populateResults(null); - me.populateCategoriesList(null); - } - - if (query.length < 3 || !query.length) return; - - frappe.call({ - method: "erpnext.templates.pages.product_search.search", - args: { - query: query - }, - callback: (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); - } - } - }); - - this.search_dropdown.removeClass("hidden"); - }); - } - - setupSearchResultContainer() { - this.search_dropdown = this.search_area.append(` - - `).find("#search-results-container"); - - this.setupCategoryContainer(); - this.setupProductsContainer(); - this.setupRecentsContainer(); - } - - setupProductsContainer() { - this.products_container = this.search_dropdown.append(` -
-
-
-
- `).find("#product-scroll"); - } - - setupCategoryContainer() { - this.category_container = this.search_dropdown.append(` -
-
-
-
- `).find(".category-chips"); - } - - setupRecentsContainer() { - let $recents_section = this.search_dropdown.append(` -
-
- ${ __("Recent") } -
-
- `).find(".recent-searches"); - - this.recents_container = $recents_section.append(` -
-
- `).find("#recents"); - } - - getRecentSearches() { - return JSON.parse(localStorage.getItem("recent_searches") || "[]"); - } - - attachEventListenersToChips() { - let me = this; - const chips = $(".recent-search"); - window.chips = chips; - - for (let chip of chips) { - chip.addEventListener("click", () => { - me.searchBox[0].value = chip.innerText.trim(); - - // Start search with `recent query` - me.searchBox.trigger("input"); - me.searchBox.focus(); - }); - } - } - - setRecentSearches(query) { - let recents = this.getRecentSearches(); - if (recents.length >= this.MAX_RECENT_SEARCHES) { - // Remove the `first` query - recents.splice(0, 1); - } - - if (recents.indexOf(query) >= 0) { - return; - } - - recents.push(query); - localStorage.setItem("recent_searches", JSON.stringify(recents)); - - this.populateRecentSearches(); - } - - populateRecentSearches() { - let recents = this.getRecentSearches(); - - if (!recents.length) { - this.recents_container.html(`No searches yet.`); - return; - } - - let html = ""; - recents.forEach((key) => { - html += ` - - `; - }); - - this.recents_container.html(html); - this.attachEventListenersToChips(); - } - - populateResults(product_results) { - if (!product_results || product_results.length === 0) { - let empty_html = ``; - this.products_container.html(empty_html); - return; - } - - let html = ""; - - product_results.forEach((res) => { - let thumbnail = res.thumbnail || '/assets/erpnext/images/ui-states/cart-empty-state.png'; - html += ` - - `; - }); - - this.products_container.html(html); - } - - populateCategoriesList(category_results) { - if (!category_results || category_results.length === 0) { - let empty_html = ` -
-
-
-
- `; - this.category_container.html(empty_html); - return; - } - - let html = ` -
- ${ __("Categories") } -
- `; - - category_results.forEach((category) => { - html += ` - - ${ category.name } - - `; - }); - - this.category_container.html(html); - } -}; diff --git a/erpnext/e_commerce/product_ui/views.js b/erpnext/e_commerce/product_ui/views.js deleted file mode 100644 index fb63b21a0837..000000000000 --- a/erpnext/e_commerce/product_ui/views.js +++ /dev/null @@ -1,548 +0,0 @@ -erpnext.ProductView = class { - /* Options: - - View Type - - Products Section Wrapper, - - Item Group: If its an Item Group page - */ - constructor(options) { - Object.assign(this, options); - this.preference = this.view_type; - this.make(); - } - - make(from_filters=false) { - this.products_section.empty(); - this.prepare_toolbar(); - this.get_item_filter_data(from_filters); - } - - prepare_toolbar() { - this.products_section.append(` -
-
- `); - this.prepare_search(); - this.prepare_view_toggler(); - - new erpnext.ProductSearch(); - } - - prepare_view_toggler() { - - if (!$("#list").length || !$("#image-view").length) { - this.render_view_toggler(); - this.bind_view_toggler_actions(); - this.set_view_state(); - } - } - - get_item_filter_data(from_filters=false) { - // Get and render all Product related views - let me = this; - this.from_filters = from_filters; - let args = this.get_query_filters(); - - this.disable_view_toggler(true); - - frappe.call({ - method: "erpnext.e_commerce.api.get_product_filter_data", - args: { - query_args: args - }, - callback: function(result) { - if (!result || result.exc || !result.message || result.message.exc) { - me.render_no_products_section(true); - } else { - // Sub Category results are independent of Items - if (me.item_group && result.message["sub_categories"].length) { - me.render_item_sub_categories(result.message["sub_categories"]); - } - - if (!result.message["items"].length) { - // if result has no items or result is empty - me.render_no_products_section(); - } else { - // Add discount filters - me.re_render_discount_filters(result.message["filters"].discount_filters); - - // Render views - me.render_list_view(result.message["items"], result.message["settings"]); - me.render_grid_view(result.message["items"], result.message["settings"]); - - me.products = result.message["items"]; - me.product_count = result.message["items_count"]; - } - - // Bind filter actions - if (!from_filters) { - // If `get_product_filter_data` was triggered after checking a filter, - // don't touch filters unnecessarily, only data must change - // filter persistence is handle on filter change event - me.bind_filters(); - me.restore_filters_state(); - } - - // Bottom paging - me.add_paging_section(result.message["settings"]); - } - - me.disable_view_toggler(false); - } - }); - } - - disable_view_toggler(disable=false) { - $('#list').prop('disabled', disable); - $('#image-view').prop('disabled', disable); - } - - render_grid_view(items, settings) { - // loop over data and add grid html to it - let me = this; - this.prepare_product_area_wrapper("grid"); - - new erpnext.ProductGrid({ - items: items, - products_section: $("#products-grid-area"), - settings: settings, - preference: me.preference - }); - } - - render_list_view(items, settings) { - let me = this; - this.prepare_product_area_wrapper("list"); - - new erpnext.ProductList({ - items: items, - products_section: $("#products-list-area"), - settings: settings, - preference: me.preference - }); - } - - prepare_product_area_wrapper(view) { - let left_margin = view == "list" ? "ml-2" : ""; - let top_margin = view == "list" ? "mt-6" : "mt-minus-1"; - return this.products_section.append(` -
-
- `); - } - - get_query_filters() { - const filters = frappe.utils.get_query_params(); - let {field_filters, attribute_filters} = filters; - - field_filters = field_filters ? JSON.parse(field_filters) : {}; - attribute_filters = attribute_filters ? JSON.parse(attribute_filters) : {}; - - return { - field_filters: field_filters, - attribute_filters: attribute_filters, - item_group: this.item_group, - start: filters.start || null, - from_filters: this.from_filters || false - }; - } - - add_paging_section(settings) { - $(".product-paging-area").remove(); - - if (this.products) { - let paging_html = ` -
-
-
-
- `; - let query_params = frappe.utils.get_query_params(); - let start = query_params.start ? cint(JSON.parse(query_params.start)) : 0; - let page_length = settings.products_per_page || 0; - - let prev_disable = start > 0 ? "" : "disabled"; - let next_disable = (this.product_count > page_length) ? "" : "disabled"; - - paging_html += ` - `; - - paging_html += ` - - `; - - paging_html += `
`; - - $(".page_content").append(paging_html); - this.bind_paging_action(); - } - } - - prepare_search() { - $(".toolbar").append(` -
- -
- `); - } - - render_view_toggler() { - $(".toolbar").append(`
`); - - ["btn-list-view", "btn-grid-view"].forEach(view => { - let icon = view === "btn-list-view" ? "list" : "image-view"; - $(".toggle-container").append(` -
- -
- `); - }); - } - - bind_view_toggler_actions() { - $("#list").click(function() { - let $btn = $(this); - $btn.removeClass('btn-primary'); - $btn.addClass('btn-primary'); - $(".btn-grid-view").removeClass('btn-primary'); - - $("#products-grid-area").addClass("hidden"); - $("#products-list-area").removeClass("hidden"); - localStorage.setItem("product_view", "List View"); - }); - - $("#image-view").click(function() { - let $btn = $(this); - $btn.removeClass('btn-primary'); - $btn.addClass('btn-primary'); - $(".btn-list-view").removeClass('btn-primary'); - - $("#products-list-area").addClass("hidden"); - $("#products-grid-area").removeClass("hidden"); - localStorage.setItem("product_view", "Grid View"); - }); - } - - set_view_state() { - if (this.preference === "List View") { - $("#list").addClass('btn-primary'); - $("#image-view").removeClass('btn-primary'); - } else { - $("#image-view").addClass('btn-primary'); - $("#list").removeClass('btn-primary'); - } - } - - bind_paging_action() { - let me = this; - $('.btn-prev, .btn-next').click((e) => { - const $btn = $(e.target); - me.from_filters = false; - - $btn.prop('disabled', true); - const start = $btn.data('start'); - - let query_params = frappe.utils.get_query_params(); - query_params.start = start; - let path = window.location.pathname + '?' + frappe.utils.get_url_from_dict(query_params); - window.location.href = path; - }); - } - - re_render_discount_filters(filter_data) { - this.get_discount_filter_html(filter_data); - if (this.from_filters) { - // Bind filter action if triggered via filters - // if not from filter action, page load will bind actions - this.bind_discount_filter_action(); - } - // discount filters are rendered with Items (later) - // unlike the other filters - this.restore_discount_filter(); - } - - get_discount_filter_html(filter_data) { - $("#discount-filters").remove(); - if (filter_data) { - $("#product-filters").append(` -
-
${ __("Discounts") }
-
- `); - - let html = `
`; - filter_data.forEach(filter => { - html += ` -
- -
- `; - }); - html += `
`; - - $("#discount-filters").append(html); - } - } - - restore_discount_filter() { - const filters = frappe.utils.get_query_params(); - let field_filters = filters.field_filters; - if (!field_filters) return; - - field_filters = JSON.parse(field_filters); - - if (field_filters && field_filters["discount"]) { - const values = field_filters["discount"]; - const selector = values.map(value => { - return `input[data-filter-name="discount"][data-filter-value="${value}"]`; - }).join(','); - $(selector).prop('checked', true); - this.field_filters = field_filters; - } - } - - bind_discount_filter_action() { - let me = this; - $('.discount-filter').on('change', (e) => { - const $checkbox = $(e.target); - const is_checked = $checkbox.is(':checked'); - - const { - filterValue: filter_value - } = $checkbox.data(); - - delete this.field_filters["discount"]; - - if (is_checked) { - this.field_filters["discount"] = []; - this.field_filters["discount"].push(filter_value); - } - - if (this.field_filters["discount"].length === 0) { - delete this.field_filters["discount"]; - } - - me.change_route_with_filters(); - }); - } - - bind_filters() { - let me = this; - this.field_filters = {}; - this.attribute_filters = {}; - - $('.product-filter').on('change', (e) => { - me.from_filters = true; - - const $checkbox = $(e.target); - const is_checked = $checkbox.is(':checked'); - - if ($checkbox.is('.attribute-filter')) { - const { - attributeName: attribute_name, - attributeValue: attribute_value - } = $checkbox.data(); - - if (is_checked) { - this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || []; - this.attribute_filters[attribute_name].push(attribute_value); - } else { - this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || []; - this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name].filter(v => v !== attribute_value); - } - - if (this.attribute_filters[attribute_name].length === 0) { - delete this.attribute_filters[attribute_name]; - } - } else if ($checkbox.is('.field-filter') || $checkbox.is('.discount-filter')) { - const { - filterName: filter_name, - filterValue: filter_value - } = $checkbox.data(); - - if ($checkbox.is('.discount-filter')) { - // clear previous discount filter to accomodate new - delete this.field_filters["discount"]; - } - if (is_checked) { - this.field_filters[filter_name] = this.field_filters[filter_name] || []; - if (!in_list(this.field_filters[filter_name], filter_value)) { - this.field_filters[filter_name].push(filter_value); - } - } else { - this.field_filters[filter_name] = this.field_filters[filter_name] || []; - this.field_filters[filter_name] = this.field_filters[filter_name].filter(v => v !== filter_value); - } - - if (this.field_filters[filter_name].length === 0) { - delete this.field_filters[filter_name]; - } - } - - me.change_route_with_filters(); - }); - - // bind filter lookup input box - $('.filter-lookup-input').on('keydown', frappe.utils.debounce((e) => { - const $input = $(e.target); - const keyword = ($input.val() || '').toLowerCase(); - const $filter_options = $input.next('.filter-options'); - - $filter_options.find('.filter-lookup-wrapper').show(); - $filter_options.find('.filter-lookup-wrapper').each((i, el) => { - const $el = $(el); - const value = $el.data('value').toLowerCase(); - if (!value.includes(keyword)) { - $el.hide(); - } - }); - }, 300)); - } - - change_route_with_filters() { - let route_params = frappe.utils.get_query_params(); - - let start = this.if_key_exists(route_params.start) || 0; - if (this.from_filters) { - start = 0; // show items from first page if new filters are triggered - } - - const query_string = this.get_query_string({ - start: start, - field_filters: JSON.stringify(this.if_key_exists(this.field_filters)), - attribute_filters: JSON.stringify(this.if_key_exists(this.attribute_filters)), - }); - window.history.pushState('filters', '', `${location.pathname}?` + query_string); - - $('.page_content input').prop('disabled', true); - - this.make(true); - $('.page_content input').prop('disabled', false); - } - - restore_filters_state() { - const filters = frappe.utils.get_query_params(); - let {field_filters, attribute_filters} = filters; - - if (field_filters) { - field_filters = JSON.parse(field_filters); - for (let fieldname in field_filters) { - const values = field_filters[fieldname]; - const selector = values.map(value => { - return `input[data-filter-name="${fieldname}"][data-filter-value="${value}"]`; - }).join(','); - $(selector).prop('checked', true); - } - this.field_filters = field_filters; - } - if (attribute_filters) { - attribute_filters = JSON.parse(attribute_filters); - for (let attribute in attribute_filters) { - const values = attribute_filters[attribute]; - const selector = values.map(value => { - return `input[data-attribute-name="${attribute}"][data-attribute-value="${value}"]`; - }).join(','); - $(selector).prop('checked', true); - } - this.attribute_filters = attribute_filters; - } - } - - render_no_products_section(error=false) { - let error_section = ` -
- Something went wrong. Please refresh or contact us. -
- `; - let no_results_section = ` -
-
- Empty Cart -
-
${ __('No products found') }

-
- `; - - this.products_section.append(error ? error_section : no_results_section); - } - - render_item_sub_categories(categories) { - if (categories && categories.length) { - let sub_group_html = ` -
- `; - - categories.forEach(category => { - sub_group_html += ` - -
- ${ category.name } -
-
- `; - }); - sub_group_html += `
`; - - $("#product-listing").prepend(sub_group_html); - } - } - - get_query_string(object) { - const url = new URLSearchParams(); - for (let key in object) { - const value = object[key]; - if (value) { - url.append(key, value); - } - } - return url.toString(); - } - - if_key_exists(obj) { - let exists = false; - for (let key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key) && obj[key]) { - exists = true; - break; - } - } - return exists ? obj : undefined; - } -}; \ No newline at end of file diff --git a/erpnext/e_commerce/redisearch_utils.py b/erpnext/e_commerce/redisearch_utils.py deleted file mode 100644 index 87ca9bd83d54..000000000000 --- a/erpnext/e_commerce/redisearch_utils.py +++ /dev/null @@ -1,255 +0,0 @@ -# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import json - -import frappe -from frappe import _ -from frappe.utils.redis_wrapper import RedisWrapper -from redis import ResponseError -from redis.commands.search.field import TagField, TextField -from redis.commands.search.indexDefinition import IndexDefinition -from redis.commands.search.suggestion import Suggestion - -WEBSITE_ITEM_INDEX = "website_items_index" -WEBSITE_ITEM_KEY_PREFIX = "website_item:" -WEBSITE_ITEM_NAME_AUTOCOMPLETE = "website_items_name_dict" -WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = "website_items_category_dict" - - -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] - - -def is_redisearch_enabled(): - "Return True only if redisearch is loaded and enabled." - is_redisearch_enabled = frappe.db.get_single_value("E Commerce Settings", "is_redisearch_enabled") - return is_search_module_loaded() and is_redisearch_enabled - - -def is_search_module_loaded(): - try: - cache = frappe.cache() - for module in cache.module_list(): - if module.get(b"name") == b"search": - return True - except Exception: - return False # handling older redis versions - - -def if_redisearch_enabled(function): - "Decorator to check if Redisearch is enabled." - - def wrapper(*args, **kwargs): - if is_redisearch_enabled(): - func = function(*args, **kwargs) - return func - return - - return wrapper - - -def make_key(key): - return frappe.cache().make_key(key) - - -@if_redisearch_enabled -def create_website_items_index(): - "Creates Index Definition." - - redis = frappe.cache() - index = redis.ft(WEBSITE_ITEM_INDEX) - - try: - index.dropindex() # drop if already exists - except ResponseError: - # will most likely raise a ResponseError if index does not exist - # ignore and create index - pass - except Exception: - raise_redisearch_error() - - idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)]) - - # Index fields mentioned in e-commerce settings - idx_fields = frappe.db.get_single_value("E Commerce Settings", "search_index_fields") - idx_fields = idx_fields.split(",") if idx_fields else [] - - if "web_item_name" in idx_fields: - idx_fields.remove("web_item_name") - - idx_fields = [to_search_field(f) for f in idx_fields] - - # TODO: sortable? - index.create_index( - [TextField("web_item_name", sortable=True)] + idx_fields, - definition=idx_def, - ) - - reindex_all_web_items() - define_autocomplete_dictionary() - - -def to_search_field(field): - if field == "tags": - return TagField("tags", separator=",") - - return TextField(field) - - -@if_redisearch_enabled -def insert_item_to_index(website_item_doc): - # Insert item to index - key = get_cache_key(website_item_doc.name) - cache = frappe.cache() - web_item = create_web_item_map(website_item_doc) - - for field, value in web_item.items(): - super(RedisWrapper, cache).hset(make_key(key), field, value) - - insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name) - - -@if_redisearch_enabled -def insert_to_name_ac(web_name, doc_name): - ac = frappe.cache().ft() - ac.sugadd(WEBSITE_ITEM_NAME_AUTOCOMPLETE, Suggestion(web_name, payload=doc_name)) - - -def create_web_item_map(website_item_doc): - fields_to_index = get_fields_indexed() - web_item = {} - - for field in fields_to_index: - web_item[field] = website_item_doc.get(field) or "" - - return web_item - - -@if_redisearch_enabled -def update_index_for_item(website_item_doc): - # Reinsert to Cache - insert_item_to_index(website_item_doc) - define_autocomplete_dictionary() - - -@if_redisearch_enabled -def delete_item_from_index(website_item_doc): - cache = frappe.cache() - key = get_cache_key(website_item_doc.name) - - try: - cache.delete(key) - except Exception: - raise_redisearch_error() - - delete_from_ac_dict(website_item_doc) - return True - - -@if_redisearch_enabled -def delete_from_ac_dict(website_item_doc): - """Removes this items's name from autocomplete dictionary""" - ac = frappe.cache().ft() - ac.sugdel(website_item_doc.web_item_name) - - -@if_redisearch_enabled -def define_autocomplete_dictionary(): - """ - Defines/Redefines an autocomplete search dictionary for Website Item Name. - Also creats autocomplete dictionary for Published Item Groups. - """ - - cache = frappe.cache() - - # Delete both autocomplete dicts - try: - cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE)) - cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE)) - except Exception: - raise_redisearch_error() - - create_items_autocomplete_dict() - create_item_groups_autocomplete_dict() - - -@if_redisearch_enabled -def create_items_autocomplete_dict(): - "Add items as suggestions in Autocompleter." - - ac = frappe.cache().ft() - items = frappe.get_all( - "Website Item", fields=["web_item_name", "item_group"], filters={"published": 1} - ) - for item in items: - ac.sugadd(WEBSITE_ITEM_NAME_AUTOCOMPLETE, Suggestion(item.web_item_name)) - - -@if_redisearch_enabled -def create_item_groups_autocomplete_dict(): - "Add item groups with weightage as suggestions in Autocompleter." - - published_item_groups = frappe.get_all( - "Item Group", fields=["name", "route", "weightage"], filters={"show_in_website": 1} - ) - if not published_item_groups: - return - - ac = frappe.cache().ft() - - for item_group in published_item_groups: - payload = json.dumps({"name": item_group.name, "route": item_group.route}) - ac.sugadd( - WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, - Suggestion( - string=item_group.name, - score=frappe.utils.flt(item_group.weightage) or 1.0, - payload=payload, # additional info that can be retrieved later - ), - ) - - -@if_redisearch_enabled -def reindex_all_web_items(): - items = frappe.get_all("Website Item", fields=get_fields_indexed(), filters={"published": True}) - - cache = frappe.cache() - for item in items: - web_item = create_web_item_map(item) - key = make_key(get_cache_key(item.name)) - - for field, value in web_item.items(): - super(RedisWrapper, cache).hset(key, field, value) - - -def get_cache_key(name): - name = frappe.scrub(name) - return f"{WEBSITE_ITEM_KEY_PREFIX}{name}" - - -def get_fields_indexed(): - fields_to_index = frappe.db.get_single_value("E Commerce Settings", "search_index_fields") - fields_to_index = fields_to_index.split(",") if fields_to_index else [] - - mandatory_fields = ["name", "web_item_name", "route", "thumbnail", "ranking"] - fields_to_index = fields_to_index + mandatory_fields - - return fields_to_index - - -def raise_redisearch_error(): - "Create an Error Log and raise error." - log = frappe.log_error("Redisearch Error") - log_link = frappe.utils.get_link_to_form("Error Log", log.name) - - frappe.throw( - msg=_("Something went wrong. Check {0}").format(log_link), title=_("Redisearch Error") - ) diff --git a/erpnext/e_commerce/shopping_cart/__init__.py b/erpnext/e_commerce/shopping_cart/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py deleted file mode 100644 index 7c7e169c528f..000000000000 --- a/erpnext/e_commerce/shopping_cart/cart.py +++ /dev/null @@ -1,721 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe -import frappe.defaults -from frappe import _, throw -from frappe.contacts.doctype.address.address import get_address_display -from frappe.contacts.doctype.contact.contact import get_contact_name -from frappe.utils import cint, cstr, flt, get_fullname -from frappe.utils.nestedset import get_root_of - -from erpnext.accounts.utils import get_account_name -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.utilities.product import get_web_item_qty_in_stock - - -class WebsitePriceListMissingError(frappe.ValidationError): - pass - - -def set_cart_count(quotation=None): - if cint(frappe.db.get_singles_value("E Commerce Settings", "enabled")): - if not quotation: - quotation = _get_cart_quotation() - cart_count = cstr(cint(quotation.get("total_qty"))) - - if hasattr(frappe.local, "cookie_manager"): - frappe.local.cookie_manager.set_cookie("cart_count", cart_count) - - -@frappe.whitelist() -def get_cart_quotation(doc=None): - party = get_party() - - if not doc: - quotation = _get_cart_quotation(party) - doc = quotation - set_cart_count(quotation) - - addresses = get_address_docs(party=party) - - if not doc.customer_address and addresses: - update_cart_address("billing", addresses[0].name) - - return { - "doc": decorate_quotation_doc(doc), - "shipping_addresses": get_shipping_addresses(party), - "billing_addresses": get_billing_addresses(party), - "shipping_rules": get_applicable_shipping_rules(party), - "cart_settings": frappe.get_cached_doc("E Commerce Settings"), - } - - -@frappe.whitelist() -def get_shipping_addresses(party=None): - if not party: - party = get_party() - addresses = get_address_docs(party=party) - return [ - {"name": address.name, "title": address.address_title, "display": address.display} - for address in addresses - if address.address_type == "Shipping" - ] - - -@frappe.whitelist() -def get_billing_addresses(party=None): - if not party: - party = get_party() - addresses = get_address_docs(party=party) - return [ - {"name": address.name, "title": address.address_title, "display": address.display} - for address in addresses - if address.address_type == "Billing" - ] - - -@frappe.whitelist() -def place_order(): - quotation = _get_cart_quotation() - cart_settings = frappe.db.get_value( - "E Commerce Settings", None, ["company", "allow_items_not_in_stock"], as_dict=1 - ) - quotation.company = cart_settings.company - - quotation.flags.ignore_permissions = True - quotation.submit() - - if quotation.quotation_to == "Lead" and quotation.party_name: - # company used to create customer accounts - frappe.defaults.set_user_default("company", quotation.company) - - if not (quotation.shipping_address_name or quotation.customer_address): - frappe.throw(_("Set Shipping Address or Billing Address")) - - from erpnext.selling.doctype.quotation.quotation import _make_sales_order - - sales_order = frappe.get_doc(_make_sales_order(quotation.name, ignore_permissions=True)) - sales_order.payment_schedule = [] - - if not cint(cart_settings.allow_items_not_in_stock): - for item in sales_order.get("items"): - item.warehouse = frappe.db.get_value( - "Website Item", {"item_code": item.item_code}, "website_warehouse" - ) - is_stock_item = frappe.db.get_value("Item", item.item_code, "is_stock_item") - - if is_stock_item: - item_stock = get_web_item_qty_in_stock(item.item_code, "website_warehouse") - if not cint(item_stock.in_stock): - throw(_("{0} Not in Stock").format(item.item_code)) - if item.qty > item_stock.stock_qty: - throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty, item.item_code)) - - sales_order.flags.ignore_permissions = True - sales_order.insert() - sales_order.submit() - - if hasattr(frappe.local, "cookie_manager"): - frappe.local.cookie_manager.delete_cookie("cart_count") - - return sales_order.name - - -@frappe.whitelist() -def request_for_quotation(): - quotation = _get_cart_quotation() - quotation.flags.ignore_permissions = True - - if get_shopping_cart_settings().save_quotations_as_draft: - quotation.save() - else: - quotation.submit() - return quotation.name - - -@frappe.whitelist() -def update_cart(item_code, qty, additional_notes=None, with_items=False): - quotation = _get_cart_quotation() - - empty_card = False - qty = flt(qty) - if qty == 0: - quotation_items = quotation.get("items", {"item_code": ["!=", item_code]}) - if quotation_items: - quotation.set("items", quotation_items) - else: - empty_card = True - - else: - warehouse = frappe.get_cached_value( - "Website Item", {"item_code": item_code}, "website_warehouse" - ) - - quotation_items = quotation.get("items", {"item_code": item_code}) - if not quotation_items: - quotation.append( - "items", - { - "doctype": "Quotation Item", - "item_code": item_code, - "qty": qty, - "additional_notes": additional_notes, - "warehouse": warehouse, - }, - ) - else: - quotation_items[0].qty = qty - quotation_items[0].additional_notes = additional_notes - quotation_items[0].warehouse = warehouse - - apply_cart_settings(quotation=quotation) - - quotation.flags.ignore_permissions = True - quotation.payment_schedule = [] - if not empty_card: - quotation.save() - else: - quotation.delete() - quotation = None - - set_cart_count(quotation) - - if cint(with_items): - context = get_cart_quotation(quotation) - return { - "items": frappe.render_template("templates/includes/cart/cart_items.html", context), - "total": frappe.render_template("templates/includes/cart/cart_items_total.html", context), - "taxes_and_totals": frappe.render_template( - "templates/includes/cart/cart_payment_summary.html", context - ), - } - else: - return {"name": quotation.name} - - -@frappe.whitelist() -def get_shopping_cart_menu(context=None): - if not context: - context = get_cart_quotation() - - return frappe.render_template("templates/includes/cart/cart_dropdown.html", context) - - -@frappe.whitelist() -def add_new_address(doc): - doc = frappe.parse_json(doc) - doc.update({"doctype": "Address"}) - address = frappe.get_doc(doc) - address.save(ignore_permissions=True) - - return address - - -@frappe.whitelist(allow_guest=True) -def create_lead_for_item_inquiry(lead, subject, message): - lead = frappe.parse_json(lead) - lead_doc = frappe.new_doc("Lead") - for fieldname in ("lead_name", "company_name", "email_id", "phone"): - lead_doc.set(fieldname, lead.get(fieldname)) - - lead_doc.set("lead_owner", "") - - if not frappe.db.exists("Lead Source", "Product Inquiry"): - frappe.get_doc({"doctype": "Lead Source", "source_name": "Product Inquiry"}).insert( - ignore_permissions=True - ) - - lead_doc.set("source", "Product Inquiry") - - try: - lead_doc.save(ignore_permissions=True) - except frappe.exceptions.DuplicateEntryError: - frappe.clear_messages() - lead_doc = frappe.get_doc("Lead", {"email_id": lead["email_id"]}) - - lead_doc.add_comment( - "Comment", - text=""" -
-
{subject}
-

{message}

-
- """.format( - subject=subject, message=message - ), - ) - - return lead_doc - - -@frappe.whitelist() -def get_terms_and_conditions(terms_name): - return frappe.db.get_value("Terms and Conditions", terms_name, "terms") - - -@frappe.whitelist() -def update_cart_address(address_type, address_name): - quotation = _get_cart_quotation() - address_doc = frappe.get_doc("Address", address_name).as_dict() - address_display = get_address_display(address_doc) - - if address_type.lower() == "billing": - quotation.customer_address = address_name - quotation.address_display = address_display - quotation.shipping_address_name = quotation.shipping_address_name or address_name - address_doc = next((doc for doc in get_billing_addresses() if doc["name"] == address_name), None) - elif address_type.lower() == "shipping": - quotation.shipping_address_name = address_name - quotation.shipping_address = address_display - quotation.customer_address = quotation.customer_address or address_name - address_doc = next( - (doc for doc in get_shipping_addresses() if doc["name"] == address_name), None - ) - apply_cart_settings(quotation=quotation) - - quotation.flags.ignore_permissions = True - quotation.save() - - context = get_cart_quotation(quotation) - context["address"] = address_doc - - return { - "taxes": frappe.render_template("templates/includes/order/order_taxes.html", context), - "address": frappe.render_template("templates/includes/cart/address_card.html", context), - } - - -def guess_territory(): - territory = None - geoip_country = frappe.session.get("session_country") - if geoip_country: - territory = frappe.db.get_value("Territory", geoip_country) - - return ( - territory - or frappe.db.get_value("E Commerce Settings", None, "territory") - or get_root_of("Territory") - ) - - -def decorate_quotation_doc(doc): - for d in doc.get("items", []): - item_code = d.item_code - fields = ["web_item_name", "thumbnail", "website_image", "description", "route"] - - # Variant Item - if not frappe.db.exists("Website Item", {"item_code": item_code}): - variant_data = frappe.db.get_values( - "Item", - filters={"item_code": item_code}, - fieldname=["variant_of", "item_name", "image"], - as_dict=True, - )[0] - item_code = variant_data.variant_of - fields = fields[1:] - d.web_item_name = variant_data.item_name - - if variant_data.image: # get image from variant or template web item - d.thumbnail = variant_data.image - fields = fields[2:] - - d.update(frappe.db.get_value("Website Item", {"item_code": item_code}, fields, as_dict=True)) - website_warehouse = frappe.get_cached_value( - "Website Item", {"item_code": item_code}, "website_warehouse" - ) - d.warehouse = website_warehouse - - return doc - - -def _get_cart_quotation(party=None): - """Return the open Quotation of type "Shopping Cart" or make a new one""" - if not party: - party = get_party() - - quotation = frappe.get_all( - "Quotation", - fields=["name"], - filters={ - "party_name": party.name, - "contact_email": frappe.session.user, - "order_type": "Shopping Cart", - "docstatus": 0, - }, - order_by="modified desc", - limit_page_length=1, - ) - - if quotation: - qdoc = frappe.get_doc("Quotation", quotation[0].name) - else: - company = frappe.db.get_value("E Commerce Settings", None, ["company"]) - qdoc = frappe.get_doc( - { - "doctype": "Quotation", - "naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-", - "quotation_to": party.doctype, - "company": company, - "order_type": "Shopping Cart", - "status": "Draft", - "docstatus": 0, - "__islocal": 1, - "party_name": party.name, - } - ) - - qdoc.contact_person = frappe.db.get_value("Contact", {"email_id": frappe.session.user}) - qdoc.contact_email = frappe.session.user - - qdoc.flags.ignore_permissions = True - qdoc.run_method("set_missing_values") - apply_cart_settings(party, qdoc) - - return qdoc - - -def update_party(fullname, company_name=None, mobile_no=None, phone=None): - party = get_party() - - party.customer_name = company_name or fullname - party.customer_type = "Company" if company_name else "Individual" - - contact_name = frappe.db.get_value("Contact", {"email_id": frappe.session.user}) - contact = frappe.get_doc("Contact", contact_name) - contact.first_name = fullname - contact.last_name = None - contact.customer_name = party.customer_name - contact.mobile_no = mobile_no - contact.phone = phone - contact.flags.ignore_permissions = True - contact.save() - - party_doc = frappe.get_doc(party.as_dict()) - party_doc.flags.ignore_permissions = True - party_doc.save() - - qdoc = _get_cart_quotation(party) - if not qdoc.get("__islocal"): - qdoc.customer_name = company_name or fullname - qdoc.run_method("set_missing_lead_customer_details") - qdoc.flags.ignore_permissions = True - qdoc.save() - - -def apply_cart_settings(party=None, quotation=None): - if not party: - party = get_party() - if not quotation: - quotation = _get_cart_quotation(party) - - cart_settings = frappe.get_doc("E Commerce Settings") - - set_price_list_and_rate(quotation, cart_settings) - - quotation.run_method("calculate_taxes_and_totals") - - set_taxes(quotation, cart_settings) - - _apply_shipping_rule(party, quotation, cart_settings) - - -def set_price_list_and_rate(quotation, cart_settings): - """set price list based on billing territory""" - - _set_price_list(cart_settings, quotation) - - # reset values - quotation.price_list_currency = ( - quotation.currency - ) = quotation.plc_conversion_rate = quotation.conversion_rate = None - for item in quotation.get("items"): - item.price_list_rate = item.discount_percentage = item.rate = item.amount = None - - # refetch values - quotation.run_method("set_price_list_and_item_details") - - if hasattr(frappe.local, "cookie_manager"): - # set it in cookies for using in product page - frappe.local.cookie_manager.set_cookie("selling_price_list", quotation.selling_price_list) - - -def _set_price_list(cart_settings, quotation=None): - """Set price list based on customer or shopping cart default""" - from erpnext.accounts.party import get_default_price_list - - party_name = quotation.get("party_name") if quotation else get_party().get("name") - selling_price_list = None - - # check if default customer price list exists - if party_name and frappe.db.exists("Customer", party_name): - selling_price_list = get_default_price_list(frappe.get_doc("Customer", party_name)) - - # check default price list in shopping cart - if not selling_price_list: - selling_price_list = cart_settings.price_list - - if quotation: - quotation.selling_price_list = selling_price_list - - return selling_price_list - - -def set_taxes(quotation, cart_settings): - """set taxes based on billing territory""" - from erpnext.accounts.party import set_taxes - - customer_group = frappe.db.get_value("Customer", quotation.party_name, "customer_group") - - quotation.taxes_and_charges = set_taxes( - quotation.party_name, - "Customer", - quotation.transaction_date, - quotation.company, - customer_group=customer_group, - supplier_group=None, - tax_category=quotation.tax_category, - billing_address=quotation.customer_address, - shipping_address=quotation.shipping_address_name, - use_for_shopping_cart=1, - ) - # - # # clear table - quotation.set("taxes", []) - # - # # append taxes - quotation.append_taxes_from_master() - - -def get_party(user=None): - if not user: - user = frappe.session.user - - contact_name = get_contact_name(user) - party = None - - if contact_name: - contact = frappe.get_doc("Contact", contact_name) - if contact.links: - party_doctype = contact.links[0].link_doctype - party = contact.links[0].link_name - - cart_settings = frappe.get_doc("E Commerce Settings") - - debtors_account = "" - - if cart_settings.enable_checkout: - debtors_account = get_debtors_account(cart_settings) - - if party: - return frappe.get_doc(party_doctype, party) - - else: - if not cart_settings.enabled: - frappe.local.flags.redirect_location = "/contact" - raise frappe.Redirect - customer = frappe.new_doc("Customer") - fullname = get_fullname(user) - customer.update( - { - "customer_name": fullname, - "customer_type": "Individual", - "customer_group": get_shopping_cart_settings().default_customer_group, - "territory": get_root_of("Territory"), - } - ) - - customer.append("portal_users", {"user": user}) - - if debtors_account: - customer.update({"accounts": [{"company": cart_settings.company, "account": debtors_account}]}) - - customer.flags.ignore_mandatory = True - customer.insert(ignore_permissions=True) - - contact = frappe.new_doc("Contact") - contact.update({"first_name": fullname, "email_ids": [{"email_id": user, "is_primary": 1}]}) - contact.append("links", dict(link_doctype="Customer", link_name=customer.name)) - contact.flags.ignore_mandatory = True - contact.insert(ignore_permissions=True) - - return customer - - -def get_debtors_account(cart_settings): - if not cart_settings.payment_gateway_account: - frappe.throw(_("Payment Gateway Account not set"), _("Mandatory")) - - payment_gateway_account_currency = frappe.get_doc( - "Payment Gateway Account", cart_settings.payment_gateway_account - ).currency - - account_name = _("Debtors ({0})").format(payment_gateway_account_currency) - - debtors_account_name = get_account_name( - "Receivable", - "Asset", - is_group=0, - account_currency=payment_gateway_account_currency, - company=cart_settings.company, - ) - - if not debtors_account_name: - debtors_account = frappe.get_doc( - { - "doctype": "Account", - "account_type": "Receivable", - "root_type": "Asset", - "is_group": 0, - "parent_account": get_account_name( - root_type="Asset", is_group=1, company=cart_settings.company - ), - "account_name": account_name, - "currency": payment_gateway_account_currency, - } - ).insert(ignore_permissions=True) - - return debtors_account.name - - else: - return debtors_account_name - - -def get_address_docs( - doctype=None, txt=None, filters=None, limit_start=0, limit_page_length=20, party=None -): - if not party: - party = get_party() - - if not party: - return [] - - address_names = frappe.db.get_all( - "Dynamic Link", - fields=("parent"), - filters=dict(parenttype="Address", link_doctype=party.doctype, link_name=party.name), - ) - - out = [] - - for a in address_names: - address = frappe.get_doc("Address", a.parent) - address.display = get_address_display(address.as_dict()) - out.append(address) - - return out - - -@frappe.whitelist() -def apply_shipping_rule(shipping_rule): - quotation = _get_cart_quotation() - - quotation.shipping_rule = shipping_rule - - apply_cart_settings(quotation=quotation) - - quotation.flags.ignore_permissions = True - quotation.save() - - return get_cart_quotation(quotation) - - -def _apply_shipping_rule(party=None, quotation=None, cart_settings=None): - if not quotation.shipping_rule: - shipping_rules = get_shipping_rules(quotation, cart_settings) - - if not shipping_rules: - return - - elif quotation.shipping_rule not in shipping_rules: - quotation.shipping_rule = shipping_rules[0] - - if quotation.shipping_rule: - quotation.run_method("apply_shipping_rule") - quotation.run_method("calculate_taxes_and_totals") - - -def get_applicable_shipping_rules(party=None, quotation=None): - shipping_rules = get_shipping_rules(quotation) - - if shipping_rules: - # we need this in sorted order as per the position of the rule in the settings page - return [[rule, rule] for rule in shipping_rules] - - -def get_shipping_rules(quotation=None, cart_settings=None): - if not quotation: - quotation = _get_cart_quotation() - - shipping_rules = [] - if quotation.shipping_address_name: - country = frappe.db.get_value("Address", quotation.shipping_address_name, "country") - if country: - sr_country = frappe.qb.DocType("Shipping Rule Country") - sr = frappe.qb.DocType("Shipping Rule") - query = ( - frappe.qb.from_(sr_country) - .join(sr) - .on(sr.name == sr_country.parent) - .select(sr.name) - .distinct() - .where((sr_country.country == country) & (sr.disabled != 1)) - ) - result = query.run(as_list=True) - shipping_rules = [x[0] for x in result] - - return shipping_rules - - -def get_address_territory(address_name): - """Tries to match city, state and country of address to existing territory""" - territory = None - - if address_name: - address_fields = frappe.db.get_value("Address", address_name, ["city", "state", "country"]) - for value in address_fields: - territory = frappe.db.get_value("Territory", value) - if territory: - break - - return territory - - -def show_terms(doc): - return doc.tc_name - - -@frappe.whitelist(allow_guest=True) -def apply_coupon_code(applied_code, applied_referral_sales_partner): - quotation = True - - if not applied_code: - frappe.throw(_("Please enter a coupon code")) - - coupon_list = frappe.get_all("Coupon Code", filters={"coupon_code": applied_code}) - if not coupon_list: - frappe.throw(_("Please enter a valid coupon code")) - - coupon_name = coupon_list[0].name - - from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code - - validate_coupon_code(coupon_name) - quotation = _get_cart_quotation() - quotation.coupon_code = coupon_name - quotation.flags.ignore_permissions = True - quotation.save() - - if applied_referral_sales_partner: - sales_partner_list = frappe.get_all( - "Sales Partner", filters={"referral_code": applied_referral_sales_partner} - ) - if sales_partner_list: - sales_partner_name = sales_partner_list[0].name - quotation.referral_sales_partner = sales_partner_name - quotation.flags.ignore_permissions = True - quotation.save() - - return quotation diff --git a/erpnext/e_commerce/shopping_cart/product_info.py b/erpnext/e_commerce/shopping_cart/product_info.py deleted file mode 100644 index 0248ca73d7f6..000000000000 --- a/erpnext/e_commerce/shopping_cart/product_info.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, - show_quantity_in_website, -) -from erpnext.e_commerce.shopping_cart.cart import _get_cart_quotation, _set_price_list -from erpnext.utilities.product import ( - get_non_stock_item_status, - get_price, - get_web_item_qty_in_stock, -) - - -@frappe.whitelist(allow_guest=True) -def get_product_info_for_website(item_code, skip_quotation_creation=False): - """get product price / stock info for website""" - - cart_settings = get_shopping_cart_settings() - if not cart_settings.enabled: - # return settings even if cart is disabled - return frappe._dict({"product_info": {}, "cart_settings": cart_settings}) - - cart_quotation = frappe._dict() - if not skip_quotation_creation: - cart_quotation = _get_cart_quotation() - - selling_price_list = ( - cart_quotation.get("selling_price_list") - if cart_quotation - else _set_price_list(cart_settings, None) - ) - - price = {} - if cart_settings.show_price: - is_guest = frappe.session.user == "Guest" - # Show Price if logged in. - # If not logged in, check if price is hidden for guest. - if not is_guest or not cart_settings.hide_price_for_guest: - price = get_price( - item_code, selling_price_list, cart_settings.default_customer_group, cart_settings.company - ) - - stock_status = None - - if cart_settings.show_stock_availability: - 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, - "qty": 0, - "uom": frappe.db.get_value("Item", item_code, "stock_uom"), - "sales_uom": frappe.db.get_value("Item", item_code, "sales_uom"), - } - - if stock_status: - 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": - item = cart_quotation.get({"item_code": item_code}) if cart_quotation else None - if item: - product_info["qty"] = item[0].qty - - return frappe._dict({"product_info": product_info, "cart_settings": cart_settings}) - - -def set_product_info_for_website(item): - """set product price uom for website""" - product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get( - "product_info" - ) - - if product_info: - item.update(product_info) - item["stock_uom"] = product_info.get("uom") - item["sales_uom"] = product_info.get("sales_uom") - if product_info.get("price"): - item["price_stock_uom"] = product_info.get("price").get("formatted_price") - item["price_sales_uom"] = product_info.get("price").get("formatted_price_sales_uom") - else: - item["price_stock_uom"] = "" - item["price_sales_uom"] = "" diff --git a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py deleted file mode 100644 index 8210f9743d39..000000000000 --- a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py +++ /dev/null @@ -1,398 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - - -import unittest - -import frappe -from frappe.tests.utils import change_settings -from frappe.utils import add_months, cint, nowdate - -from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item -from erpnext.e_commerce.shopping_cart.cart import ( - _get_cart_quotation, - get_cart_quotation, - get_party, - request_for_quotation, - update_cart, -) - - -class TestShoppingCart(unittest.TestCase): - """ - Note: - Shopping Cart == Quotation - """ - - def setUp(self): - frappe.set_user("Administrator") - self.enable_shopping_cart() - if not frappe.db.exists("Website Item", {"item_code": "_Test Item"}): - make_website_item(frappe.get_cached_doc("Item", "_Test Item")) - - if not frappe.db.exists("Website Item", {"item_code": "_Test Item 2"}): - make_website_item(frappe.get_cached_doc("Item", "_Test Item 2")) - - def tearDown(self): - frappe.db.rollback() - frappe.set_user("Administrator") - self.disable_shopping_cart() - - @classmethod - def tearDownClass(cls): - frappe.db.sql("delete from `tabTax Rule`") - - def test_get_cart_new_user(self): - self.login_as_customer( - "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer" - ) - create_address_and_contact( - address_title="_Test Address for Customer 2", - first_name="_Test Contact for Customer 2", - email="test_contact_two_customer@example.com", - customer="_Test Customer 2", - ) - # test if lead is created and quotation with new lead is fetched - customer = frappe.get_doc("Customer", "_Test Customer 2") - quotation = _get_cart_quotation(party=customer) - self.assertEqual(quotation.quotation_to, "Customer") - self.assertEqual( - quotation.contact_person, - frappe.db.get_value("Contact", dict(email_id="test_contact_two_customer@example.com")), - ) - self.assertEqual(quotation.contact_email, frappe.session.user) - - return quotation - - def test_get_cart_customer(self, customer="_Test Customer 2"): - def validate_quotation(customer_name): - # test if quotation with customer is fetched - party = frappe.get_doc("Customer", customer_name) - quotation = _get_cart_quotation(party=party) - self.assertEqual(quotation.quotation_to, "Customer") - self.assertEqual(quotation.party_name, customer_name) - self.assertEqual(quotation.contact_email, frappe.session.user) - return quotation - - quotation = validate_quotation(customer) - return quotation - - def test_add_to_cart(self): - self.login_as_customer( - "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer" - ) - create_address_and_contact( - address_title="_Test Address for Customer 2", - first_name="_Test Contact for Customer 2", - email="test_contact_two_customer@example.com", - customer="_Test Customer 2", - ) - # clear existing quotations - self.clear_existing_quotations() - - # add first item - update_cart("_Test Item", 1) - - quotation = self.test_get_cart_customer("_Test Customer 2") - - self.assertEqual(quotation.get("items")[0].item_code, "_Test Item") - self.assertEqual(quotation.get("items")[0].qty, 1) - self.assertEqual(quotation.get("items")[0].amount, 10) - - # add second item - update_cart("_Test Item 2", 1) - quotation = self.test_get_cart_customer("_Test Customer 2") - self.assertEqual(quotation.get("items")[1].item_code, "_Test Item 2") - self.assertEqual(quotation.get("items")[1].qty, 1) - self.assertEqual(quotation.get("items")[1].amount, 20) - - self.assertEqual(len(quotation.get("items")), 2) - - def test_update_cart(self): - # first, add to cart - self.test_add_to_cart() - - # update first item - update_cart("_Test Item", 5) - quotation = self.test_get_cart_customer("_Test Customer 2") - self.assertEqual(quotation.get("items")[0].item_code, "_Test Item") - self.assertEqual(quotation.get("items")[0].qty, 5) - self.assertEqual(quotation.get("items")[0].amount, 50) - self.assertEqual(quotation.net_total, 70) - self.assertEqual(len(quotation.get("items")), 2) - - def test_remove_from_cart(self): - # first, add to cart - self.test_add_to_cart() - - # remove first item - update_cart("_Test Item", 0) - quotation = self.test_get_cart_customer("_Test Customer 2") - - self.assertEqual(quotation.get("items")[0].item_code, "_Test Item 2") - self.assertEqual(quotation.get("items")[0].qty, 1) - self.assertEqual(quotation.get("items")[0].amount, 20) - self.assertEqual(quotation.net_total, 20) - self.assertEqual(len(quotation.get("items")), 1) - - @unittest.skip("Flaky in CI") - def test_tax_rule(self): - self.create_tax_rule() - - self.login_as_customer( - "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer" - ) - create_address_and_contact( - address_title="_Test Address for Customer 2", - first_name="_Test Contact for Customer 2", - email="test_contact_two_customer@example.com", - customer="_Test Customer 2", - ) - - quotation = self.create_quotation() - - from erpnext.accounts.party import set_taxes - - tax_rule_master = set_taxes( - quotation.party_name, - "Customer", - None, - quotation.company, - customer_group=None, - supplier_group=None, - tax_category=quotation.tax_category, - billing_address=quotation.customer_address, - shipping_address=quotation.shipping_address_name, - use_for_shopping_cart=1, - ) - - self.assertEqual(quotation.taxes_and_charges, tax_rule_master) - self.assertEqual(quotation.total_taxes_and_charges, 1000.0) - - self.remove_test_quotation(quotation) - - @change_settings( - "E Commerce Settings", - { - "company": "_Test Company", - "enabled": 1, - "default_customer_group": "_Test Customer Group", - "price_list": "_Test Price List India", - "show_price": 1, - }, - ) - def test_add_item_variant_without_web_item_to_cart(self): - "Test adding Variants having no Website Items in cart via Template Web Item." - from erpnext.controllers.item_variant import create_variant - from erpnext.e_commerce.doctype.website_item.website_item import make_website_item - from erpnext.stock.doctype.item.test_item import make_item - - template_item = make_item( - "Test-Tshirt-Temp", - { - "has_variant": 1, - "variant_based_on": "Item Attribute", - "attributes": [{"attribute": "Test Size"}, {"attribute": "Test Colour"}], - }, - ) - variant = create_variant("Test-Tshirt-Temp", {"Test Size": "Small", "Test Colour": "Red"}) - variant.save() - make_website_item(template_item) # publish template not variant - - update_cart("Test-Tshirt-Temp-S-R", 1) - - cart = get_cart_quotation() # test if cart page gets data without errors - doc = cart.get("doc") - - self.assertEqual(doc.get("items")[0].item_name, "Test-Tshirt-Temp-S-R") - - # test if items are rendered without error - frappe.render_template("templates/includes/cart/cart_items.html", cart) - - @change_settings("E Commerce Settings", {"save_quotations_as_draft": 1}) - def test_cart_without_checkout_and_draft_quotation(self): - "Test impact of 'save_quotations_as_draft' checkbox." - frappe.local.shopping_cart_settings = None - - # add item to cart - update_cart("_Test Item", 1) - quote_name = request_for_quotation() # Request for Quote - quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus")) - - self.assertEqual(quote_doctstatus, 0) - - frappe.db.set_single_value("E Commerce Settings", "save_quotations_as_draft", 0) - frappe.local.shopping_cart_settings = None - update_cart("_Test Item", 1) - quote_name = request_for_quotation() # Request for Quote - quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus")) - - self.assertEqual(quote_doctstatus, 1) - - def create_tax_rule(self): - tax_rule = frappe.get_test_records("Tax Rule")[0] - try: - frappe.get_doc(tax_rule).insert(ignore_if_duplicate=True) - except (frappe.DuplicateEntryError, ConflictingTaxRule): - pass - - def create_quotation(self): - quotation = frappe.new_doc("Quotation") - - values = { - "doctype": "Quotation", - "quotation_to": "Customer", - "order_type": "Shopping Cart", - "party_name": get_party(frappe.session.user).name, - "docstatus": 0, - "contact_email": frappe.session.user, - "selling_price_list": "_Test Price List Rest of the World", - "currency": "USD", - "taxes_and_charges": "_Test Tax 1 - _TC", - "conversion_rate": 1, - "transaction_date": nowdate(), - "valid_till": add_months(nowdate(), 1), - "items": [{"item_code": "_Test Item", "qty": 1}], - "taxes": frappe.get_doc("Sales Taxes and Charges Template", "_Test Tax 1 - _TC").taxes, - "company": "_Test Company", - } - - quotation.update(values) - - quotation.insert(ignore_permissions=True) - - return quotation - - def remove_test_quotation(self, quotation): - frappe.set_user("Administrator") - quotation.delete() - - # helper functions - def enable_shopping_cart(self): - settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings") - - settings.update( - { - "enabled": 1, - "company": "_Test Company", - "default_customer_group": "_Test Customer Group", - "quotation_series": "_T-Quotation-", - "price_list": "_Test Price List India", - } - ) - - # insert item price - if not frappe.db.get_value( - "Item Price", {"price_list": "_Test Price List India", "item_code": "_Test Item"} - ): - frappe.get_doc( - { - "doctype": "Item Price", - "price_list": "_Test Price List India", - "item_code": "_Test Item", - "price_list_rate": 10, - } - ).insert() - frappe.get_doc( - { - "doctype": "Item Price", - "price_list": "_Test Price List India", - "item_code": "_Test Item 2", - "price_list_rate": 20, - } - ).insert() - - settings.save() - frappe.local.shopping_cart_settings = None - - def disable_shopping_cart(self): - settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings") - settings.enabled = 0 - settings.save() - frappe.local.shopping_cart_settings = None - - def login_as_new_user(self): - self.create_user_if_not_exists("test_cart_user@example.com") - frappe.set_user("test_cart_user@example.com") - - def login_as_customer( - self, email="test_contact_customer@example.com", name="_Test Contact For _Test Customer" - ): - self.create_user_if_not_exists(email, name) - frappe.set_user(email) - - def clear_existing_quotations(self): - quotations = frappe.get_all( - "Quotation", - filters={"party_name": get_party().name, "order_type": "Shopping Cart", "docstatus": 0}, - order_by="modified desc", - pluck="name", - ) - - for quotation in quotations: - frappe.delete_doc("Quotation", quotation, ignore_permissions=True, force=True) - - def create_user_if_not_exists(self, email, first_name=None): - if frappe.db.exists("User", email): - return - - user = frappe.get_doc( - { - "doctype": "User", - "user_type": "Website User", - "email": email, - "send_welcome_email": 0, - "first_name": first_name or email.split("@")[0], - } - ).insert(ignore_permissions=True) - - user.add_roles("Customer") - - -def create_address_and_contact(**kwargs): - if not frappe.db.get_value("Address", {"address_title": kwargs.get("address_title")}): - frappe.get_doc( - { - "doctype": "Address", - "address_title": kwargs.get("address_title"), - "address_type": kwargs.get("address_type") or "Office", - "address_line1": kwargs.get("address_line1") or "Station Road", - "city": kwargs.get("city") or "_Test City", - "state": kwargs.get("state") or "Test State", - "country": kwargs.get("country") or "India", - "links": [ - {"link_doctype": "Customer", "link_name": kwargs.get("customer") or "_Test Customer"} - ], - } - ).insert() - - if not frappe.db.get_value("Contact", {"first_name": kwargs.get("first_name")}): - contact = frappe.get_doc( - { - "doctype": "Contact", - "first_name": kwargs.get("first_name"), - "links": [ - {"link_doctype": "Customer", "link_name": kwargs.get("customer") or "_Test Customer"} - ], - } - ) - contact.add_email(kwargs.get("email") or "test_contact_customer@example.com", is_primary=True) - contact.add_phone(kwargs.get("phone") or "+91 0000000000", is_primary_phone=True) - contact.insert() - - -test_dependencies = [ - "Sales Taxes and Charges Template", - "Price List", - "Item Price", - "Shipping Rule", - "Currency Exchange", - "Customer Group", - "Lead", - "Customer", - "Contact", - "Address", - "Item", - "Tax Rule", -] diff --git a/erpnext/e_commerce/shopping_cart/utils.py b/erpnext/e_commerce/shopping_cart/utils.py deleted file mode 100644 index 3d48c28dd1ca..000000000000 --- a/erpnext/e_commerce/shopping_cart/utils.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import is_cart_enabled - - -def show_cart_count(): - if ( - is_cart_enabled() - and frappe.db.get_value("User", frappe.session.user, "user_type") == "Website User" - ): - return True - - return False - - -def set_cart_count(login_manager): - # since this is run only on hooks login event - # make sure user is already a customer - # before trying to set cart count - user_is_customer = is_customer() - if not user_is_customer: - return - - if show_cart_count(): - from erpnext.e_commerce.shopping_cart.cart import set_cart_count - - # set_cart_count will try to fetch existing cart quotation - # or create one if non existent (and create a customer too) - # cart count is calculated from this quotation's items - set_cart_count() - - -def clear_cart_count(login_manager): - if show_cart_count(): - frappe.local.cookie_manager.delete_cookie("cart_count") - - -def update_website_context(context): - cart_enabled = is_cart_enabled() - context["shopping_cart_enabled"] = cart_enabled - - -def is_customer(): - if frappe.session.user and frappe.session.user != "Guest": - contact_name = frappe.get_value("Contact", {"email_id": frappe.session.user}) - if contact_name: - contact = frappe.get_doc("Contact", contact_name) - for link in contact.links: - if link.link_doctype == "Customer": - return True - - return False diff --git a/erpnext/e_commerce/variant_selector/__init__.py b/erpnext/e_commerce/variant_selector/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/variant_selector/item_variants_cache.py b/erpnext/e_commerce/variant_selector/item_variants_cache.py deleted file mode 100644 index f8439d5d43d0..000000000000 --- a/erpnext/e_commerce/variant_selector/item_variants_cache.py +++ /dev/null @@ -1,130 +0,0 @@ -import frappe - - -class ItemVariantsCacheManager: - def __init__(self, item_code): - self.item_code = item_code - - def get_item_variants_data(self): - val = frappe.cache().hget("item_variants_data", self.item_code) - - if not val: - self.build_cache() - - return frappe.cache().hget("item_variants_data", self.item_code) - - def get_attribute_value_item_map(self): - val = frappe.cache().hget("attribute_value_item_map", self.item_code) - - if not val: - self.build_cache() - - return frappe.cache().hget("attribute_value_item_map", self.item_code) - - def get_item_attribute_value_map(self): - val = frappe.cache().hget("item_attribute_value_map", self.item_code) - - if not val: - self.build_cache() - - return frappe.cache().hget("item_attribute_value_map", self.item_code) - - def get_optional_attributes(self): - val = frappe.cache().hget("optional_attributes", self.item_code) - - if not val: - self.build_cache() - - return frappe.cache().hget("optional_attributes", self.item_code) - - def get_ordered_attribute_values(self): - val = frappe.cache().get_value("ordered_attribute_values_map") - if val: - return val - - all_attribute_values = frappe.get_all( - "Item Attribute Value", ["attribute_value", "idx", "parent"], order_by="idx asc" - ) - - ordered_attribute_values_map = frappe._dict({}) - for d in all_attribute_values: - ordered_attribute_values_map.setdefault(d.parent, []).append(d.attribute_value) - - frappe.cache().set_value("ordered_attribute_values_map", ordered_attribute_values_map) - return ordered_attribute_values_map - - def build_cache(self): - parent_item_code = self.item_code - - attributes = [ - a.attribute - for a in frappe.get_all( - "Item Variant Attribute", {"parent": parent_item_code}, ["attribute"], order_by="idx asc" - ) - ] - - # Get Variants and tehir Attributes that are not disabled - iva = frappe.qb.DocType("Item Variant Attribute") - item = frappe.qb.DocType("Item") - query = ( - frappe.qb.from_(iva) - .join(item) - .on(item.name == iva.parent) - .select(iva.parent, iva.attribute, iva.attribute_value) - .where((iva.variant_of == parent_item_code) & (item.disabled == 0)) - .orderby(iva.name) - ) - item_variants_data = query.run() - - attribute_value_item_map = frappe._dict() - item_attribute_value_map = frappe._dict() - - for row in item_variants_data: - item_code, attribute, attribute_value = row - # (attr, value) => [item1, item2] - attribute_value_item_map.setdefault((attribute, attribute_value), []).append(item_code) - # item => {attr1: value1, attr2: value2} - item_attribute_value_map.setdefault(item_code, {})[attribute] = attribute_value - - optional_attributes = set() - for item_code, attr_dict in item_attribute_value_map.items(): - for attribute in attributes: - if attribute not in attr_dict: - optional_attributes.add(attribute) - - frappe.cache().hset("attribute_value_item_map", parent_item_code, attribute_value_item_map) - frappe.cache().hset("item_attribute_value_map", parent_item_code, item_attribute_value_map) - frappe.cache().hset("item_variants_data", parent_item_code, item_variants_data) - frappe.cache().hset("optional_attributes", parent_item_code, optional_attributes) - - def clear_cache(self): - keys = [ - "attribute_value_item_map", - "item_attribute_value_map", - "item_variants_data", - "optional_attributes", - ] - - for key in keys: - frappe.cache().hdel(key, self.item_code) - - def rebuild_cache(self): - self.clear_cache() - enqueue_build_cache(self.item_code) - - -def build_cache(item_code): - frappe.cache().hset("item_cache_build_in_progress", item_code, 1) - i = ItemVariantsCacheManager(item_code) - i.build_cache() - frappe.cache().hset("item_cache_build_in_progress", item_code, 0) - - -def enqueue_build_cache(item_code): - if frappe.cache().hget("item_cache_build_in_progress", item_code): - return - frappe.enqueue( - "erpnext.e_commerce.variant_selector.item_variants_cache.build_cache", - item_code=item_code, - queue="long", - ) diff --git a/erpnext/e_commerce/variant_selector/test_variant_selector.py b/erpnext/e_commerce/variant_selector/test_variant_selector.py deleted file mode 100644 index 8eb497c1b530..000000000000 --- a/erpnext/e_commerce/variant_selector/test_variant_selector.py +++ /dev/null @@ -1,125 +0,0 @@ -import frappe -from frappe.tests.utils import FrappeTestCase - -from erpnext.controllers.item_variant import create_variant -from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import ( - setup_e_commerce_settings, -) -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item -from erpnext.e_commerce.variant_selector.utils import get_next_attribute_and_values -from erpnext.stock.doctype.item.test_item import make_item - -test_dependencies = ["Item"] - - -class TestVariantSelector(FrappeTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - template_item = make_item( - "Test-Tshirt-Temp", - { - "has_variant": 1, - "variant_based_on": "Item Attribute", - "attributes": [{"attribute": "Test Size"}, {"attribute": "Test Colour"}], - }, - ) - - # create L-R, L-G, M-R, M-G and S-R - for size in ( - "Large", - "Medium", - ): - for colour in ( - "Red", - "Green", - ): - variant = create_variant("Test-Tshirt-Temp", {"Test Size": size, "Test Colour": colour}) - variant.save() - - variant = create_variant("Test-Tshirt-Temp", {"Test Size": "Small", "Test Colour": "Red"}) - variant.save() - - make_website_item(template_item) # publish template not variants - - def test_item_attributes(self): - """ - Test if the right attributes are fetched in the popup. - (Attributes must only come from active items) - - Attribute selection must not be linked to Website Items. - """ - from erpnext.e_commerce.variant_selector.utils import get_attributes_and_values - - attr_data = get_attributes_and_values("Test-Tshirt-Temp") - - self.assertEqual(attr_data[0]["attribute"], "Test Size") - self.assertEqual(attr_data[1]["attribute"], "Test Colour") - self.assertEqual(len(attr_data[0]["values"]), 3) # ['Small', 'Medium', 'Large'] - self.assertEqual(len(attr_data[1]["values"]), 2) # ['Red', 'Green'] - - # disable small red tshirt, now there are no small tshirts. - # but there are some red tshirts - small_variant = frappe.get_doc("Item", "Test-Tshirt-Temp-S-R") - small_variant.disabled = 1 - small_variant.save() # trigger cache rebuild - - attr_data = get_attributes_and_values("Test-Tshirt-Temp") - - # Only L and M attribute values must be fetched since S is disabled - self.assertEqual(len(attr_data[0]["values"]), 2) # ['Medium', 'Large'] - - # teardown - small_variant.disabled = 0 - small_variant.save() - - def test_next_item_variant_values(self): - """ - Test if on selecting an attribute value, the next possible values - are filtered accordingly. - Values that dont apply should not be fetched. - E.g. - There is a ** Small-Red ** Tshirt. No other colour in this size. - On selecting ** Small **, only ** Red ** should be selectable next. - """ - next_values = get_next_attribute_and_values( - "Test-Tshirt-Temp", selected_attributes={"Test Size": "Small"} - ) - next_colours = next_values["valid_options_for_attributes"]["Test Colour"] - filtered_items = next_values["filtered_items"] - - self.assertEqual(len(next_colours), 1) - self.assertEqual(next_colours.pop(), "Red") - self.assertEqual(len(filtered_items), 1) - self.assertEqual(filtered_items.pop(), "Test-Tshirt-Temp-S-R") - - def test_exact_match_with_price(self): - """ - Test price fetching and matching of variant without Website Item - """ - from erpnext.e_commerce.doctype.website_item.test_website_item import make_web_item_price - - frappe.set_user("Administrator") - setup_e_commerce_settings( - { - "company": "_Test Company", - "enabled": 1, - "default_customer_group": "_Test Customer Group", - "price_list": "_Test Price List India", - "show_price": 1, - } - ) - - make_web_item_price(item_code="Test-Tshirt-Temp-S-R", price_list_rate=100) - - frappe.local.shopping_cart_settings = None # clear cached settings values - next_values = get_next_attribute_and_values( - "Test-Tshirt-Temp", selected_attributes={"Test Size": "Small", "Test Colour": "Red"} - ) - print(">>>>", next_values) - price_info = next_values["product_info"]["price"] - - self.assertEqual(next_values["exact_match"][0], "Test-Tshirt-Temp-S-R") - self.assertEqual(next_values["exact_match"][0], "Test-Tshirt-Temp-S-R") - self.assertEqual(price_info["price_list_rate"], 100.0) - self.assertEqual(price_info["formatted_price_sales_uom"], "₹ 100.00") diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py deleted file mode 100644 index 88356f5e9096..000000000000 --- a/erpnext/e_commerce/variant_selector/utils.py +++ /dev/null @@ -1,251 +0,0 @@ -import frappe -from frappe.utils import cint, flt - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.e_commerce.shopping_cart.cart import _set_price_list -from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager -from erpnext.utilities.product import get_price - - -def get_item_codes_by_attributes(attribute_filters, template_item_code=None): - items = [] - - for attribute, values in attribute_filters.items(): - attribute_values = values - - if not isinstance(attribute_values, list): - attribute_values = [attribute_values] - - if not attribute_values: - continue - - wheres = [] - query_values = [] - for attribute_value in attribute_values: - wheres.append("( attribute = %s and attribute_value = %s )") - query_values += [attribute, attribute_value] - - attribute_query = " or ".join(wheres) - - if template_item_code: - variant_of_query = "AND t2.variant_of = %s" - query_values.append(template_item_code) - else: - variant_of_query = "" - - query = """ - SELECT - t1.parent - FROM - `tabItem Variant Attribute` t1 - WHERE - 1 = 1 - AND ( - {attribute_query} - ) - AND EXISTS ( - SELECT - 1 - FROM - `tabItem` t2 - WHERE - t2.name = t1.parent - {variant_of_query} - ) - GROUP BY - t1.parent - ORDER BY - NULL - """.format( - attribute_query=attribute_query, variant_of_query=variant_of_query - ) - - item_codes = set([r[0] for r in frappe.db.sql(query, query_values)]) # nosemgrep - items.append(item_codes) - - res = list(set.intersection(*items)) - - return res - - -@frappe.whitelist(allow_guest=True) -def get_attributes_and_values(item_code): - """Build a list of attributes and their possible values. - This will ignore the values upon selection of which there cannot exist one item. - """ - item_cache = ItemVariantsCacheManager(item_code) - item_variants_data = item_cache.get_item_variants_data() - - attributes = get_item_attributes(item_code) - attribute_list = [a.attribute for a in attributes] - - valid_options = {} - for item_code, attribute, attribute_value in item_variants_data: - if attribute in attribute_list: - valid_options.setdefault(attribute, set()).add(attribute_value) - - item_attribute_values = frappe.db.get_all( - "Item Attribute Value", ["parent", "attribute_value", "idx"], order_by="parent asc, idx asc" - ) - ordered_attribute_value_map = frappe._dict() - for iv in item_attribute_values: - ordered_attribute_value_map.setdefault(iv.parent, []).append(iv.attribute_value) - - # build attribute values in idx order - for attr in attributes: - valid_attribute_values = valid_options.get(attr.attribute, []) - ordered_values = ordered_attribute_value_map.get(attr.attribute, []) - attr["values"] = [v for v in ordered_values if v in valid_attribute_values] - - return attributes - - -@frappe.whitelist(allow_guest=True) -def get_next_attribute_and_values(item_code, selected_attributes): - from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses - - """Find the count of Items that match the selected attributes. - Also, find the attribute values that are not applicable for further searching. - If less than equal to 10 items are found, return item_codes of those items. - If one item is matched exactly, return item_code of that item. - """ - selected_attributes = frappe.parse_json(selected_attributes) - - item_cache = ItemVariantsCacheManager(item_code) - item_variants_data = item_cache.get_item_variants_data() - - attributes = get_item_attributes(item_code) - attribute_list = [a.attribute for a in attributes] - filtered_items = get_items_with_selected_attributes(item_code, selected_attributes) - - next_attribute = None - - for attribute in attribute_list: - if attribute not in selected_attributes: - next_attribute = attribute - break - - valid_options_for_attributes = frappe._dict() - - for a in attribute_list: - valid_options_for_attributes[a] = set() - - selected_attribute = selected_attributes.get(a, None) - if selected_attribute: - # already selected attribute values are valid options - valid_options_for_attributes[a].add(selected_attribute) - - for row in item_variants_data: - item_code, attribute, attribute_value = row - if ( - item_code in filtered_items - and attribute not in selected_attributes - and attribute in attribute_list - ): - valid_options_for_attributes[attribute].add(attribute_value) - - optional_attributes = item_cache.get_optional_attributes() - exact_match = [] - # search for exact match if all selected attributes are required attributes - if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)): - item_attribute_value_map = item_cache.get_item_attribute_value_map() - for item_code, attr_dict in item_attribute_value_map.items(): - if item_code in filtered_items and set(attr_dict.keys()) == set(selected_attributes.keys()): - exact_match.append(item_code) - - filtered_items_count = len(filtered_items) - - # get product info if exact match - # from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website - if exact_match: - cart_settings = get_shopping_cart_settings() - product_info = get_item_variant_price_dict(exact_match[0], cart_settings) - - if product_info: - product_info["is_stock_item"] = frappe.get_cached_value("Item", exact_match[0], "is_stock_item") - product_info["allow_items_not_in_stock"] = cint(cart_settings.allow_items_not_in_stock) - else: - product_info = None - - product_id = "" - warehouse = "" - if exact_match or filtered_items: - if exact_match and len(exact_match) == 1: - product_id = exact_match[0] - elif filtered_items_count == 1: - product_id = list(filtered_items)[0] - - if product_id: - warehouse = frappe.get_cached_value( - "Website Item", {"item_code": product_id}, "website_warehouse" - ) - - available_qty = 0.0 - if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: - warehouses = get_child_warehouses(warehouse) - else: - warehouses = [warehouse] if warehouse else [] - - for warehouse in warehouses: - available_qty += flt( - frappe.db.get_value("Bin", {"item_code": product_id, "warehouse": warehouse}, "actual_qty") - ) - - return { - "next_attribute": next_attribute, - "valid_options_for_attributes": valid_options_for_attributes, - "filtered_items_count": filtered_items_count, - "filtered_items": filtered_items if filtered_items_count < 10 else [], - "exact_match": exact_match, - "product_info": product_info, - "available_qty": available_qty, - } - - -def get_items_with_selected_attributes(item_code, selected_attributes): - item_cache = ItemVariantsCacheManager(item_code) - attribute_value_item_map = item_cache.get_attribute_value_item_map() - - items = [] - for attribute, value in selected_attributes.items(): - filtered_items = attribute_value_item_map.get((attribute, value), []) - items.append(set(filtered_items)) - - return set.intersection(*items) - - -# utilities - - -def get_item_attributes(item_code): - attributes = frappe.db.get_all( - "Item Variant Attribute", - fields=["attribute"], - filters={"parenttype": "Item", "parent": item_code}, - order_by="idx asc", - ) - - optional_attributes = ItemVariantsCacheManager(item_code).get_optional_attributes() - - for a in attributes: - if a.attribute in optional_attributes: - a.optional = True - - return attributes - - -def get_item_variant_price_dict(item_code, cart_settings): - if cart_settings.enabled and cart_settings.show_price: - is_guest = frappe.session.user == "Guest" - # Show Price if logged in. - # If not logged in, check if price is hidden for guest. - if not is_guest or not cart_settings.hide_price_for_guest: - price_list = _set_price_list(cart_settings, None) - price = get_price( - item_code, price_list, cart_settings.default_customer_group, cart_settings.company - ) - return {"price": price} - - return None diff --git a/erpnext/e_commerce/web_template/__init__.py b/erpnext/e_commerce/web_template/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/web_template/hero_slider/__init__.py b/erpnext/e_commerce/web_template/hero_slider/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/web_template/hero_slider/hero_slider.html b/erpnext/e_commerce/web_template/hero_slider/hero_slider.html deleted file mode 100644 index fe4fee375bd8..000000000000 --- a/erpnext/e_commerce/web_template/hero_slider/hero_slider.html +++ /dev/null @@ -1,86 +0,0 @@ -{%- macro slide(image, title, subtitle, action, label, index, align="Left", theme="Dark") -%} -{%- set align_class = resolve_class({ - 'text-right': align == 'Right', - 'text-center': align == 'Centre', - 'text-left': align == 'Left', -}) -%} - -{%- set heading_class = resolve_class({ - 'text-white': theme == 'Dark', - '': theme == 'Light', -}) -%} - -{%- endmacro -%} - -{%- set hero_slider_id = 'id-' + frappe.utils.generate_hash('HeroSlider', 12) -%} - - - - diff --git a/erpnext/e_commerce/web_template/hero_slider/hero_slider.json b/erpnext/e_commerce/web_template/hero_slider/hero_slider.json deleted file mode 100644 index 39b2b3eaeb88..000000000000 --- a/erpnext/e_commerce/web_template/hero_slider/hero_slider.json +++ /dev/null @@ -1,288 +0,0 @@ -{ - "__unsaved": 1, - "creation": "2020-11-17 15:21:51.207221", - "docstatus": 0, - "doctype": "Web Template", - "fields": [ - { - "fieldname": "slider_name", - "fieldtype": "Data", - "label": "Slider Name", - "reqd": 1 - }, - { - "default": "1", - "fieldname": "show_indicators", - "fieldtype": "Check", - "label": "Show Indicators", - "reqd": 0 - }, - { - "default": "1", - "fieldname": "show_controls", - "fieldtype": "Check", - "label": "Show Controls", - "reqd": 0 - }, - { - "fieldname": "slide_1", - "fieldtype": "Section Break", - "label": "Slide 1", - "reqd": 0 - }, - { - "fieldname": "slide_1_image", - "fieldtype": "Attach Image", - "label": "Image", - "reqd": 0 - }, - { - "fieldname": "slide_1_title", - "fieldtype": "Data", - "label": "Title", - "reqd": 0 - }, - { - "fieldname": "slide_1_subtitle", - "fieldtype": "Small Text", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "slide_1_primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label", - "reqd": 0 - }, - { - "fieldname": "slide_1_primary_action", - "fieldtype": "Data", - "label": "Primary Action", - "reqd": 0 - }, - { - "fieldname": "slide_1_content_align", - "fieldtype": "Select", - "label": "Content Align", - "options": "Left\nCentre\nRight", - "reqd": 0 - }, - { - "fieldname": "slide_1_theme", - "fieldtype": "Select", - "label": "Slide Theme", - "options": "Dark\nLight", - "reqd": 0 - }, - { - "fieldname": "slide_2", - "fieldtype": "Section Break", - "label": "Slide 2", - "reqd": 0 - }, - { - "fieldname": "slide_2_image", - "fieldtype": "Attach Image", - "label": "Image ", - "reqd": 0 - }, - { - "fieldname": "slide_2_title", - "fieldtype": "Data", - "label": "Title ", - "reqd": 0 - }, - { - "fieldname": "slide_2_subtitle", - "fieldtype": "Small Text", - "label": "Subtitle ", - "reqd": 0 - }, - { - "fieldname": "slide_2_primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label ", - "reqd": 0 - }, - { - "fieldname": "slide_2_primary_action", - "fieldtype": "Data", - "label": "Primary Action ", - "reqd": 0 - }, - { - "default": "Left", - "fieldname": "slide_2_content_align", - "fieldtype": "Select", - "label": "Content Align", - "options": "Left\nCentre\nRight", - "reqd": 0 - }, - { - "fieldname": "slide_2_theme", - "fieldtype": "Select", - "label": "Slide Theme", - "options": "Dark\nLight", - "reqd": 0 - }, - { - "fieldname": "slide_3", - "fieldtype": "Section Break", - "label": "Slide 3", - "reqd": 0 - }, - { - "fieldname": "slide_3_image", - "fieldtype": "Attach Image", - "label": "Image", - "reqd": 0 - }, - { - "fieldname": "slide_3_title", - "fieldtype": "Data", - "label": "Title", - "reqd": 0 - }, - { - "fieldname": "slide_3_subtitle", - "fieldtype": "Small Text", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "slide_3_primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label", - "reqd": 0 - }, - { - "fieldname": "slide_3_primary_action", - "fieldtype": "Data", - "label": "Primary Action", - "reqd": 0 - }, - { - "fieldname": "slide_3_content_align", - "fieldtype": "Select", - "label": "Content Align", - "options": "Left\nCentre\nRight", - "reqd": 0 - }, - { - "fieldname": "slide_3_theme", - "fieldtype": "Select", - "label": "Slide Theme", - "options": "Dark\nLight", - "reqd": 0 - }, - { - "fieldname": "slide_4", - "fieldtype": "Section Break", - "label": "Slide 4", - "reqd": 0 - }, - { - "fieldname": "slide_4_image", - "fieldtype": "Attach Image", - "label": "Image", - "reqd": 0 - }, - { - "fieldname": "slide_4_title", - "fieldtype": "Data", - "label": "Title", - "reqd": 0 - }, - { - "fieldname": "slide_4_subtitle", - "fieldtype": "Small Text", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "slide_4_primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label", - "reqd": 0 - }, - { - "fieldname": "slide_4_primary_action", - "fieldtype": "Data", - "label": "Primary Action", - "reqd": 0 - }, - { - "fieldname": "slide_4_content_align", - "fieldtype": "Select", - "label": "Content Align", - "options": "Left\nCentre\nRight", - "reqd": 0 - }, - { - "fieldname": "slide_4_theme", - "fieldtype": "Select", - "label": "Slide Theme", - "options": "Dark\nLight", - "reqd": 0 - }, - { - "fieldname": "slide_5", - "fieldtype": "Section Break", - "label": "Slide 5", - "reqd": 0 - }, - { - "fieldname": "slide_5_image", - "fieldtype": "Attach Image", - "label": "Image", - "reqd": 0 - }, - { - "fieldname": "slide_5_title", - "fieldtype": "Data", - "label": "Title", - "reqd": 0 - }, - { - "fieldname": "slide_5_subtitle", - "fieldtype": "Small Text", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "slide_5_primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label", - "reqd": 0 - }, - { - "fieldname": "slide_5_primary_action", - "fieldtype": "Data", - "label": "Primary Action", - "reqd": 0 - }, - { - "fieldname": "slide_5_content_align", - "fieldtype": "Select", - "label": "Content Align", - "options": "Left\nCentre\nRight", - "reqd": 0 - }, - { - "fieldname": "slide_5_theme", - "fieldtype": "Select", - "label": "Slide Theme", - "options": "Dark\nLight", - "reqd": 0 - } - ], - "idx": 2, - "modified": "2023-05-12 15:03:57.604060", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Hero Slider", - "owner": "Administrator", - "standard": 1, - "template": "", - "type": "Section" -} \ No newline at end of file diff --git a/erpnext/e_commerce/web_template/item_card_group/__init__.py b/erpnext/e_commerce/web_template/item_card_group/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 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 deleted file mode 100644 index 07952f056a52..000000000000 --- a/erpnext/e_commerce/web_template/item_card_group/item_card_group.html +++ /dev/null @@ -1,37 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %} - -
-
-
- {%- if title -%} -

{{ title }}

- {%- endif -%} - {%- if subtitle -%} -

{{ subtitle }}

- {%- endif -%} -
-
- {%- if primary_action -%} - - {{ primary_action_label }} - - {%- endif -%} -
-
- -
- {%- for index in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'] -%} - {%- set item = values['card_' + index + '_item'] -%} - {%- if item -%} - {%- set web_item = frappe.get_doc("Website Item", item) -%} - {{ item_card( - web_item, is_featured=values['card_' + index + '_featured'], - is_full_width=True, align="Center" - ) }} - {%- endif -%} - {%- endfor -%} -
-
- - diff --git a/erpnext/e_commerce/web_template/item_card_group/item_card_group.json b/erpnext/e_commerce/web_template/item_card_group/item_card_group.json deleted file mode 100644 index ad9e2a7b243f..000000000000 --- a/erpnext/e_commerce/web_template/item_card_group/item_card_group.json +++ /dev/null @@ -1,270 +0,0 @@ -{ - "__unsaved": 1, - "creation": "2020-11-17 15:35:05.285322", - "docstatus": 0, - "doctype": "Web Template", - "fields": [ - { - "fieldname": "title", - "fieldtype": "Data", - "label": "Title", - "reqd": 1 - }, - { - "fieldname": "subtitle", - "fieldtype": "Data", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "primary_action_label", - "fieldtype": "Data", - "label": "Primary Action Label", - "reqd": 0 - }, - { - "fieldname": "primary_action", - "fieldtype": "Data", - "label": "Primary Action", - "reqd": 0 - }, - { - "fieldname": "card_1", - "fieldtype": "Section Break", - "label": "Card 1", - "reqd": 0 - }, - { - "fieldname": "card_1_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_1_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_2", - "fieldtype": "Section Break", - "label": "Card 2", - "reqd": 0 - }, - { - "fieldname": "card_2_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_2_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_3", - "fieldtype": "Section Break", - "label": "Card 3", - "options": "", - "reqd": 0 - }, - { - "fieldname": "card_3_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_3_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_4", - "fieldtype": "Section Break", - "label": "Card 4", - "reqd": 0 - }, - { - "fieldname": "card_4_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_4_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_5", - "fieldtype": "Section Break", - "label": "Card 5", - "reqd": 0 - }, - { - "fieldname": "card_5_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_5_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_6", - "fieldtype": "Section Break", - "label": "Card 6", - "reqd": 0 - }, - { - "fieldname": "card_6_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_6_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_7", - "fieldtype": "Section Break", - "label": "Card 7", - "reqd": 0 - }, - { - "fieldname": "card_7_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_7_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_8", - "fieldtype": "Section Break", - "label": "Card 8", - "reqd": 0 - }, - { - "fieldname": "card_8_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_8_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_9", - "fieldtype": "Section Break", - "label": "Card 9", - "reqd": 0 - }, - { - "fieldname": "card_9_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_9_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_10", - "fieldtype": "Section Break", - "label": "Card 10", - "reqd": 0 - }, - { - "fieldname": "card_10_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_10_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_11", - "fieldtype": "Section Break", - "label": "Card 11", - "reqd": 0 - }, - { - "fieldname": "card_11_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_11_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - }, - { - "fieldname": "card_12", - "fieldtype": "Section Break", - "label": "Card 12", - "reqd": 0 - }, - { - "fieldname": "card_12_item", - "fieldtype": "Link", - "label": "Website Item", - "options": "Website Item", - "reqd": 0 - }, - { - "fieldname": "card_12_featured", - "fieldtype": "Check", - "label": "Featured", - "reqd": 0 - } - ], - "idx": 0, - "modified": "2021-12-21 14:44:59.821335", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Item Card Group", - "owner": "Administrator", - "standard": 1, - "template": "", - "type": "Section" -} \ No newline at end of file diff --git a/erpnext/e_commerce/web_template/product_card/__init__.py b/erpnext/e_commerce/web_template/product_card/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/web_template/product_card/product_card.html b/erpnext/e_commerce/web_template/product_card/product_card.html deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/web_template/product_card/product_card.json b/erpnext/e_commerce/web_template/product_card/product_card.json deleted file mode 100644 index 2eb73741efb9..000000000000 --- a/erpnext/e_commerce/web_template/product_card/product_card.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "__unsaved": 1, - "creation": "2020-11-17 15:28:47.809342", - "docstatus": 0, - "doctype": "Web Template", - "fields": [ - { - "fieldname": "item", - "fieldtype": "Link", - "label": "Item", - "options": "Item", - "reqd": 0 - }, - { - "fieldname": "featured", - "fieldtype": "Check", - "label": "Featured", - "options": "", - "reqd": 0 - } - ], - "idx": 0, - "modified": "2021-02-24 16:05:17.926610", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Product Card", - "owner": "Administrator", - "standard": 1, - "template": "", - "type": "Component" -} \ No newline at end of file diff --git a/erpnext/e_commerce/web_template/product_category_cards/__init__.py b/erpnext/e_commerce/web_template/product_category_cards/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html deleted file mode 100644 index 6d75a8b1d5ee..000000000000 --- a/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html +++ /dev/null @@ -1,47 +0,0 @@ -{%- macro card(title, image, url, text_primary=False) -%} -{%- set align_class = resolve_class({ - 'text-right': text_primary, - 'text-centre': align == 'Center', - 'text-left': align == 'Left', -}) -%} -
- {% if image %} - {{ title }} - {% else %} -
- - {{ frappe.utils.get_abbr(title or '') }} - -
- {% endif %} - -
- {{ title or '' }} -
- -
-{%- endmacro -%} - -
- {%- if title -%} -

{{ title }}

- {%- endif -%} - {%- if subtitle -%} -

{{ subtitle }}

- {%- endif -%} - -
-
- {%- for index in ['1', '2', '3', '4', '5', '6', '7', '8'] -%} - {%- set category = values['category_' + index] -%} - {%- if category -%} - {%- set category = frappe.get_doc("Item Group", category) -%} - {{ card(category.name, category.image, category.route) }} - {%- endif -%} - {%- endfor -%} -
-
-
- - diff --git a/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json deleted file mode 100644 index 0202165d08eb..000000000000 --- a/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "__unsaved": 1, - "creation": "2020-11-17 15:25:50.855934", - "docstatus": 0, - "doctype": "Web Template", - "fields": [ - { - "fieldname": "title", - "fieldtype": "Data", - "label": "Title", - "reqd": 1 - }, - { - "fieldname": "subtitle", - "fieldtype": "Data", - "label": "Subtitle", - "reqd": 0 - }, - { - "fieldname": "category_1", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_2", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_3", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_4", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_5", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_6", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_7", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - }, - { - "fieldname": "category_8", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 0 - } - ], - "idx": 0, - "modified": "2021-02-24 16:03:33.835635", - "modified_by": "Administrator", - "module": "E-commerce", - "name": "Product Category Cards", - "owner": "Administrator", - "standard": 1, - "template": "", - "type": "Section" -} \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 33b82846b79e..5483a10b5790 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -52,11 +52,7 @@ filters_config = "erpnext.startup.filters.get_filters_config" additional_print_settings = "erpnext.controllers.print_settings.get_print_settings" -on_session_creation = [ - "erpnext.portal.utils.create_customer_or_supplier", - "erpnext.e_commerce.shopping_cart.utils.set_cart_count", -] -on_logout = "erpnext.e_commerce.shopping_cart.utils.clear_cart_count" +on_session_creation = "erpnext.portal.utils.create_customer_or_supplier" treeviews = [ "Account", @@ -90,15 +86,11 @@ } # website -update_website_context = [ - "erpnext.e_commerce.shopping_cart.utils.update_website_context", -] -my_account_context = "erpnext.e_commerce.shopping_cart.utils.update_my_account_context" webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context" calendars = ["Task", "Work Order", "Sales Order", "Holiday List", "ToDo"] -website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner"] +website_generators = ["BOM", "Sales Partner"] website_context = { "favicon": "/assets/erpnext/images/erpnext-favicon.svg", @@ -349,9 +341,6 @@ "Event": { "after_insert": "erpnext.crm.utils.link_events_with_prospect", }, - "Sales Taxes and Charges Template": { - "on_update": "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.validate_cart_settings" - }, "Sales Invoice": { "on_submit": [ "erpnext.regional.create_transaction_log", diff --git a/erpnext/modules.txt b/erpnext/modules.txt index dcb421298d8e..c53cdf467d2f 100644 --- a/erpnext/modules.txt +++ b/erpnext/modules.txt @@ -17,5 +17,4 @@ Quality Management Communication Telephony Bulk Transaction -E-commerce -Subcontracting \ No newline at end of file +Subcontracting diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 01fdcc2c6ed5..d59fe0ec4c8f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -223,9 +223,6 @@ execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Catego erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021 erpnext.patches.v13_0.fix_invoice_statuses -erpnext.patches.v13_0.create_website_items #30-09-2021 -erpnext.patches.v13_0.populate_e_commerce_settings -erpnext.patches.v13_0.make_homepage_products_website_items erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields @@ -242,7 +239,6 @@ erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.healthcare_deprecation_warning erpnext.patches.v13_0.item_naming_series_not_mandatory erpnext.patches.v13_0.update_category_in_ltds_certificate -erpnext.patches.v13_0.fetch_thumbnail_in_website_items erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit erpnext.patches.v14_0.migrate_crm_settings erpnext.patches.v13_0.wipe_serial_no_field_for_0_qty @@ -257,6 +253,7 @@ erpnext.patches.v13_0.show_hr_payroll_deprecation_warning erpnext.patches.v13_0.reset_corrupt_defaults erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair erpnext.patches.v15_0.delete_taxjar_doctypes +erpnext.patches.v15_0.delete_ecommerce_doctypes erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets erpnext.patches.v14_0.update_reference_due_date_in_journal_entry erpnext.patches.v15_0.saudi_depreciation_warning @@ -277,8 +274,6 @@ erpnext.patches.v14_0.delete_datev_doctypes erpnext.patches.v14_0.rearrange_company_fields erpnext.patches.v13_0.update_sane_transfer_against erpnext.patches.v14_0.migrate_cost_center_allocations -erpnext.patches.v13_0.convert_to_website_item_in_item_card_group_template -erpnext.patches.v13_0.shopping_cart_to_ecommerce erpnext.patches.v13_0.update_reserved_qty_closed_wo erpnext.patches.v13_0.update_exchange_rate_settings erpnext.patches.v14_0.delete_amazon_mws_doctype @@ -288,7 +283,6 @@ erpnext.patches.v14_0.update_batch_valuation_flag erpnext.patches.v14_0.delete_non_profit_doctypes erpnext.patches.v13_0.set_return_against_in_pos_invoice_references erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022 -erpnext.patches.v13_0.copy_custom_field_filters_to_website_item erpnext.patches.v13_0.change_default_item_manufacturer_fieldtype erpnext.patches.v13_0.requeue_recoverable_reposts erpnext.patches.v14_0.discount_accounting_separation @@ -347,4 +341,4 @@ execute:frappe.delete_doc("Page", "welcome-to-erpnext") erpnext.patches.v15_0.delete_payment_gateway_doctypes erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item # below migration patch should always run last -erpnext.patches.v14_0.migrate_gl_to_payment_ledger +erpnext.patches.v14_0.migrate_gl_to_payment_ledger \ No newline at end of file diff --git a/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py b/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py deleted file mode 100644 index 1bac0fdbf0b5..000000000000 --- a/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py +++ /dev/null @@ -1,60 +0,0 @@ -import json -from typing import List, Union - -import frappe - -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item - - -def execute(): - """ - Convert all Item links to Website Item link values in - exisitng 'Item Card Group' Web Page Block data. - """ - frappe.reload_doc("e_commerce", "web_template", "item_card_group") - - blocks = frappe.db.get_all( - "Web Page Block", - filters={"web_template": "Item Card Group"}, - fields=["parent", "web_template_values", "name"], - ) - - fields = generate_fields_to_edit() - - for block in blocks: - web_template_value = json.loads(block.get("web_template_values")) - - for field in fields: - item = web_template_value.get(field) - if not item: - continue - - if frappe.db.exists("Website Item", {"item_code": item}): - website_item = frappe.db.get_value("Website Item", {"item_code": item}) - else: - website_item = make_new_website_item(item) - - if website_item: - web_template_value[field] = website_item - - frappe.db.set_value( - "Web Page Block", block.name, "web_template_values", json.dumps(web_template_value) - ) - - -def generate_fields_to_edit() -> List: - fields = [] - for i in range(1, 13): - fields.append(f"card_{i}_item") # fields like 'card_1_item', etc. - - return fields - - -def make_new_website_item(item: str) -> Union[str, None]: - try: - doc = frappe.get_doc("Item", item) - web_item = make_website_item(doc) # returns [website_item.name, item_name] - return web_item[0] - except Exception: - doc.log_error("Website Item creation failed") - return None diff --git a/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py b/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py deleted file mode 100644 index 4ad572fdb09a..000000000000 --- a/erpnext/patches/v13_0/copy_custom_field_filters_to_website_item.py +++ /dev/null @@ -1,94 +0,0 @@ -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_field - - -def execute(): - "Add Field Filters, that are not standard fields in Website Item, as Custom Fields." - - def move_table_multiselect_data(docfield): - "Copy child table data (Table Multiselect) from Item to Website Item for a docfield." - table_multiselect_data = get_table_multiselect_data(docfield) - field = docfield.fieldname - - for row in table_multiselect_data: - # add copied multiselect data rows in Website Item - web_item = frappe.db.get_value("Website Item", {"item_code": row.parent}) - web_item_doc = frappe.get_doc("Website Item", web_item) - - child_doc = frappe.new_doc(docfield.options, parent_doc=web_item_doc, parentfield=field) - - for field in ["name", "creation", "modified", "idx"]: - row[field] = None - - child_doc.update(row) - - child_doc.parenttype = "Website Item" - child_doc.parent = web_item - - child_doc.insert() - - def get_table_multiselect_data(docfield): - child_table = frappe.qb.DocType(docfield.options) - item = frappe.qb.DocType("Item") - - table_multiselect_data = ( # query table data for field - frappe.qb.from_(child_table) - .join(item) - .on(item.item_code == child_table.parent) - .select(child_table.star) - .where((child_table.parentfield == docfield.fieldname) & (item.published_in_website == 1)) - ).run(as_dict=True) - - return table_multiselect_data - - settings = frappe.get_doc("E Commerce Settings") - - if not (settings.enable_field_filters or settings.filter_fields): - return - - item_meta = frappe.get_meta("Item") - valid_item_fields = [ - df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"] - ] - - web_item_meta = frappe.get_meta("Website Item") - valid_web_item_fields = [ - df.fieldname for df in web_item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"] - ] - - for row in settings.filter_fields: - # skip if illegal field - if row.fieldname not in valid_item_fields: - continue - - # if Item field is not in Website Item, add it as a custom field - if row.fieldname not in valid_web_item_fields: - df = item_meta.get_field(row.fieldname) - create_custom_field( - "Website Item", - dict( - owner="Administrator", - fieldname=df.fieldname, - label=df.label, - fieldtype=df.fieldtype, - options=df.options, - description=df.description, - read_only=df.read_only, - no_copy=df.no_copy, - insert_after="on_backorder", - ), - ) - - # map field values - if df.fieldtype == "Table MultiSelect": - move_table_multiselect_data(df) - else: - frappe.db.sql( # nosemgrep - """ - UPDATE `tabWebsite Item` wi, `tabItem` i - SET wi.{0} = i.{0} - WHERE wi.item_code = i.item_code - """.format( - row.fieldname - ) - ) diff --git a/erpnext/patches/v13_0/create_website_items.py b/erpnext/patches/v13_0/create_website_items.py deleted file mode 100644 index b010f0ecc608..000000000000 --- a/erpnext/patches/v13_0/create_website_items.py +++ /dev/null @@ -1,85 +0,0 @@ -import frappe - -from erpnext.e_commerce.doctype.website_item.website_item import make_website_item - - -def execute(): - frappe.reload_doc("e_commerce", "doctype", "website_item") - frappe.reload_doc("e_commerce", "doctype", "website_item_tabbed_section") - frappe.reload_doc("e_commerce", "doctype", "website_offer") - frappe.reload_doc("e_commerce", "doctype", "recommended_items") - frappe.reload_doc("e_commerce", "doctype", "e_commerce_settings") - frappe.reload_doc("stock", "doctype", "item") - - item_fields = [ - "item_code", - "item_name", - "item_group", - "stock_uom", - "brand", - "has_variants", - "variant_of", - "description", - "weightage", - ] - web_fields_to_map = [ - "route", - "slideshow", - "website_image_alt", - "website_warehouse", - "web_long_description", - "website_content", - "website_image", - "thumbnail", - ] - - # get all valid columns (fields) from Item master DB schema - item_table_fields = frappe.db.sql("desc `tabItem`", as_dict=1) # nosemgrep - item_table_fields = [d.get("Field") for d in item_table_fields] - - # prepare fields to query from Item, check if the web field exists in Item master - web_query_fields = [] - for web_field in web_fields_to_map: - if web_field in item_table_fields: - web_query_fields.append(web_field) - item_fields.append(web_field) - - # check if the filter fields exist in Item master - or_filters = {} - for field in ["show_in_website", "show_variant_in_website"]: - if field in item_table_fields: - or_filters[field] = 1 - - if not web_query_fields or not or_filters: - # web fields to map are not present in Item master schema - # most likely a fresh installation that doesnt need this patch - return - - items = frappe.db.get_all("Item", fields=item_fields, or_filters=or_filters) - total_count = len(items) - - for count, item in enumerate(items, start=1): - if frappe.db.exists("Website Item", {"item_code": item.item_code}): - continue - - # make new website item from item (publish item) - website_item = make_website_item(item, save=False) - website_item.ranking = item.get("weightage") - - for field in web_fields_to_map: - website_item.update({field: item.get(field)}) - - website_item.save() - - # move Website Item Group & Website Specification table to Website Item - for doctype in ("Website Item Group", "Item Website Specification"): - frappe.db.set_value( - doctype, - {"parenttype": "Item", "parent": item.item_code}, # filters - {"parenttype": "Website Item", "parent": website_item.name}, # value dict - ) - - if count % 20 == 0: # commit after every 20 items - frappe.db.commit() - - frappe.utils.update_progress_bar("Creating Website Items", count, total_count) diff --git a/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py b/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py deleted file mode 100644 index 9197d86058d0..000000000000 --- a/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py +++ /dev/null @@ -1,11 +0,0 @@ -import frappe - - -def execute(): - if frappe.db.has_column("Item", "thumbnail"): - website_item = frappe.qb.DocType("Website Item").as_("wi") - item = frappe.qb.DocType("Item") - - frappe.qb.update(website_item).inner_join(item).on(website_item.item_code == item.item_code).set( - website_item.thumbnail, item.thumbnail - ).where(website_item.website_image.notnull() & website_item.thumbnail.isnull()).run() diff --git a/erpnext/patches/v13_0/make_homepage_products_website_items.py b/erpnext/patches/v13_0/make_homepage_products_website_items.py deleted file mode 100644 index 50bfd358ea5c..000000000000 --- a/erpnext/patches/v13_0/make_homepage_products_website_items.py +++ /dev/null @@ -1,15 +0,0 @@ -import frappe - - -def execute(): - homepage = frappe.get_doc("Homepage") - - for row in homepage.products: - web_item = frappe.db.get_value("Website Item", {"item_code": row.item_code}, "name") - if not web_item: - continue - - row.item_code = web_item - - homepage.flags.ignore_mandatory = True - homepage.save() diff --git a/erpnext/patches/v13_0/populate_e_commerce_settings.py b/erpnext/patches/v13_0/populate_e_commerce_settings.py deleted file mode 100644 index ecf512b011da..000000000000 --- a/erpnext/patches/v13_0/populate_e_commerce_settings.py +++ /dev/null @@ -1,68 +0,0 @@ -import frappe -from frappe.utils import cint - - -def execute(): - frappe.reload_doc("e_commerce", "doctype", "e_commerce_settings") - frappe.reload_doc("portal", "doctype", "website_filter_field") - frappe.reload_doc("portal", "doctype", "website_attribute") - - products_settings_fields = [ - "hide_variants", - "products_per_page", - "enable_attribute_filters", - "enable_field_filters", - ] - - shopping_cart_settings_fields = [ - "enabled", - "show_attachments", - "show_price", - "show_stock_availability", - "enable_variants", - "show_contact_us_button", - "show_quantity_in_website", - "show_apply_coupon_code_in_website", - "allow_items_not_in_stock", - "company", - "price_list", - "default_customer_group", - "quotation_series", - "enable_checkout", - "payment_success_url", - "payment_gateway_account", - "save_quotations_as_draft", - ] - - settings = frappe.get_doc("E Commerce Settings") - - def map_into_e_commerce_settings(doctype, fields): - singles = frappe.qb.DocType("Singles") - query = ( - frappe.qb.from_(singles) - .select(singles["field"], singles.value) - .where((singles.doctype == doctype) & (singles["field"].isin(fields))) - ) - data = query.run(as_dict=True) - - # {'enable_attribute_filters': '1', ...} - mapper = {row.field: row.value for row in data} - - for key, value in mapper.items(): - value = cint(value) if (value and value.isdigit()) else value - settings.update({key: value}) - - settings.save() - - # shift data to E Commerce Settings - map_into_e_commerce_settings("Products Settings", products_settings_fields) - map_into_e_commerce_settings("Shopping Cart Settings", shopping_cart_settings_fields) - - # move filters and attributes tables to E Commerce Settings from Products Settings - for doctype in ("Website Filter Field", "Website Attribute"): - frappe.db.set_value( - doctype, - {"parent": "Products Settings"}, - {"parenttype": "E Commerce Settings", "parent": "E Commerce Settings"}, - update_modified=False, - ) diff --git a/erpnext/patches/v13_0/shopping_cart_to_ecommerce.py b/erpnext/patches/v13_0/shopping_cart_to_ecommerce.py deleted file mode 100644 index 35710a9bb4a2..000000000000 --- a/erpnext/patches/v13_0/shopping_cart_to_ecommerce.py +++ /dev/null @@ -1,29 +0,0 @@ -import click -import frappe - - -def execute(): - - frappe.delete_doc("DocType", "Shopping Cart Settings", ignore_missing=True) - frappe.delete_doc("DocType", "Products Settings", ignore_missing=True) - frappe.delete_doc("DocType", "Supplier Item Group", ignore_missing=True) - - if frappe.db.get_single_value("E Commerce Settings", "enabled"): - notify_users() - - -def notify_users(): - - click.secho( - "Shopping cart and Product settings are merged into E-commerce settings.\n" - "Checkout the documentation to learn more:" - "https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce", - fg="yellow", - ) - - note = frappe.new_doc("Note") - note.title = "New E-Commerce Module" - note.public = 1 - note.notify_on_login = 1 - note.content = """

You are seeing this message because Shopping Cart is enabled on your site.


Shopping Cart Settings and Products settings are now merged into "E Commerce Settings".


You can learn about new and improved E-Commerce features in the official documentation.

  1. https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce


""" - note.insert(ignore_mandatory=True) diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py index a53adf1a83a0..9a2a39fb78c1 100644 --- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py +++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py @@ -11,6 +11,9 @@ def execute(): asset_depreciation_schedules_map = get_asset_depreciation_schedules_map() for asset in assets: + if not asset_depreciation_schedules_map.get(asset.name): + continue + depreciation_schedules = asset_depreciation_schedules_map[asset.name] for fb_row in asset_finance_books_map[asset.name]: diff --git a/erpnext/patches/v15_0/delete_ecommerce_doctypes.py b/erpnext/patches/v15_0/delete_ecommerce_doctypes.py new file mode 100644 index 000000000000..af0398782e8b --- /dev/null +++ b/erpnext/patches/v15_0/delete_ecommerce_doctypes.py @@ -0,0 +1,30 @@ +import click +import frappe + + +def execute(): + if "webshop" in frappe.get_installed_apps(): + return + + if not frappe.db.table_exists("Website Item"): + return + + doctypes = [ + "E Commerce Settings", + "Website Item", + "Recommended Items", + "Item Review", + "Wishlist Item", + "Wishlist", + "Website Offer", + "Website Item Tabbed Section", + ] + + for doctype in doctypes: + frappe.delete_doc("DocType", doctype, ignore_missing=True) + + click.secho( + "ECommerce is renamed and moved to a separate app" + "Please install the app for ECommerce features: https://github.com/frappe/webshop", + fg="yellow", + ) diff --git a/erpnext/portal/doctype/homepage/homepage.js b/erpnext/portal/doctype/homepage/homepage.js index 59f808a3158d..67979044242d 100644 --- a/erpnext/portal/doctype/homepage/homepage.js +++ b/erpnext/portal/doctype/homepage/homepage.js @@ -19,12 +19,3 @@ frappe.ui.form.on('Homepage', { }); }, }); - -frappe.ui.form.on('Homepage Featured Product', { - view: function(frm, cdt, cdn) { - var child= locals[cdt][cdn]; - if (child.item_code && child.route) { - window.open('/' + child.route, '_blank'); - } - } -}); diff --git a/erpnext/portal/doctype/homepage/homepage.json b/erpnext/portal/doctype/homepage/homepage.json index 73f816d4d49d..2b891f726806 100644 --- a/erpnext/portal/doctype/homepage/homepage.json +++ b/erpnext/portal/doctype/homepage/homepage.json @@ -15,10 +15,7 @@ "description", "hero_image", "slideshow", - "hero_section", - "products_section", - "products_url", - "products" + "hero_section" ], "fields": [ { @@ -86,30 +83,11 @@ "fieldtype": "Link", "label": "Homepage Section", "options": "Homepage Section" - }, - { - "fieldname": "products_section", - "fieldtype": "Section Break", - "label": "Products" - }, - { - "default": "/all-products", - "fieldname": "products_url", - "fieldtype": "Data", - "label": "URL for \"All Products\"" - }, - { - "description": "Products to be shown on website homepage", - "fieldname": "products", - "fieldtype": "Table", - "label": "Products", - "options": "Homepage Featured Product", - "width": "40px" } ], "issingle": 1, "links": [], - "modified": "2021-02-18 13:29:29.531639", + "modified": "2022-12-19 21:10:29.127277", "modified_by": "Administrator", "module": "Portal", "name": "Homepage", @@ -138,6 +116,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "company", "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/portal/doctype/homepage/homepage.py b/erpnext/portal/doctype/homepage/homepage.py index 0d2e36078818..c0a0c07d7d53 100644 --- a/erpnext/portal/doctype/homepage/homepage.py +++ b/erpnext/portal/doctype/homepage/homepage.py @@ -12,26 +12,3 @@ def validate(self): if not self.description: self.description = frappe._("This is an example website auto-generated from ERPNext") delete_page_cache("home") - - def setup_items(self): - for d in frappe.get_all( - "Website Item", - fields=["name", "item_name", "description", "website_image", "route"], - filters={"published": 1}, - limit=3, - ): - - doc = frappe.get_doc("Website Item", d.name) - if not doc.route: - # set missing route - doc.save() - self.append( - "products", - dict( - item_code=d.name, - item_name=d.item_name, - description=d.description, - image=d.website_image, - route=d.route, - ), - ) diff --git a/erpnext/portal/doctype/homepage_featured_product/__init__.py b/erpnext/portal/doctype/homepage_featured_product/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json b/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json deleted file mode 100644 index 63789e35b560..000000000000 --- a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "actions": [], - "autoname": "hash", - "creation": "2016-04-22 05:57:06.261401", - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "item_code", - "col_break1", - "item_name", - "view", - "section_break_5", - "description", - "column_break_7", - "image", - "thumbnail", - "route" - ], - "fields": [ - { - "bold": 1, - "fieldname": "item_code", - "fieldtype": "Link", - "in_filter": 1, - "in_list_view": 1, - "label": "Item", - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Website Item", - "print_width": "150px", - "reqd": 1, - "search_index": 1, - "width": "150px" - }, - { - "fieldname": "col_break1", - "fieldtype": "Column Break" - }, - { - "fetch_from": "item_code.item_name", - "fetch_if_empty": 1, - "fieldname": "item_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Item Name", - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "print_hide": 1, - "print_width": "150", - "read_only": 1, - "reqd": 1, - "width": "150" - }, - { - "fieldname": "view", - "fieldtype": "Button", - "in_list_view": 1, - "label": "View" - }, - { - "collapsible": 1, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "label": "Details" - }, - { - "fetch_from": "item_code.web_long_description", - "fieldname": "description", - "fieldtype": "Text Editor", - "in_filter": 1, - "in_list_view": 1, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Small Text", - "print_width": "300px", - "width": "300px" - }, - { - "fieldname": "column_break_7", - "fieldtype": "Column Break" - }, - { - "fetch_from": "item_code.website_image", - "fetch_if_empty": 1, - "fieldname": "image", - "fieldtype": "Attach Image", - "label": "Image" - }, - { - "fetch_from": "item_code.thumbnail", - "fieldname": "thumbnail", - "fieldtype": "Attach Image", - "hidden": 1, - "label": "Thumbnail" - }, - { - "fetch_from": "item_code.route", - "fieldname": "route", - "fieldtype": "Small Text", - "label": "route", - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-02-18 13:05:50.669311", - "modified_by": "Administrator", - "module": "Portal", - "name": "Homepage Featured Product", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py b/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py deleted file mode 100644 index c21461d631d3..000000000000 --- a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - - -from frappe.model.document import Document - - -class HomepageFeaturedProduct(Document): - pass diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py index c8b03e678bc2..903d4a6196c1 100644 --- a/erpnext/portal/utils.py +++ b/erpnext/portal/utils.py @@ -1,10 +1,4 @@ import frappe -from frappe.utils.nestedset import get_root_of - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.e_commerce.shopping_cart.cart import get_debtors_account def set_default_role(doc, method): @@ -56,26 +50,7 @@ def create_customer_or_supplier(): party = frappe.new_doc(doctype) fullname = frappe.utils.get_fullname(user) - if doctype == "Customer": - cart_settings = get_shopping_cart_settings() - - if cart_settings.enable_checkout: - debtors_account = get_debtors_account(cart_settings) - else: - debtors_account = "" - - party.update( - { - "customer_name": fullname, - "customer_type": "Individual", - "customer_group": cart_settings.default_customer_group, - "territory": get_root_of("Territory"), - } - ) - - if debtors_account: - party.update({"accounts": [{"company": cart_settings.company, "account": debtors_account}]}) - else: + if not doctype == "Customer": party.update( { "supplier_name": fullname, diff --git a/erpnext/public/js/customer_reviews.js b/erpnext/public/js/customer_reviews.js deleted file mode 100644 index e13ded6b489f..000000000000 --- a/erpnext/public/js/customer_reviews.js +++ /dev/null @@ -1,138 +0,0 @@ -$(() => { - class CustomerReviews { - constructor() { - this.bind_button_actions(); - this.start = 0; - this.page_length = 10; - } - - bind_button_actions() { - this.write_review(); - this.view_more(); - } - - write_review() { - //TODO: make dialog popup on stray page - $('.page_content').on('click', '.btn-write-review', (e) => { - // Bind action on write a review button - const $btn = $(e.currentTarget); - - let d = new frappe.ui.Dialog({ - title: __("Write a Review"), - fields: [ - {fieldname: "title", fieldtype: "Data", label: "Headline", reqd: 1}, - {fieldname: "rating", fieldtype: "Rating", label: "Overall Rating", reqd: 1}, - {fieldtype: "Section Break"}, - {fieldname: "comment", fieldtype: "Small Text", label: "Your Review"} - ], - primary_action: function() { - let data = d.get_values(); - frappe.call({ - method: "erpnext.e_commerce.doctype.item_review.item_review.add_item_review", - args: { - web_item: $btn.attr('data-web-item'), - title: data.title, - rating: data.rating, - comment: data.comment - }, - freeze: true, - freeze_message: __("Submitting Review ..."), - callback: (r) => { - if (!r.exc) { - frappe.msgprint({ - message: __("Thank you for submitting your review"), - title: __("Review Submitted"), - indicator: "green" - }); - d.hide(); - location.reload(); - } - } - }); - }, - primary_action_label: __('Submit') - }); - d.show(); - }); - } - - view_more() { - $('.page_content').on('click', '.btn-view-more', (e) => { - // Bind action on view more button - const $btn = $(e.currentTarget); - $btn.prop('disabled', true); - - this.start += this.page_length; - let me = this; - - frappe.call({ - method: "erpnext.e_commerce.doctype.item_review.item_review.get_item_reviews", - args: { - web_item: $btn.attr('data-web-item'), - start: me.start, - end: me.page_length - }, - callback: (result) => { - if (result.message) { - let res = result.message; - me.get_user_review_html(res.reviews); - - $btn.prop('disabled', false); - if (res.total_reviews <= (me.start + me.page_length)) { - $btn.hide(); - } - - } - } - }); - }); - - } - - get_user_review_html(reviews) { - let me = this; - let $content = $('.user-reviews'); - - reviews.forEach((review) => { - $content.append(` -
-
-

- ${__(review.review_title)} -

-
- ${me.get_review_stars(review.rating)} -
-
- -
-

- ${__(review.comment)} -

-
-
- ${__(review.customer)} - - ${__(review.published_on)} -
-
- `); - }); - } - - get_review_stars(rating) { - let stars = ``; - for (let i = 1; i < 6; i++) { - let fill_class = i <= rating ? 'star-click' : ''; - stars += ` - - - - `; - } - return stars; - } - } - - new CustomerReviews(); -}); \ No newline at end of file diff --git a/erpnext/public/js/erpnext-web.bundle.js b/erpnext/public/js/erpnext-web.bundle.js index cbe899dc0607..45c6a648ecd4 100644 --- a/erpnext/public/js/erpnext-web.bundle.js +++ b/erpnext/public/js/erpnext-web.bundle.js @@ -1,8 +1 @@ import "./website_utils"; -import "./wishlist"; -import "./shopping_cart"; -import "./customer_reviews"; -import "../../e_commerce/product_ui/list"; -import "../../e_commerce/product_ui/views"; -import "../../e_commerce/product_ui/grid"; -import "../../e_commerce/product_ui/search"; \ No newline at end of file diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js deleted file mode 100644 index d14740c1060d..000000000000 --- a/erpnext/public/js/shopping_cart.js +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -// shopping cart -frappe.provide("erpnext.e_commerce.shopping_cart"); -var shopping_cart = erpnext.e_commerce.shopping_cart; - -var getParams = function (url) { - var params = []; - var parser = document.createElement('a'); - parser.href = url; - var query = parser.search.substring(1); - var vars = query.split('&'); - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split('='); - params[pair[0]] = decodeURIComponent(pair[1]); - } - return params; -}; - -frappe.ready(function() { - var full_name = frappe.session && frappe.session.user_fullname; - // update user - if(full_name) { - $('.navbar li[data-label="User"] a') - .html(' ' + full_name); - } - // set coupon code and sales partner code - - var url_args = getParams(window.location.href); - - var referral_coupon_code = url_args['cc']; - var referral_sales_partner = url_args['sp']; - - var d = new Date(); - // expires within 30 minutes - d.setTime(d.getTime() + (0.02 * 24 * 60 * 60 * 1000)); - var expires = "expires="+d.toUTCString(); - if (referral_coupon_code) { - document.cookie = "referral_coupon_code=" + referral_coupon_code + ";" + expires + ";path=/"; - } - if (referral_sales_partner) { - document.cookie = "referral_sales_partner=" + referral_sales_partner + ";" + expires + ";path=/"; - } - referral_coupon_code=frappe.get_cookie("referral_coupon_code"); - referral_sales_partner=frappe.get_cookie("referral_sales_partner"); - - if (referral_coupon_code && $(".tot_quotation_discount").val()==undefined ) { - $(".txtcoupon").val(referral_coupon_code); - } - if (referral_sales_partner) { - $(".txtreferral_sales_partner").val(referral_sales_partner); - } - - // update login - shopping_cart.show_shoppingcart_dropdown(); - shopping_cart.set_cart_count(); - shopping_cart.show_cart_navbar(); -}); - -$.extend(shopping_cart, { - show_shoppingcart_dropdown: function() { - $(".shopping-cart").on('shown.bs.dropdown', function() { - if (!$('.shopping-cart-menu .cart-container').length) { - return frappe.call({ - method: 'erpnext.e_commerce.shopping_cart.cart.get_shopping_cart_menu', - callback: function(r) { - if (r.message) { - $('.shopping-cart-menu').html(r.message); - } - } - }); - } - }); - }, - - update_cart: function(opts) { - 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"; - }); - } else { - shopping_cart.freeze(); - return frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.update_cart", - args: { - item_code: opts.item_code, - qty: opts.qty, - additional_notes: opts.additional_notes !== undefined ? opts.additional_notes : undefined, - with_items: opts.with_items || 0 - }, - btn: opts.btn, - callback: function(r) { - shopping_cart.unfreeze(); - shopping_cart.set_cart_count(true); - if(opts.callback) - opts.callback(r); - } - }); - } - }, - - set_cart_count: function(animate=false) { - $(".intermediate-empty-cart").remove(); - - var cart_count = frappe.get_cookie("cart_count"); - if(frappe.session.user==="Guest") { - cart_count = 0; - } - - if(cart_count) { - $(".shopping-cart").toggleClass('hidden', false); - } - - var $cart = $('.cart-icon'); - var $badge = $cart.find("#cart-count"); - - if(parseInt(cart_count) === 0 || cart_count === undefined) { - $cart.css("display", "none"); - $(".cart-tax-items").hide(); - $(".btn-place-order").hide(); - $(".cart-payment-addresses").hide(); - - let intermediate_empty_cart_msg = ` -
- ${ __("Cart is Empty") } -
- `; - $(".cart-table").after(intermediate_empty_cart_msg); - } - else { - $cart.css("display", "inline"); - $("#cart-count").text(cart_count); - } - - if(cart_count) { - $badge.html(cart_count); - - if (animate) { - $cart.addClass("cart-animate"); - setTimeout(() => { - $cart.removeClass("cart-animate"); - }, 500); - } - } else { - $badge.remove(); - } - }, - - shopping_cart_update: function({item_code, qty, cart_dropdown, additional_notes}) { - shopping_cart.update_cart({ - item_code, - qty, - additional_notes, - with_items: 1, - btn: this, - callback: function(r) { - if(!r.exc) { - $(".cart-items").html(r.message.items); - $(".cart-tax-items").html(r.message.total); - $(".payment-summary").html(r.message.taxes_and_totals); - shopping_cart.set_cart_count(); - - if (cart_dropdown != true) { - $(".cart-icon").hide(); - } - } - }, - }); - }, - - show_cart_navbar: function () { - frappe.call({ - method: "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.is_cart_enabled", - callback: function(r) { - $(".shopping-cart").toggleClass('hidden', r.message ? false : true); - } - }); - }, - - toggle_button_class(button, remove, add) { - button.removeClass(remove); - button.addClass(add); - }, - - bind_add_to_cart_action() { - $('.page_content').on('click', '.btn-add-to-cart-list', (e) => { - 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.closest('.cart-action-container').addClass('d-flex'); - $btn.parent().find('.go-to-cart').removeClass('hidden'); - $btn.parent().find('.go-to-cart-grid').removeClass('hidden'); - $btn.parent().find('.cart-indicator').removeClass('hidden'); - - const item_code = $btn.data('item-code'); - erpnext.e_commerce.shopping_cart.update_cart({ - item_code, - qty: 1 - }); - - }); - }, - - freeze() { - if (window.location.pathname !== "/cart") return; - - if (!$('#freeze').length) { - let freeze = $('') - .appendTo("body"); - - setTimeout(function() { - freeze.addClass("show"); - }, 1); - } else { - $("#freeze").addClass("show"); - } - }, - - unfreeze() { - if ($('#freeze').length) { - let freeze = $('#freeze').removeClass("show"); - setTimeout(function() { - freeze.remove(); - }, 1); - } - } -}); diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js deleted file mode 100644 index f6599e9f6d1a..000000000000 --- a/erpnext/public/js/wishlist.js +++ /dev/null @@ -1,204 +0,0 @@ -frappe.provide("erpnext.e_commerce.wishlist"); -var wishlist = erpnext.e_commerce.wishlist; - -frappe.provide("erpnext.e_commerce.shopping_cart"); -var shopping_cart = erpnext.e_commerce.shopping_cart; - -$.extend(wishlist, { - set_wishlist_count: function(animate=false) { - // set badge count for wishlist icon - var wish_count = frappe.get_cookie("wish_count"); - if (frappe.session.user==="Guest") { - wish_count = 0; - } - - if (wish_count) { - $(".wishlist").toggleClass('hidden', false); - } - - var $wishlist = $('.wishlist-icon'); - var $badge = $wishlist.find("#wish-count"); - - if (parseInt(wish_count) === 0 || wish_count === undefined) { - $wishlist.css("display", "none"); - } else { - $wishlist.css("display", "inline"); - } - if (wish_count) { - $badge.html(wish_count); - if (animate) { - $wishlist.addClass('cart-animate'); - setTimeout(() => { - $wishlist.removeClass('cart-animate'); - }, 500); - } - } else { - $badge.remove(); - } - }, - - bind_move_to_cart_action: function() { - // move item to cart from wishlist - $('.page_content').on("click", ".btn-add-to-cart", (e) => { - const $move_to_cart_btn = $(e.currentTarget); - let item_code = $move_to_cart_btn.data("item-code"); - - shopping_cart.shopping_cart_update({ - item_code, - qty: 1, - cart_dropdown: true - }); - - let success_action = function() { - const $card_wrapper = $move_to_cart_btn.closest(".wishlist-card"); - $card_wrapper.addClass("wish-removed"); - }; - let args = { item_code: item_code }; - this.add_remove_from_wishlist("remove", args, success_action, null, true); - }); - }, - - bind_remove_action: function() { - // remove item from wishlist - let me = this; - - $('.page_content').on("click", ".remove-wish", (e) => { - const $remove_wish_btn = $(e.currentTarget); - let item_code = $remove_wish_btn.data("item-code"); - - let success_action = function() { - const $card_wrapper = $remove_wish_btn.closest(".wishlist-card"); - $card_wrapper.addClass("wish-removed"); - if (frappe.get_cookie("wish_count") == 0) { - $(".page_content").empty(); - me.render_empty_state(); - } - }; - let args = { item_code: item_code }; - this.add_remove_from_wishlist("remove", args, success_action); - }); - }, - - bind_wishlist_action() { - // 'wish'('like') or 'unwish' item in product listing - $('.page_content').on('click', '.like-action, .like-action-list', (e) => { - const $btn = $(e.currentTarget); - 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() { - erpnext.e_commerce.wishlist.set_wishlist_count(true); - }; - - 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')}; - 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); - }, - - add_remove_from_wishlist(action, args, success_action, failure_action, async=false) { - /* AJAX call to add or remove Item from Wishlist - action: "add" or "remove" - args: args for method (item_code, price, formatted_price), - success_action: method to execute on successs, - failure_action: method to execute on failure, - async: make call asynchronously (true/false). */ - if (frappe.session.user==="Guest") { - if (localStorage) { - localStorage.setItem("last_visited", window.location.pathname); - } - this.redirect_guest(); - } else { - let method = "erpnext.e_commerce.doctype.wishlist.wishlist.add_to_wishlist"; - if (action === "remove") { - method = "erpnext.e_commerce.doctype.wishlist.wishlist.remove_from_wishlist"; - } - - frappe.call({ - async: async, - type: "POST", - method: method, - args: args, - callback: function (r) { - if (r.exc) { - if (failure_action && (typeof failure_action === 'function')) { - failure_action(); - } - frappe.msgprint({ - message: __("Sorry, something went wrong. Please refresh."), - indicator: "red", title: __("Note") - }); - } else if (success_action && (typeof success_action === 'function')) { - success_action(); - } - } - }); - } - }, - - redirect_guest() { - frappe.call('erpnext.e_commerce.api.get_guest_redirect_on_action').then((res) => { - window.location.href = res.message || "/login"; - }); - }, - - render_empty_state() { - $(".page_content").append(` -
-
- Empty Cart -
-
${ __('Wishlist is empty !') }

-
- `); - } - -}); - -frappe.ready(function() { - if (window.location.pathname !== "/wishlist") { - $(".wishlist").toggleClass('hidden', true); - wishlist.set_wishlist_count(); - } else { - wishlist.bind_move_to_cart_action(); - wishlist.bind_remove_action(); - } - -}); \ No newline at end of file diff --git a/erpnext/public/scss/erpnext-web.bundle.scss b/erpnext/public/scss/erpnext-web.bundle.scss index 6ef1892a3df9..18d7c6cf4ec7 100644 --- a/erpnext/public/scss/erpnext-web.bundle.scss +++ b/erpnext/public/scss/erpnext-web.bundle.scss @@ -1,2 +1 @@ -@import "./shopping_cart"; @import "./website"; diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss deleted file mode 100644 index 6ae464d2c21e..000000000000 --- a/erpnext/public/scss/shopping_cart.scss +++ /dev/null @@ -1,1381 +0,0 @@ -@import "frappe/public/scss/common/mixins"; - -:root { - --green-info: #38A160; - --product-bg-color: white; - --body-bg-color: var(--gray-50); -} - -body.product-page { - background: var(--body-bg-color); -} - -.item-breadcrumbs { - .breadcrumb-container { - a { - color: var(--gray-900); - } - } -} - -.carousel-control { - height: 42px; - width: 42px; - display: flex; - align-items: center; - justify-content: center; - background: white; - box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.08), 0px 1px 2px 1px rgba(0, 0, 0, 0.06); - border-radius: 100px; -} - -.carousel-control-prev, -.carousel-control-next { - opacity: 1; - width: 8%; - - @media (max-width: 1200px) { - width: 10%; - } - @media (max-width: 768px) { - width: 15%; - } -} - -.carousel-body { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -} - -.carousel-content { - max-width: 400px; - margin-left: 5rem; - margin-right: 5rem; -} - -.card { - border: none; -} - -.product-category-section { - .card:hover { - box-shadow: 0px 16px 45px 6px rgba(0, 0, 0, 0.08), 0px 8px 10px -10px rgba(0, 0, 0, 0.04); - } - - .card-grid { - display: grid; - grid-gap: 15px; - grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); - } -} - -.no-image-item { - height: 340px; - width: 340px; - background: var(--gray-100); - border-radius: var(--border-radius); - font-size: 2rem; - color: var(--gray-500); - display: flex; - align-items: center; - justify-content: center; -} - -.item-card-group-section { - .card { - height: 100%; - align-items: center; - justify-content: center; - - &:hover { - box-shadow: 0px 16px 60px rgba(0, 0, 0, 0.08), 0px 8px 30px -20px rgba(0, 0, 0, 0.04); - transition: box-shadow 400ms; - } - } - - .card:hover, .card:focus-within { - .btn-add-to-cart-list { - visibility: visible; - } - .like-action { - visibility: visible; - } - .btn-explore-variants { - visibility: visible; - } - } - - - .card-img-container { - height: 210px; - width: 100%; - } - - .card-img { - max-height: 210px; - object-fit: contain; - margin-top: 1.25rem; - } - - .no-image { - @include flex(flex, center, center, null); - height: 220px; - background: var(--gray-100); - width: 100%; - border-radius: var(--border-radius) var(--border-radius) 0 0; - font-size: 2rem; - color: var(--gray-500); - } - - .no-image-list { - @include flex(flex, center, center, null); - height: 150px; - background: var(--gray-100); - border-radius: var(--border-radius); - font-size: 2rem; - color: var(--gray-500); - margin-top: 15px; - margin-bottom: 15px; - } - - .card-body-flex { - display: flex; - flex-direction: column; - } - - .product-title { - font-size: 14px; - color: var(--gray-800); - font-weight: 500; - } - - .product-description { - font-size: 12px; - color: var(--text-color); - margin: 20px 0; - display: -webkit-box; - -webkit-line-clamp: 6; - -webkit-box-orient: vertical; - - p { - margin-bottom: 0.5rem; - } - } - - .product-category { - font-size: 13px; - color: var(--text-muted); - margin: var(--margin-sm) 0; - } - - .product-price { - font-size: 18px; - font-weight: 600; - color: var(--text-color); - margin: var(--margin-sm) 0; - margin-bottom: auto !important; - - .striked-price { - font-weight: 500; - font-size: 15px; - color: var(--gray-500); - } - } - - .product-info-green { - color: var(--green-info); - font-weight: 600; - } - - .item-card { - padding: var(--padding-sm); - min-width: 300px; - } - - .wishlist-card { - padding: var(--padding-sm); - min-width: 260px; - .card-body-flex { - display: flex; - flex-direction: column; - } - } -} - -#products-list-area, #products-grid-area { - padding: 0 5px; -} - -.list-row { - background-color: white; - padding-bottom: 1rem; - padding-top: 1.5rem !important; - border-radius: 8px; - border-bottom: 1px solid var(--gray-50); - - &:hover, &:focus-within { - box-shadow: 0px 16px 60px rgba(0, 0, 0, 0.08), 0px 8px 30px -20px rgba(0, 0, 0, 0.04); - transition: box-shadow 400ms; - - .btn-add-to-cart-list { - visibility: visible; - } - .like-action-list { - visibility: visible; - } - .btn-explore-variants { - visibility: visible; - } - } - - .product-code { - padding-top: 0 !important; - } - - .btn-explore-variants { - min-width: 135px; - max-height: 30px; - float: right; - padding: 0.25rem 1rem; - } -} - -[data-doctype="Item Group"], -#page-index { - .page-header { - font-size: 20px; - font-weight: 700; - color: var(--text-color); - } - - .filters-section { - .title-section { - border-bottom: 1px solid var(--table-border-color); - } - - .filter-title { - font-weight: 500; - } - - .clear-filters { - font-size: 13px; - } - - .filter-lookup-input { - background-color: white; - border: 1px solid var(--gray-300); - - &:focus { - border: 1px solid var(--primary); - } - } - - .filter-label { - font-size: 11px; - font-weight: 600; - color: var(--gray-700); - text-transform: uppercase; - } - - .filter-block { - border-bottom: 1px solid var(--table-border-color); - } - - .checkbox { - .label-area { - font-size: 13px; - color: var(--gray-800); - } - } - } -} - -.product-filter { - width: 14px !important; - height: 14px !important; -} - -.discount-filter { - &:before { - width: 14px !important; - height: 14px !important; - } -} - -.list-image { - border: none !important; - overflow: hidden; - max-height: 200px; - background-color: white; -} - -.product-container { - @include card($padding: var(--padding-md)); - background-color: var(--product-bg-color) !important; - min-height: fit-content; - - .product-details { - max-width: 50%; - - .btn-add-to-cart { - font-size: 14px; - } - } - - &.item-main { - .product-image { - width: 100%; - } - } - - .expand { - max-width: 100% !important; // expand in absence of slideshow - } - - @media (max-width: 789px) { - .product-details { - max-width: 90% !important; - - .btn-add-to-cart { - font-size: 14px; - } - } - } - - .btn-add-to-wishlist { - svg use { - --icon-stroke: #F47A7A; - } - } - - .btn-view-in-wishlist { - svg use { - fill: #F47A7A; - --icon-stroke: none; - } - } - - .product-title { - font-size: 16px; - font-weight: 600; - color: var(--text-color); - padding: 0 !important; - } - - .product-description { - font-size: 13px; - color: var(--gray-800); - } - - .product-image { - border-color: var(--table-border-color) !important; - padding: 15px; - - @media (max-width: var(--md-width)) { - height: 300px; - width: 300px; - } - - @media (min-width: var(--lg-width)) { - height: 350px; - width: 350px; - } - - img { - object-fit: contain; - } - } - - .item-slideshow { - - @media (max-width: var(--md-width)) { - max-height: 320px; - } - - @media (min-width: var(--lg-width)) { - max-height: 430px; - } - - overflow: auto; - } - - .item-slideshow-image { - height: 4rem; - width: 6rem; - object-fit: contain; - padding: 0.5rem; - border: 1px solid var(--table-border-color); - border-radius: 4px; - cursor: pointer; - - &:hover, &.active { - border-color: var(--primary); - } - } - - .item-cart { - .product-price { - font-size: 22px; - color: var(--text-color); - font-weight: 600; - - .formatted-price { - color: var(--text-muted); - font-size: 14px; - } - } - - .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 { - min-height: 100px; - width: 40%; - - .r-product-image { - padding: 2px 15px; - } - - .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-right: 0; - padding-left: 10px; - 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 { - .modal-body { - padding-bottom: var(--padding-xl); - - .status-area { - .alert { - padding: var(--padding-xs) var(--padding-sm); - font-size: var(--text-sm); - } - } - - .form-layout { - max-height: 50vh; - overflow-y: auto; - } - - .section-body { - .form-column { - .form-group { - .control-label { - font-size: var(--text-md); - color: var(--gray-700); - } - - .help-box { - margin-top: 2px; - font-size: var(--text-sm); - } - } - } - } - } -} - -.item-group-slideshow { - - .carousel-inner.rounded-carousel { - border-radius: var(--card-border-radius); - } -} - -.sub-category-container { - padding-bottom: .5rem; - margin-bottom: 1.25rem; - border-bottom: 1px solid var(--table-border-color); - - .heading { - color: var(--gray-500); - } -} - -.scroll-categories { - .category-pill { - display: inline-block; - width: fit-content; - padding: 6px 12px; - margin-bottom: 8px; - background-color: #ecf5fe; - font-size: 14px; - border-radius: 18px; - color: var(--blue-500); - } -} - - -.shopping-badge { - position: relative; - top: -10px; - left: -12px; - background: var(--red-600); - align-items: center; - height: 16px; - font-size: 10px; - border-radius: 50%; -} - - -.cart-animate { - animation: wiggle 0.5s linear; -} -@keyframes wiggle { - 8%, - 41% { - transform: translateX(-10px); - } - 25%, - 58% { - transform: translate(10px); - } - 75% { - transform: translate(-5px); - } - 92% { - transform: translate(5px); - } - 0%, - 100% { - transform: translate(0); - } -} - -.total-discount { - font-size: 14px; - color: var(--primary-color) !important; -} - -#page-cart { - .shopping-cart-header { - font-weight: bold; - } - - .cart-container { - color: var(--text-color); - - .frappe-card { - display: flex; - flex-direction: column; - justify-content: space-between; - height: fit-content; - } - - .cart-items-header { - font-weight: 600; - } - - .cart-table { - tr { - margin-bottom: 1rem; - } - - th, tr, td { - border-color: var(--border-color); - border-width: 1px; - } - - th { - font-weight: normal; - font-size: 13px; - color: var(--text-muted); - padding: var(--padding-sm) 0; - } - - td { - padding: var(--padding-sm) 0; - color: var(--text-color); - } - - .cart-item-image { - width: 20%; - min-width: 100px; - img { - max-height: 112px; - } - } - - .cart-items { - .item-title { - width: 80%; - font-size: 14px; - font-weight: 500; - color: var(--text-color); - } - - .item-subtitle { - color: var(--text-muted); - font-size: 13px; - } - - .item-subtotal { - font-size: 14px; - font-weight: 500; - } - - .sm-item-subtotal { - font-size: 14px; - font-weight: 500; - display: none; - - @media (max-width: 992px) { - display: unset !important; - } - } - - .item-rate { - font-size: 13px; - color: var(--text-muted); - } - - .free-tag { - padding: 4px 8px; - border-radius: 4px; - background-color: var(--dark-green-50); - } - - textarea { - width: 80%; - height: 60px; - font-size: 14px; - } - - } - - .cart-tax-items { - .item-grand-total { - font-size: 16px; - font-weight: 700; - color: var(--text-color); - } - } - - .column-sm-view { - @media (max-width: 992px) { - display: none !important; - } - } - - .item-column { - width: 50%; - @media (max-width: 992px) { - width: 70%; - } - } - - .remove-cart-item { - border-radius: 6px; - border: 1px solid var(--gray-100); - width: 28px; - height: 28px; - font-weight: 300; - color: var(--gray-700); - background-color: var(--gray-100); - float: right; - cursor: pointer; - margin-top: .25rem; - justify-content: center; - } - - .remove-cart-item-logo { - margin-top: 2px; - margin-left: 2.2px; - fill: var(--gray-700) !important; - } - } - - .cart-payment-addresses { - hr { - border-color: var(--border-color); - } - } - - .payment-summary { - h6 { - padding-bottom: 1rem; - border-bottom: solid 1px var(--gray-200); - } - - table { - font-size: 14px; - td { - padding: 0; - padding-top: 0.35rem !important; - border: none !important; - } - - &.grand-total { - border-top: solid 1px var(--gray-200); - } - } - - .bill-label { - color: var(--gray-600); - } - - .bill-content { - font-weight: 500; - &.net-total { - font-size: 16px; - font-weight: 600; - } - } - - .btn-coupon-code { - font-size: 14px; - border: dashed 1px var(--gray-400); - box-shadow: none; - } - } - - .number-spinner { - width: 75%; - min-width: 105px; - .cart-btn { - border: none; - background: var(--gray-100); - box-shadow: none; - width: 24px; - height: 28px; - align-items: center; - justify-content: center; - display: flex; - font-size: 20px; - font-weight: 300; - color: var(--gray-700); - } - - .cart-qty { - height: 28px; - font-size: 13px; - &:disabled { - background: var(--gray-100); - opacity: 0.65; - } - } - } - - .place-order-container { - .btn-place-order { - float: right; - } - } - } - - .t-and-c-container { - padding: 1.5rem; - } - - .t-and-c-terms { - font-size: 14px; - } -} - -.no-image-cart-item { - max-height: 112px; - 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; -} - -.cart-empty.frappe-card { - min-height: 76vh; - @include flex(flex, center, center, column); - - .cart-empty-message { - font-size: 18px; - color: var(--text-color); - font-weight: bold; - } -} - -.address-card { - .card-title { - font-size: 14px; - font-weight: 500; - } - - .card-body { - max-width: 80%; - } - - .card-text { - font-size: 13px; - color: var(--gray-700); - } - - .card-link { - font-size: 13px; - - svg use { - stroke: var(--primary-color); - } - } - - .btn-change-address { - border: 1px solid var(--primary-color); - color: var(--primary-color); - box-shadow: none; - } -} - -.address-header { - margin-top: .15rem;padding: 0; -} - -.btn-new-address { - float: right; - font-size: 15px !important; - color: var(--primary-color) !important; -} - -.btn-new-address:hover, .btn-change-address:hover { - color: var(--primary-color) !important; -} - -.modal .address-card { - .card-body { - padding: var(--padding-sm); - border-radius: var(--border-radius); - border: 1px solid var(--dark-border-color); - } -} - -.cart-indicator { - position: absolute; - text-align: center; - width: 22px; - height: 22px; - left: calc(100% - 40px); - top: 22px; - - border-radius: 66px; - box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1); - background: white; - color: var(--primary-color); - font-size: 14px; - - &.list-indicator { - position: unset; - margin-left: auto; - } -} - - -.like-action { - visibility: hidden; - text-align: center; - position: absolute; - cursor: pointer; - width: 28px; - height: 28px; - left: 20px; - top: 20px; - - /* White */ - background: white; - box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1); - border-radius: 66px; - - &.like-action-wished { - visibility: visible !important; - } - - @media (max-width: 992px) { - visibility: visible !important; - } -} - -.like-action-list { - visibility: hidden; - text-align: center; - position: absolute; - cursor: pointer; - width: 28px; - height: 28px; - left: 20px; - top: 0; - - /* White */ - background: white; - box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1); - border-radius: 66px; - - &.like-action-wished { - visibility: visible !important; - } - - @media (max-width: 992px) { - visibility: visible !important; - } -} - -.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; -} - -@keyframes expand { - 30% { - transform: scale(1.3); - } - 50% { - transform: scale(0.8); - } - 70% { - transform: scale(1.1); - } - 100% { - transform: scale(1); - } - } - -.not-wished { - cursor: pointer; - --icon-stroke: #F47A7A !important; - - &:hover { - fill: #F47A7A; - } -} - -.wished { - --icon-stroke: none; - fill: #F47A7A !important; -} - -.list-row-checkbox { - &:before { - display: none; - } - - &:checked:before { - display: block; - z-index: 1; - } -} - -#pay-for-order { - padding: .5rem 1rem; // Pay button in SO -} - -.btn-explore-variants { - visibility: hidden; - box-shadow: none; - margin: var(--margin-sm) 0; - width: 90px; - max-height: 50px; // to avoid resizing on window resize - flex: none; - transition: 0.3s ease; - - color: white; - background-color: var(--orange-500); - border: 1px solid var(--orange-500); - font-size: 13px; - - &:hover { - color: white; - } -} - -.btn-add-to-cart-list{ - visibility: hidden; - box-shadow: none; - margin: var(--margin-sm) 0; - // margin-top: auto !important; - max-height: 50px; // to avoid resizing on window resize - flex: none; - transition: 0.3s ease; - - font-size: 13px; - - &:hover { - color: white; - } - - @media (max-width: 992px) { - visibility: visible !important; - } -} - -.go-to-cart-grid { - max-height: 30px; - margin-top: 1rem !important; -} - -.go-to-cart { - max-height: 30px; - float: right; -} - -.remove-wish { - background-color: white; - position: absolute; - cursor: pointer; - top:10px; - right: 20px; - width: 32px; - height: 32px; - - border-radius: 50%; - border: 1px solid var(--gray-100); - box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1); -} - -.wish-removed { - display: none; -} - -.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); - } -} - -.btn-view-more { - font-size: 14px; -} - -.rating-summary-section { - display: flex; -} - -.rating-summary-title { - margin-top: 0.15rem; - 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: 15px; - font-weight: 600; -} - -.rating { - --star-fill: var(--gray-300); - .star-hover { - --star-fill: var(--yellow-100); - } - .star-click { - --star-fill: var(--yellow-300); - } -} - -.ratings-pill { - background-color: var(--gray-100); - padding: .5rem 1rem; - border-radius: 66px; -} - -.review { - max-width: 80%; - line-height: 1.6; - padding-bottom: 0.5rem; - border-bottom: 1px solid #E2E6E9; -} - -.review-signature { - display: flex; - font-size: 13px; - color: var(--gray-500); - font-weight: 400; - - .reviewer { - padding-right: 8px; - color: var(--gray-600); - } -} - -.rating-progress-bar-section { - padding-bottom: 2rem; - - .rating-bar-title { - margin-left: -15px; - } - - .rating-progress-bar { - margin-bottom: 4px; - height: 7px; - margin-top: 6px; - - .progress-bar-cosmetic { - background-color: var(--gray-600); - border-radius: var(--border-radius); - } - } -} - -.offer-container { - font-size: 14px; -} - -#search-results-container { - border: 1px solid var(--gray-200); - padding: .25rem 1rem; - - .category-chip { - background-color: var(--gray-100); - border: none !important; - box-shadow: none; - } - - .recent-search { - padding: .5rem .5rem; - border-radius: var(--border-radius); - - &:hover { - background-color: var(--gray-100); - } - } -} - -#search-box { - background-color: white; - height: 100%; - padding-left: 2.5rem; - border: 1px solid var(--gray-200); -} - -.search-icon { - position: absolute; - left: 0; - top: 0; - width: 2.5rem; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - padding-bottom: 1px; -} - -#toggle-view { - float: right; - - .btn-primary { - background-color: var(--gray-600); - box-shadow: 0 0 0 0.2rem var(--gray-400); - } -} - -.placeholder-div { - height:80%; - width: -webkit-fill-available; - padding: 50px; - text-align: center; - background-color: #F9FAFA; - border-top-left-radius: calc(0.75rem - 1px); - border-top-right-radius: calc(0.75rem - 1px); -} -.placeholder { - font-size: 72px; -} - -[data-path="cart"] { - .modal-backdrop { - background-color: var(--gray-50); // lighter backdrop only on cart freeze - } -} - -.item-thumb { - height: 50px; - max-width: 80px; - min-width: 80px; - object-fit: cover; -} - -.brand-line { - color: gray; -} - -.btn-next, .btn-prev { - font-size: 14px; -} - -.alert-error { - color: #e27a84; - background-color: #fff6f7; - border-color: #f5c6cb; -} - -.font-md { - font-size: 14px !important; -} - -.in-green { - color: var(--green-info) !important; - font-weight: 500; -} - -.has-stock { - font-weight: 400 !important; -} - -.out-of-stock { - font-weight: 400; - font-size: 14px; - line-height: 20px; - color: #F47A7A; -} - -.mt-minus-2 { - margin-top: -2rem; -} - -.mt-minus-1 { - margin-top: -1rem; -} \ No newline at end of file diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 8ff681b04813..95d2d2c57716 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -26,7 +26,6 @@ def validate(self): self.set_status() self.validate_uom_is_integer("stock_uom", "qty") self.validate_valid_till() - self.validate_shopping_cart_items() self.set_customer_name() if self.items: self.with_items = 1 @@ -42,26 +41,6 @@ def validate_valid_till(self): if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date): frappe.throw(_("Valid till date cannot be before transaction date")) - def validate_shopping_cart_items(self): - if self.order_type != "Shopping Cart": - return - - for item in self.items: - has_web_item = frappe.db.exists("Website Item", {"item_code": item.item_code}) - - # If variant is unpublished but template is published: valid - template = frappe.get_cached_value("Item", item.item_code, "variant_of") - if template and not has_web_item: - has_web_item = frappe.db.exists("Website Item", {"item_code": template}) - - if not has_web_item: - frappe.throw( - _("Row #{0}: Item {1} must have a Website Item for Shopping Cart Quotations").format( - item.idx, frappe.bold(item.item_code) - ), - title=_("Unpublished Item"), - ) - def set_has_alternative_item(self): """Mark 'Has Alternative Item' for rows.""" if not any(row.is_alternative for row in self.get("items")): @@ -263,8 +242,8 @@ def make_sales_order(source_name: str, target_doc=None): return _make_sales_order(source_name, target_doc) -def _make_sales_order(source_name, target_doc=None, ignore_permissions=False): - customer = _make_customer(source_name, ignore_permissions) +def _make_sales_order(source_name, target_doc=None, customer_group=None, ignore_permissions=False): + customer = _make_customer(source_name, ignore_permissions, customer_group) ordered_items = frappe._dict( frappe.db.get_all( "Sales Order Item", @@ -428,7 +407,7 @@ def update_item(obj, target, source_parent): return doclist -def _make_customer(source_name, ignore_permissions=False): +def _make_customer(source_name, ignore_permissions=False, customer_group=None): quotation = frappe.db.get_value( "Quotation", source_name, ["order_type", "party_name", "customer_name"], as_dict=1 ) @@ -445,10 +424,7 @@ def _make_customer(source_name, ignore_permissions=False): customer_doclist = _make_customer(lead_name, ignore_permissions=ignore_permissions) customer = frappe.get_doc(customer_doclist) customer.flags.ignore_permissions = ignore_permissions - if quotation.get("party_name") == "Shopping Cart": - customer.customer_group = frappe.db.get_value( - "E Commerce Settings", None, "default_customer_group" - ) + customer.customer_group = customer_group try: customer.insert() diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 5623a12cdda7..590cd3d0cf95 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -161,15 +161,6 @@ def test_so_from_expired_quotation(self): make_sales_order(quotation.name) - def test_shopping_cart_without_website_item(self): - if frappe.db.exists("Website Item", {"item_code": "_Test Item Home Desktop 100"}): - frappe.get_last_doc("Website Item", {"item_code": "_Test Item Home Desktop 100"}).delete() - - quotation = frappe.copy_doc(test_records[0]) - quotation.order_type = "Shopping Cart" - quotation.valid_till = getdate() - self.assertRaises(frappe.ValidationError, quotation.validate) - def test_create_quotation_with_margin(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order from erpnext.selling.doctype.sales_order.sales_order import ( diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js index 4b04ac1d5ef2..d6eb11f73d9c 100644 --- a/erpnext/setup/doctype/item_group/item_group.js +++ b/erpnext/setup/doctype/item_group/item_group.js @@ -71,20 +71,6 @@ frappe.ui.form.on("Item Group", { frappe.set_route("List", "Item", {"item_group": frm.doc.name}); }); } - - frappe.model.with_doctype('Website Item', () => { - const web_item_meta = frappe.get_meta('Website Item'); - - 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 }) - ); - - frm.get_field("filter_fields").grid.update_docfield_property( - 'fieldname', 'options', valid_fields - ); - }); }, set_root_readonly: function(frm) { diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json index e0f509047498..dfa5a8ed0a7b 100644 --- a/erpnext/setup/doctype/item_group/item_group.json +++ b/erpnext/setup/doctype/item_group/item_group.json @@ -19,22 +19,9 @@ "item_group_defaults", "sec_break_taxes", "taxes", - "sb9", - "route", - "website_title", - "description", - "show_in_website", - "include_descendants", - "column_break_16", - "weightage", - "slideshow", - "website_specifications", - "website_filters_section", - "filter_fields", - "filter_attributes", "lft", - "rgt", - "old_parent" + "old_parent", + "rgt" ], "fields": [ { @@ -106,54 +93,6 @@ "label": "Taxes", "options": "Item Tax" }, - { - "fieldname": "sb9", - "fieldtype": "Section Break", - "label": "Website Settings" - }, - { - "default": "0", - "description": "Make Item Group visible in website", - "fieldname": "show_in_website", - "fieldtype": "Check", - "label": "Show in Website" - }, - { - "depends_on": "show_in_website", - "fieldname": "route", - "fieldtype": "Data", - "label": "Route", - "no_copy": 1, - "unique": 1 - }, - { - "depends_on": "show_in_website", - "fieldname": "weightage", - "fieldtype": "Int", - "label": "Weightage" - }, - { - "depends_on": "show_in_website", - "description": "Show this slideshow at the top of the page", - "fieldname": "slideshow", - "fieldtype": "Link", - "label": "Slideshow", - "options": "Website Slideshow" - }, - { - "depends_on": "show_in_website", - "description": "HTML / Banner that will show on the top of product list.", - "fieldname": "description", - "fieldtype": "Text Editor", - "label": "Description" - }, - { - "depends_on": "show_in_website", - "fieldname": "website_specifications", - "fieldtype": "Table", - "label": "Website Specifications", - "options": "Item Website Specification" - }, { "fieldname": "lft", "fieldtype": "Int", @@ -188,43 +127,6 @@ "options": "Item Group", "print_hide": 1, "report_hide": 1 - }, - { - "collapsible": 1, - "depends_on": "show_in_website", - "fieldname": "website_filters_section", - "fieldtype": "Section Break", - "label": "Website Filters" - }, - { - "fieldname": "filter_fields", - "fieldtype": "Table", - "label": "Item Fields", - "options": "Website Filter Field" - }, - { - "fieldname": "filter_attributes", - "fieldtype": "Table", - "label": "Attributes", - "options": "Website Attribute" - }, - { - "depends_on": "show_in_website", - "fieldname": "website_title", - "fieldtype": "Data", - "label": "Title" - }, - { - "fieldname": "column_break_16", - "fieldtype": "Column Break" - }, - { - "default": "0", - "depends_on": "show_in_website", - "description": "Include Website Items belonging to child Item Groups", - "fieldname": "include_descendants", - "fieldtype": "Check", - "label": "Include Descendants" } ], "icon": "fa fa-sitemap", @@ -233,7 +135,7 @@ "is_tree": 1, "links": [], "max_attachments": 3, - "modified": "2023-08-28 22:27:48.382985", + "modified": "2023-10-12 13:44:13.611287", "modified_by": "Administrator", "module": "Setup", "name": "Item Group", diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index cc67c696b437..fe7a241dc49c 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -2,39 +2,19 @@ # License: GNU General Public License v3. See license.txt import copy -from urllib.parse import quote import frappe from frappe import _ -from frappe.utils import cint from frappe.utils.nestedset import NestedSet -from frappe.website.utils import clear_cache -from frappe.website.website_generator import WebsiteGenerator -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ECommerceSettings -from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder - - -class ItemGroup(NestedSet, WebsiteGenerator): - nsm_parent_field = "parent_item_group" - website = frappe._dict( - condition_field="show_in_website", - template="templates/generators/item_group.html", - no_cache=1, - no_breadcrumbs=1, - ) +class ItemGroup(NestedSet): def validate(self): - super(ItemGroup, self).validate() - if not self.parent_item_group and not frappe.flags.in_test: if frappe.db.exists("Item Group", _("All Item Groups")): self.parent_item_group = _("All Item Groups") - - self.make_route() self.validate_item_group_defaults() self.check_item_tax() - ECommerceSettings.validate_field_filters(self.filter_fields, enable_field_filters=True) def check_item_tax(self): """Check whether Tax Rate is not entered twice for same Tax Type""" @@ -53,66 +33,13 @@ def check_item_tax(self): def on_update(self): NestedSet.on_update(self) - invalidate_cache_for(self) self.validate_one_root() self.delete_child_item_groups_key() - def make_route(self): - """Make website route""" - if not self.route: - self.route = "" - if self.parent_item_group: - parent_item_group = frappe.get_doc("Item Group", self.parent_item_group) - - # make parent route only if not root - if parent_item_group.parent_item_group and parent_item_group.route: - self.route = parent_item_group.route + "/" - - self.route += self.scrub(self.item_group_name) - - return self.route - def on_trash(self): NestedSet.on_trash(self, allow_root_deletion=True) - WebsiteGenerator.on_trash(self) self.delete_child_item_groups_key() - def get_context(self, context): - context.show_search = True - context.body_class = "product-page" - context.page_length = ( - cint(frappe.db.get_single_value("E Commerce Settings", "products_per_page")) or 6 - ) - context.search_link = "/product_search" - - filter_engine = ProductFiltersBuilder(self.name) - - context.field_filters = filter_engine.get_field_filters() - context.attribute_filters = filter_engine.get_attribute_filters() - - context.update({"parents": get_parent_item_groups(self.parent_item_group), "title": self.name}) - - if self.slideshow: - values = {"show_indicators": 1, "show_controls": 0, "rounded": 1, "slider_name": self.slideshow} - slideshow = frappe.get_doc("Website Slideshow", self.slideshow) - slides = slideshow.get({"doctype": "Website Slideshow Item"}) - for index, slide in enumerate(slides): - values[f"slide_{index + 1}_image"] = slide.image - values[f"slide_{index + 1}_title"] = slide.heading - values[f"slide_{index + 1}_subtitle"] = slide.description - values[f"slide_{index + 1}_theme"] = slide.get("theme") or "Light" - values[f"slide_{index + 1}_content_align"] = slide.get("content_align") or "Centre" - values[f"slide_{index + 1}_primary_action"] = slide.url - - context.slideshow = values - - context.no_breadcrumbs = False - context.title = self.website_title or self.name - context.name = self.name - context.item_group_name = self.item_group_name - - return context - def delete_child_item_groups_key(self): frappe.cache().hdel("child_item_groups", self.name) @@ -122,20 +49,6 @@ def validate_item_group_defaults(self): validate_item_default_company_links(self.item_group_defaults) -def get_child_groups_for_website(item_group_name, immediate=False, include_self=False): - """Returns child item groups *excluding* passed group.""" - item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1) - filters = {"lft": [">", item_group.lft], "rgt": ["<", item_group.rgt], "show_in_website": 1} - - if immediate: - filters["parent_item_group"] = item_group_name - - if include_self: - filters.update({"lft": [">=", item_group.lft], "rgt": ["<=", item_group.rgt]}) - - return frappe.get_all("Item Group", filters=filters, fields=["name", "route"], order_by="name") - - def get_child_item_groups(item_group_name): item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1) @@ -149,63 +62,6 @@ def get_child_item_groups(item_group_name): return child_item_groups or {} -def get_item_for_list_in_html(context): - # add missing absolute link in files - # user may forget it during upload - if (context.get("website_image") or "").startswith("files/"): - context["website_image"] = "/" + quote(context["website_image"]) - - products_template = "templates/includes/products_as_list.html" - - return frappe.get_template(products_template).render(context) - - -def get_parent_item_groups(item_group_name, from_item=False): - settings = frappe.get_cached_doc("E Commerce Settings") - - if settings.enable_field_filters: - base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"} - else: - base_nav_page = {"name": _("All Products"), "route": "/all-products"} - - if from_item and frappe.request.environ.get("HTTP_REFERER"): - # base page after 'Home' will vary on Item page - last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0] - if last_page and last_page in ("shop-by-category", "all-products"): - base_nav_page_title = " ".join(last_page.split("-")).title() - base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page} - - base_parents = [ - {"name": _("Home"), "route": "/"}, - base_nav_page, - ] - - if not item_group_name: - return base_parents - - item_group = frappe.db.get_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1) - parent_groups = frappe.db.sql( - """select name, route from `tabItem Group` - where lft <= %s and rgt >= %s - and show_in_website=1 - order by lft asc""", - (item_group.lft, item_group.rgt), - as_dict=True, - ) - - return base_parents + parent_groups - - -def invalidate_cache_for(doc, item_group=None): - if not item_group: - item_group = doc.name - - for d in get_parent_item_groups(item_group): - item_group_name = frappe.db.get_value("Item Group", d.get("name")) - if item_group_name: - clear_cache(frappe.db.get_value("Item Group", item_group_name, "route")) - - def get_item_group_defaults(item, company): item = frappe.get_cached_doc("Item", item) item_group = frappe.get_cached_doc("Item Group", item.item_group) diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py index ace5cca0b027..d4aac5ee46f8 100644 --- a/erpnext/setup/setup_wizard/operations/company_setup.py +++ b/erpnext/setup/setup_wizard/operations/company_setup.py @@ -33,20 +33,6 @@ def create_fiscal_year_and_company(args): ).insert() -def enable_shopping_cart(args): # nosemgrep - # Needs price_lists - frappe.get_doc( - { - "doctype": "E Commerce Settings", - "enabled": 1, - "company": args.get("company_name"), - "price_list": frappe.db.get_value("Price List", {"selling": 1}), - "default_customer_group": _("Individual"), - "quotation_series": "QTN-", - } - ).insert() - - def get_fy_details(fy_start_date, fy_end_date): start_year = getdate(fy_start_date).year if start_year == getdate(fy_end_date).year: diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index ae6881b99e01..2205924e5093 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -454,7 +454,6 @@ def install_defaults(args=None): # nosemgrep set_global_defaults(args) update_stock_settings() - update_shopping_cart_settings(args) args.update({"set_default": 1}) create_bank_account(args) @@ -529,20 +528,6 @@ def create_bank_account(args): pass -def update_shopping_cart_settings(args): # nosemgrep - shopping_cart = frappe.get_doc("E Commerce Settings") - shopping_cart.update( - { - "enabled": 1, - "company": args.company_name, - "price_list": frappe.db.get_value("Price List", {"selling": 1}), - "default_customer_group": _("Individual"), - "quotation_series": "QTN-", - } - ) - shopping_cart.update_single(shopping_cart.get_valid_dict()) - - def get_fy_details(fy_start_date, fy_end_date): start_year = getdate(fy_start_date).year if start_year == getdate(fy_end_date).year: diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index 5806fd1f788a..2f9cec40b0fa 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -1,500 +1,500 @@ { - "charts": [], - "content": "[{\"id\":\"NO5yYHJopc\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"col\":12}},{\"id\":\"CDxIM-WuZ9\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"id\":\"-Uh7DKJNJX\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":3}},{\"id\":\"K9ST9xcDXh\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":3}},{\"id\":\"27IdVHVQMb\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":3}},{\"id\":\"Rwp5zff88b\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":3}},{\"id\":\"hkfnQ2sevf\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Global Defaults\",\"col\":3}},{\"id\":\"jjxI_PDawD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"id\":\"R3CoYYFXye\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yynbm1J_VO\",\"type\":\"header\",\"data\":{\"text\":\"Settings\",\"col\":12}},{\"id\":\"KDCv2MvSg3\",\"type\":\"card\",\"data\":{\"card_name\":\"Module Settings\",\"col\":4}},{\"id\":\"Q0_bqT7cxQ\",\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"id\":\"UnqK5haBnh\",\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"id\":\"kp7u1H5hCd\",\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"id\":\"Ufc3jycgy9\",\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"id\":\"89bSNzv3Yh\",\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]", - "creation": "2022-01-27 13:14:47.349433", - "custom_blocks": [], - "docstatus": 0, - "doctype": "Workspace", - "for_user": "", - "hide_custom": 0, - "icon": "setting", - "idx": 0, - "is_hidden": 0, - "label": "ERPNext Settings", - "links": [ - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Import Data", - "link_count": 0, - "link_to": "Data Import", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Export Data", - "link_count": 0, - "link_to": "Data Export", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Bulk Update", - "link_count": 0, - "link_to": "Bulk Update", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Download Backups", - "link_count": 0, - "link_to": "backups", - "link_type": "Page", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Deleted Documents", - "link_count": 0, - "link_to": "Deleted Document", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Email / Notifications", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Email Account", - "link_count": 0, - "link_to": "Email Account", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Email Domain", - "link_count": 0, - "link_to": "Email Domain", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Notification", - "link_count": 0, - "link_to": "Notification", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Email Template", - "link_count": 0, - "link_to": "Email Template", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Auto Email Report", - "link_count": 0, - "link_to": "Auto Email Report", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Newsletter", - "link_count": 0, - "link_to": "Newsletter", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Notification Settings", - "link_count": 0, - "link_to": "Notification Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Website", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Website Settings", - "link_count": 0, - "link_to": "Website Settings", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Website Theme", - "link_count": 0, - "link_to": "Website Theme", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Website Script", - "link_count": 0, - "link_to": "Website Script", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "About Us Settings", - "link_count": 0, - "link_to": "About Us Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Contact Us Settings", - "link_count": 0, - "link_to": "Contact Us Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Printing", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Print Format Builder", - "link_count": 0, - "link_to": "print-format-builder", - "link_type": "Page", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Print Settings", - "link_count": 0, - "link_to": "Print Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Print Format", - "link_count": 0, - "link_to": "Print Format", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Print Style", - "link_count": 0, - "link_to": "Print Style", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Workflow", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Workflow", - "link_count": 0, - "link_to": "Workflow", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Workflow State", - "link_count": 0, - "link_to": "Workflow State", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Workflow Action", - "link_count": 0, - "link_to": "Workflow Action", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Core", - "link_count": 3, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "System Settings", - "link_count": 0, - "link_to": "System Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Domain Settings", - "link_count": 0, - "link_to": "Domain Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Global Defaults", - "link_count": 0, - "link_to": "Global Defaults", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Module Settings", - "link_count": 8, - "onboard": 0, - "type": "Card Break" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Accounts Settings", - "link_count": 0, - "link_to": "Accounts Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Stock Settings", - "link_count": 0, - "link_to": "Stock Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Selling Settings", - "link_count": 0, - "link_to": "Selling Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Buying Settings", - "link_count": 0, - "link_to": "Buying Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Manufacturing Settings", - "link_count": 0, - "link_to": "Manufacturing Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "CRM Settings", - "link_count": 0, - "link_to": "CRM Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Projects Settings", - "link_count": 0, - "link_to": "Projects Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Support Settings", - "link_count": 0, - "link_to": "Support Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - } - ], - "modified": "2023-05-24 14:47:25.356531", - "modified_by": "Administrator", - "module": "Setup", - "name": "ERPNext Settings", - "number_cards": [], - "owner": "Administrator", - "parent_page": "", - "public": 1, - "quick_lists": [], - "restrict_to_domain": "", - "roles": [], - "sequence_id": 19.0, - "shortcuts": [ - { - "color": "Grey", - "doc_view": "List", - "label": "Print Settings", - "link_to": "Print Settings", - "type": "DocType" - }, - { - "color": "Grey", - "doc_view": "List", - "label": "System Settings", - "link_to": "System Settings", - "type": "DocType" - }, - { - "icon": "accounting", - "label": "Accounts Settings", - "link_to": "Accounts Settings", - "type": "DocType" - }, - { - "color": "Grey", - "doc_view": "List", - "label": "Global Defaults", - "link_to": "Global Defaults", - "type": "DocType" - }, - { - "icon": "stock", - "label": "Stock Settings", - "link_to": "Stock Settings", - "type": "DocType" - }, - { - "icon": "sell", - "label": "Selling Settings", - "link_to": "Selling Settings", - "type": "DocType" - }, - { - "icon": "buying", - "label": "Buying Settings", - "link_to": "Buying Settings", - "type": "DocType" - } - ], - "title": "ERPNext Settings" -} \ No newline at end of file + "charts": [], + "content": "[{\"id\":\"NO5yYHJopc\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"col\":12}},{\"id\":\"CDxIM-WuZ9\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"id\":\"-Uh7DKJNJX\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":3}},{\"id\":\"K9ST9xcDXh\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":3}},{\"id\":\"27IdVHVQMb\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":3}},{\"id\":\"Rwp5zff88b\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":3}},{\"id\":\"hkfnQ2sevf\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Global Defaults\",\"col\":3}},{\"id\":\"jjxI_PDawD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"id\":\"R3CoYYFXye\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yynbm1J_VO\",\"type\":\"header\",\"data\":{\"text\":\"Settings\",\"col\":12}},{\"id\":\"KDCv2MvSg3\",\"type\":\"card\",\"data\":{\"card_name\":\"Module Settings\",\"col\":4}},{\"id\":\"Q0_bqT7cxQ\",\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"id\":\"UnqK5haBnh\",\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"id\":\"kp7u1H5hCd\",\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"id\":\"Ufc3jycgy9\",\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"id\":\"89bSNzv3Yh\",\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]", + "creation": "2022-01-27 13:14:47.349433", + "custom_blocks": [], + "docstatus": 0, + "doctype": "Workspace", + "for_user": "", + "hide_custom": 0, + "icon": "setting", + "idx": 0, + "is_hidden": 0, + "label": "ERPNext Settings", + "links": [ + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Import Data", + "link_count": 0, + "link_to": "Data Import", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Export Data", + "link_count": 0, + "link_to": "Data Export", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Bulk Update", + "link_count": 0, + "link_to": "Bulk Update", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Download Backups", + "link_count": 0, + "link_to": "backups", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Deleted Documents", + "link_count": 0, + "link_to": "Deleted Document", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Email / Notifications", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Email Account", + "link_count": 0, + "link_to": "Email Account", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Email Domain", + "link_count": 0, + "link_to": "Email Domain", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Notification", + "link_count": 0, + "link_to": "Notification", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Email Template", + "link_count": 0, + "link_to": "Email Template", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Auto Email Report", + "link_count": 0, + "link_to": "Auto Email Report", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Newsletter", + "link_count": 0, + "link_to": "Newsletter", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Notification Settings", + "link_count": 0, + "link_to": "Notification Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Website", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Website Settings", + "link_count": 0, + "link_to": "Website Settings", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Website Theme", + "link_count": 0, + "link_to": "Website Theme", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Website Script", + "link_count": 0, + "link_to": "Website Script", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "About Us Settings", + "link_count": 0, + "link_to": "About Us Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Contact Us Settings", + "link_count": 0, + "link_to": "Contact Us Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Printing", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Print Format Builder", + "link_count": 0, + "link_to": "print-format-builder", + "link_type": "Page", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Print Settings", + "link_count": 0, + "link_to": "Print Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Print Format", + "link_count": 0, + "link_to": "Print Format", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Print Style", + "link_count": 0, + "link_to": "Print Style", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Workflow", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Workflow", + "link_count": 0, + "link_to": "Workflow", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Workflow State", + "link_count": 0, + "link_to": "Workflow State", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Workflow Action", + "link_count": 0, + "link_to": "Workflow Action", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Core", + "link_count": 3, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "System Settings", + "link_count": 0, + "link_to": "System Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Domain Settings", + "link_count": 0, + "link_to": "Domain Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Global Defaults", + "link_count": 0, + "link_to": "Global Defaults", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Module Settings", + "link_count": 8, + "onboard": 0, + "type": "Card Break" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Accounts Settings", + "link_count": 0, + "link_to": "Accounts Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Stock Settings", + "link_count": 0, + "link_to": "Stock Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Selling Settings", + "link_count": 0, + "link_to": "Selling Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Buying Settings", + "link_count": 0, + "link_to": "Buying Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Manufacturing Settings", + "link_count": 0, + "link_to": "Manufacturing Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "CRM Settings", + "link_count": 0, + "link_to": "CRM Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Projects Settings", + "link_count": 0, + "link_to": "Projects Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Support Settings", + "link_count": 0, + "link_to": "Support Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2023-05-24 14:47:25.356531", + "modified_by": "Administrator", + "module": "Setup", + "name": "ERPNext Settings", + "number_cards": [], + "owner": "Administrator", + "parent_page": "", + "public": 1, + "quick_lists": [], + "restrict_to_domain": "", + "roles": [], + "sequence_id": 19.0, + "shortcuts": [ + { + "color": "Grey", + "doc_view": "List", + "label": "Print Settings", + "link_to": "Print Settings", + "type": "DocType" + }, + { + "color": "Grey", + "doc_view": "List", + "label": "System Settings", + "link_to": "System Settings", + "type": "DocType" + }, + { + "icon": "accounting", + "label": "Accounts Settings", + "link_to": "Accounts Settings", + "type": "DocType" + }, + { + "color": "Grey", + "doc_view": "List", + "label": "Global Defaults", + "link_to": "Global Defaults", + "type": "DocType" + }, + { + "icon": "stock", + "label": "Stock Settings", + "link_to": "Stock Settings", + "type": "DocType" + }, + { + "icon": "sell", + "label": "Selling Settings", + "link_to": "Selling Settings", + "type": "DocType" + }, + { + "icon": "buying", + "label": "Buying Settings", + "link_to": "Buying Settings", + "type": "DocType" + } + ], + "title": "ERPNext Settings" + } \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 4ae9bf5b2a22..6e810e5987ed 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -125,36 +125,6 @@ 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')); - } else { - frm.add_custom_button(__("Website Item"), function() { - frappe.db.get_value("Website Item", {item_code: frm.doc.name}, "name", (d) => { - if (!d.name) frappe.throw(__("Website Item not found")); - frappe.set_route("Form", "Website Item", d.name); - }); - }, __("View")); - } - erpnext.item.edit_prices_button(frm); erpnext.item.toggle_attributes(frm); diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 1bcddfa77e52..54491bbee362 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -117,7 +117,6 @@ "customer_code", "default_item_manufacturer", "default_manufacturer_part_no", - "published_in_website", "total_projected_qty" ], "fields": [ @@ -815,14 +814,6 @@ "label": "Default Manufacturer Part No", "read_only": 1 }, - { - "default": "0", - "depends_on": "published_in_website", - "fieldname": "published_in_website", - "fieldtype": "Check", - "label": "Published in Website", - "read_only": 1 - }, { "default": "1", "fieldname": "grant_commission", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index aff958738a8f..9e281990b542 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -32,7 +32,6 @@ make_variant_item_code, validate_item_variant_attributes, ) -from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for from erpnext.stock.doctype.item_default.item_default import ItemDefault @@ -122,10 +121,8 @@ def validate(self): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") def on_update(self): - invalidate_cache_for_item(self) self.update_variants() self.update_item_price() - self.update_website_item() def validate_description(self): """Clean HTML description if set""" @@ -248,29 +245,6 @@ def add_default_uom_in_conversion_factor_table(self): if self.stock_uom not in uoms_list: self.append("uoms", {"uom": self.stock_uom, "conversion_factor": 1}) - def update_website_item(self): - """Update Website Item if change in Item impacts it.""" - web_item = frappe.db.exists("Website Item", {"item_code": self.item_code}) - - if web_item: - changed = {} - editable_fields = ["item_name", "item_group", "stock_uom", "brand", "description", "disabled"] - doc_before_save = self.get_doc_before_save() - - for field in editable_fields: - if doc_before_save.get(field) != self.get(field): - if field == "disabled": - changed["published"] = not self.get(field) - else: - changed[field] = self.get(field) - - if not changed: - return - - web_item_doc = frappe.get_doc("Website Item", web_item) - web_item_doc.update(changed) - web_item_doc.save() - def validate_item_tax_net_rate_range(self): for tax in self.get("taxes"): if flt(tax.maximum_net_rate) < flt(tax.minimum_net_rate): @@ -454,7 +428,6 @@ def before_rename(self, old_name, new_name, merge=False): if merge: self.validate_properties_before_merge(new_name) self.validate_duplicate_product_bundles_before_merge(old_name, new_name) - self.validate_duplicate_website_item_before_merge(old_name, new_name) self.delete_old_bins(old_name) def after_rename(self, old_name, new_name, merge): @@ -466,9 +439,6 @@ def after_rename(self, old_name, new_name, merge): title=_("Note"), ) - if self.published_in_website: - invalidate_cache_for_item(self) - frappe.db.set_value("Item", new_name, "item_code", new_name) if merge: @@ -554,27 +524,6 @@ def validate_duplicate_product_bundles_before_merge(self, old_name, new_name): ) frappe.throw(msg, title=_("Cannot Merge"), exc=DataValidationError) - def validate_duplicate_website_item_before_merge(self, old_name, new_name): - """ - Block merge if both old and new items have website items against them. - This is to avoid duplicate website items after merging. - """ - web_items = frappe.get_all( - "Website Item", - filters={"item_code": ["in", [old_name, new_name]]}, - fields=["item_code", "name"], - ) - - if len(web_items) <= 1: - return - - old_web_item = [d.get("name") for d in web_items if d.get("item_code") == old_name][0] - web_item_link = get_link_to_form("Website Item", old_web_item) - old_name, new_name = frappe.bold(old_name), frappe.bold(new_name) - - msg = f"Please delete linked Website Item {frappe.bold(web_item_link)} before merging {old_name} into {new_name}" - frappe.throw(_(msg), title=_("Cannot Merge"), exc=DataValidationError) - def set_last_purchase_rate(self, new_name): last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0) frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate) @@ -1151,32 +1100,6 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): return out -def invalidate_cache_for_item(doc): - """Invalidate Item Group cache and rebuild ItemVariantsCacheManager.""" - invalidate_cache_for(doc, doc.item_group) - - if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group: - invalidate_cache_for(doc, doc.old_item_group) - - invalidate_item_variants_cache_for_website(doc) - - -def invalidate_item_variants_cache_for_website(doc): - """Rebuild ItemVariantsCacheManager via Item or Website Item.""" - from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager - - item_code = None - is_web_item = doc.get("published_in_website") or doc.get("published") - if doc.has_variants and is_web_item: - item_code = doc.item_code - elif doc.variant_of and frappe.db.get_value("Item", doc.variant_of, "published_in_website"): - item_code = doc.variant_of - - if item_code: - item_cache = ItemVariantsCacheManager(item_code) - item_cache.rebuild_cache() - - def check_stock_uom_with_bin(item, stock_uom): if stock_uom == frappe.db.get_value("Item", item, "stock_uom"): return diff --git a/erpnext/stock/doctype/item/item_dashboard.py b/erpnext/stock/doctype/item/item_dashboard.py index 34bb4d12257d..88ae34f228ca 100644 --- a/erpnext/stock/doctype/item/item_dashboard.py +++ b/erpnext/stock/doctype/item/item_dashboard.py @@ -32,6 +32,5 @@ def get_data(): {"label": _("Manufacture"), "items": ["Production Plan", "Work Order", "Item Manufacturer"]}, {"label": _("Traceability"), "items": ["Serial No", "Batch"]}, {"label": _("Stock Movement"), "items": ["Stock Entry", "Stock Reconciliation"]}, - {"label": _("E-commerce"), "items": ["Website Item"]}, ], } diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py index e77d53a36712..21c0f18cc3ef 100644 --- a/erpnext/stock/doctype/price_list/price_list.py +++ b/erpnext/stock/doctype/price_list/price_list.py @@ -13,9 +13,6 @@ def validate(self): if not cint(self.buying) and not cint(self.selling): throw(_("Price List must be applicable for Buying or Selling")) - if not self.is_new(): - self.check_impact_on_shopping_cart() - def on_update(self): self.set_default_if_missing() self.update_item_price() @@ -37,19 +34,6 @@ def update_item_price(self): (self.currency, cint(self.buying), cint(self.selling), self.name), ) - def check_impact_on_shopping_cart(self): - "Check if Price List currency change impacts E Commerce Cart." - from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - validate_cart_settings, - ) - - doc_before_save = self.get_doc_before_save() - currency_changed = self.currency != doc_before_save.currency - affects_cart = self.name == frappe.db.get_single_value("E Commerce Settings", "price_list") - - if currency_changed and affects_cart: - validate_cart_settings() - def on_trash(self): self.delete_price_list_details_key() diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html deleted file mode 100644 index 358c1c52e5f8..000000000000 --- a/erpnext/templates/generators/item/item.html +++ /dev/null @@ -1,80 +0,0 @@ -{% extends "templates/web.html" %} -{% from "erpnext/templates/includes/macros.html" import recommended_item_row %} - -{% block title %} {{ title }} {% endblock %} - -{% block breadcrumbs %} -
- {% include "templates/includes/breadcrumbs.html" %} -
-{% endblock %} - -{% block page_content %} -
- {% from "erpnext/templates/includes/macros.html" import product_image %} -
-
- -
- {% include "templates/generators/item/item_image.html" %} - {% include "templates/generators/item/item_details.html" %} -
-
-
-
- - -
- {% set show_recommended_items = recommended_items and shopping_cart.cart_settings.enable_recommendations %} - {% set info_col = 'col-9' if show_recommended_items else 'col-12' %} - - {% set padding_top = 'pt-0' if (show_tabs and tabs) else '' %} - -
-
-
- - {% if show_tabs and tabs %} -
- - {{ web_block("Section with Tabs", values=tabs, add_container=0, - add_top_padding=0, add_bottom_padding=0) - }} -
- {% elif website_specifications %} - {% include "templates/generators/item/item_specifications.html"%} - {% endif %} - - - {{ doc.website_content or '' }} - - - {% if shopping_cart.cart_settings.enable_reviews and not doc.has_variants %} - {% include "templates/generators/item/item_reviews.html"%} - {% endif %} -
-
-
- - - {% if show_recommended_items %} - - {% endif %} - -
-{% endblock %} - -{% block base_scripts %} - - -{{ include_script("frappe-web.bundle.js") }} -{{ include_script("controls.bundle.js") }} -{{ include_script("dialog.bundle.js") }} -{% endblock %} diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html deleted file mode 100644 index 9bd3f7514c95..000000000000 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ /dev/null @@ -1,180 +0,0 @@ -{% if shopping_cart and shopping_cart.cart_settings.enabled %} - -{% set cart_settings = shopping_cart.cart_settings %} -{% set product_info = shopping_cart.product_info %} - -
-
- - {% if cart_settings.show_price and product_info.price %} - {% set price_info = product_info.price %} - -
- - - {{ price_info.formatted_price_sales_uom }} - {{ price_info.currency }} - - - - {% if price_info.formatted_mrp %} - - MRP {{ price_info.formatted_mrp }} - - - -{{ price_info.get("formatted_discount_percent") or price_info.get("formatted_discount_rate")}} - - {% endif %} - - - - ({{ price_info.formatted_price }} / {{ product_info.uom }}) - -
- {% else %} - {{ _("UOM") }} : {{ product_info.uom }} - {% endif %} - - {% if cart_settings.show_stock_availability %} -
- {% 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 }}) - {% endif %} - - {% endif %} -
- {% endif %} - - - {% if doc.offers %} -
-
- - - - Available Offers -
-
- {% for offer in doc.offers %} -
-
- - - - - - -
-

- {{ _(offer.offer_title) }}: - {{ _(offer.offer_subtitle) if offer.offer_subtitle else '' }} - - {{ _("More") }} - -

-
- {% endfor %} -
- {% endif %} - - -
-
- - {% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %} - - - {% endif %} - - - {% if cart_settings.show_contact_us_button %} - {% include "templates/generators/item/item_inquiry.html" %} - {% endif %} -
-
-
-
- - - -{% endif %} diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html deleted file mode 100644 index e97a275fbd8e..000000000000 --- a/erpnext/templates/generators/item/item_configure.html +++ /dev/null @@ -1,20 +0,0 @@ -{% if shopping_cart and shopping_cart.cart_settings.enabled %} -{% set cart_settings = shopping_cart.cart_settings %} - -
- {% if cart_settings.enable_variants | int %} - - {% endif %} - {% if cart_settings.show_contact_us_button %} - {% include "templates/generators/item/item_inquiry.html" %} - {% endif %} -
- -{% endif %} diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js deleted file mode 100644 index 9beba3fd01eb..000000000000 --- a/erpnext/templates/generators/item/item_configure.js +++ /dev/null @@ -1,343 +0,0 @@ -class ItemConfigure { - constructor(item_code, item_name) { - this.item_code = item_code; - this.item_name = item_name; - - this.get_attributes_and_values() - .then(attribute_data => { - this.attribute_data = attribute_data; - this.show_configure_dialog(); - }); - } - - show_configure_dialog() { - const fields = this.attribute_data.map(a => { - return { - fieldtype: 'Select', - label: a.attribute, - fieldname: a.attribute, - options: a.values.map(v => { - return { - label: v, - value: v - }; - }), - change: (e) => { - this.on_attribute_selection(e); - } - }; - }); - - this.dialog = new frappe.ui.Dialog({ - title: __('Select Variant for {0}', [this.item_name]), - fields, - on_hide: () => { - set_continue_configuration(); - } - }); - - this.attribute_data.forEach(a => { - const field = this.dialog.get_field(a.attribute); - const $a = $(`${__("Clear")}`); - $a.on('click', (e) => { - e.preventDefault(); - this.dialog.set_value(a.attribute, ''); - }); - field.$wrapper.find('.help-box').append($a); - }); - - this.append_status_area(); - this.dialog.show(); - - this.dialog.set_values(JSON.parse(localStorage.getItem(this.get_cache_key()))); - - $('.btn-configure').prop('disabled', false); - } - - on_attribute_selection(e) { - if (e) { - const changed_fieldname = $(e.target).data('fieldname'); - this.show_range_input_if_applicable(changed_fieldname); - } else { - this.show_range_input_for_all_fields(); - } - - const values = this.dialog.get_values(); - if (Object.keys(values).length === 0) { - this.clear_status(); - localStorage.removeItem(this.get_cache_key()); - return; - } - - // save state - localStorage.setItem(this.get_cache_key(), JSON.stringify(values)); - - // show - this.set_loading_status(); - - this.get_next_attribute_and_values(values) - .then(data => { - const { - valid_options_for_attributes, - } = data; - - this.set_item_found_status(data); - - for (let attribute in valid_options_for_attributes) { - const valid_options = valid_options_for_attributes[attribute]; - const options = this.dialog.get_field(attribute).df.options; - const new_options = options.map(o => { - o.disabled = !valid_options.includes(o.value); - return o; - }); - - this.dialog.set_df_property(attribute, 'options', new_options); - this.dialog.get_field(attribute).set_options(); - } - }); - } - - show_range_input_for_all_fields() { - this.dialog.fields.forEach(f => { - this.show_range_input_if_applicable(f.fieldname); - }); - } - - show_range_input_if_applicable(fieldname) { - const changed_field = this.dialog.get_field(fieldname); - const changed_value = changed_field.get_value(); - if (changed_value && changed_value.includes(' to ')) { - // possible range input - let numbers = changed_value.split(' to '); - numbers = numbers.map(number => parseFloat(number)); - - if (!numbers.some(n => isNaN(n))) { - numbers.sort((a, b) => a - b); - if (changed_field.$input_wrapper.find('.range-selector').length) { - return; - } - const parent = $('
') - .insertBefore(changed_field.$input_wrapper.find('.help-box')); - const control = frappe.ui.form.make_control({ - df: { - fieldtype: 'Int', - label: __('Enter value betweeen {0} and {1}', [numbers[0], numbers[1]]), - change: () => { - const value = control.get_value(); - if (value < numbers[0] || value > numbers[1]) { - control.$wrapper.addClass('was-validated'); - control.set_description( - __('Value must be between {0} and {1}', [numbers[0], numbers[1]])); - control.$input[0].setCustomValidity('error'); - } else { - control.$wrapper.removeClass('was-validated'); - control.set_description(''); - control.$input[0].setCustomValidity(''); - this.update_range_values(fieldname, value); - } - } - }, - render_input: true, - parent - }); - control.$wrapper.addClass('mt-3'); - } - } - } - - update_range_values(attribute, range_value) { - this.range_values = this.range_values || {}; - this.range_values[attribute] = range_value; - } - - show_remaining_optional_attributes() { - // show all attributes if remaining - // unselected attributes are all optional - const unselected_attributes = this.dialog.fields.filter(df => { - const value_selected = this.dialog.get_value(df.fieldname); - return !value_selected; - }); - const is_optional_attribute = df => { - const optional_attributes = this.attribute_data - .filter(a => a.optional).map(a => a.attribute); - return optional_attributes.includes(df.fieldname); - }; - if (unselected_attributes.every(is_optional_attribute)) { - unselected_attributes.forEach(df => { - this.dialog.fields_dict[df.fieldname].$wrapper.show(); - }); - } - } - - set_loading_status() { - this.dialog.$status_area.html(` - - `); - } - - set_item_found_status(data) { - const html = this.get_html_for_item_found(data); - this.dialog.$status_area.html(html); - } - - clear_status() { - this.dialog.$status_area.empty(); - } - - get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info, available_qty, settings }) { - const one_item = exact_match.length === 1 - ? exact_match[0] - : filtered_items_count === 1 - ? filtered_items[0] - : ''; - - let item_add_to_cart = one_item ? ` - - ` : ''; - - const items_found = filtered_items_count === 1 ? - __('{0} item found.', [filtered_items_count]) : - __('{0} items found.', [filtered_items_count]); - - /* eslint-disable indent */ - const item_found_status = exact_match.length === 1 - ? `` - : ``; - /* eslint-disable indent */ - - if (!product_info?.allow_items_not_in_stock && available_qty === 0 - && product_info && product_info?.is_stock_item) { - item_add_to_cart = ''; - } - - return ` - ${item_found_status} - ${item_add_to_cart} - `; - } - - btn_add_to_cart(e) { - if (frappe.session.user !== 'Guest') { - localStorage.removeItem(this.get_cache_key()); - } - const item_code = $(e.currentTarget).data('item-code'); - const additional_notes = Object.keys(this.range_values || {}).map(attribute => { - return `${attribute}: ${this.range_values[attribute]}`; - }).join('\n'); - erpnext.e_commerce.shopping_cart.update_cart({ - item_code, - additional_notes, - qty: 1 - }); - this.dialog.hide(); - } - - btn_clear_values() { - this.dialog.fields_list.forEach(f => { - if (f.df?.options) { - f.df.options = f.df.options.map(option => { - option.disabled = false; - return option; - }); - } - }); - this.dialog.clear(); - this.dialog.$status_area.empty(); - this.on_attribute_selection(); - } - - append_status_area() { - this.dialog.$status_area = $('
'); - this.dialog.$wrapper.find('.modal-body').append(this.dialog.$status_area); - this.dialog.$wrapper.on('click', '[data-action]', (e) => { - e.preventDefault(); - const $target = $(e.currentTarget); - const action = $target.data('action'); - const method = this[action]; - method.call(this, e); - }); - this.dialog.$wrapper.addClass('item-configurator-dialog'); - } - - get_next_attribute_and_values(selected_attributes) { - return this.call('erpnext.e_commerce.variant_selector.utils.get_next_attribute_and_values', { - item_code: this.item_code, - selected_attributes - }); - } - - get_attributes_and_values() { - return this.call('erpnext.e_commerce.variant_selector.utils.get_attributes_and_values', { - item_code: this.item_code - }); - } - - get_cache_key() { - return `configure:${this.item_code}`; - } - - call(method, args) { - // promisified frappe.call - return new Promise((resolve, reject) => { - frappe.call(method, args) - .then(r => resolve(r.message)) - .fail(reject); - }); - } -} - -function set_continue_configuration() { - const $btn_configure = $('.btn-configure'); - const { itemCode } = $btn_configure.data(); - - if (localStorage.getItem(`configure:${itemCode}`)) { - $btn_configure.text(__('Continue Selection')); - } else { - $btn_configure.text(__('Select Variant')); - } -} - -frappe.ready(() => { - const $btn_configure = $('.btn-configure'); - if (!$btn_configure.length) return; - const { itemCode, itemName } = $btn_configure.data(); - - set_continue_configuration(); - - $btn_configure.on('click', () => { - $btn_configure.prop('disabled', true); - new ItemConfigure(itemCode, itemName); - }); -}); diff --git a/erpnext/templates/generators/item/item_details.html b/erpnext/templates/generators/item/item_details.html deleted file mode 100644 index 028936bf5f72..000000000000 --- a/erpnext/templates/generators/item/item_details.html +++ /dev/null @@ -1,63 +0,0 @@ -{% set width_class = "expand" if not slides else "" %} -{% set cart_settings = shopping_cart.cart_settings %} -{% set product_info = shopping_cart.product_info %} -{% set price_info = product_info.get('price') or {} %} - -
-
- -
- {{ doc.web_item_name }} -
- - - {% if cart_settings.enable_wishlist %} - - {% endif %} -
- -

- - {{ _(doc.item_group) }} - - - {{ _("Item Code") }}: - - {{ doc.item_code }} -

- {% if has_variants %} - - {% include "templates/generators/item/item_configure.html" %} - {% else %} - - {% include "templates/generators/item/item_add_to_cart.html" %} - {% endif %} - -
- {% if frappe.utils.strip_html(doc.web_long_description or '') %} - {{ doc.web_long_description | safe }} - {% elif frappe.utils.strip_html(doc.description or '') %} - {{ doc.description | safe }} - {% else %} - {{ "" }} - {% endif %} -
-
- -{% block base_scripts %} - - -{% endblock %} - - \ No newline at end of file diff --git a/erpnext/templates/generators/item/item_image.html b/erpnext/templates/generators/item/item_image.html deleted file mode 100644 index e1bb3b98655a..000000000000 --- a/erpnext/templates/generators/item/item_image.html +++ /dev/null @@ -1,108 +0,0 @@ -{% set column_size = 5 if slides else 4 %} -
- {% if slides %} -
- {% for item in slides %} - {{ item.heading }} - {% endfor %} -
- {{ product_image(slides[0].image, 'product-image') }} - - - {% else %} - {{ product_image(doc.website_image, alt=doc.website_image_alt or doc.item_name) }} - {% endif %} - - - - -
- - diff --git a/erpnext/templates/generators/item/item_inquiry.html b/erpnext/templates/generators/item/item_inquiry.html deleted file mode 100644 index af636f1582b5..000000000000 --- a/erpnext/templates/generators/item/item_inquiry.html +++ /dev/null @@ -1,11 +0,0 @@ -{% if shopping_cart and shopping_cart.cart_settings.enabled %} -{% set cart_settings = shopping_cart.cart_settings %} - {% if cart_settings.show_contact_us_button | int %} - - {% endif %} - -{% endif %} diff --git a/erpnext/templates/generators/item/item_inquiry.js b/erpnext/templates/generators/item/item_inquiry.js deleted file mode 100644 index 0aee99667247..000000000000 --- a/erpnext/templates/generators/item/item_inquiry.js +++ /dev/null @@ -1,77 +0,0 @@ -frappe.ready(() => { - const d = new frappe.ui.Dialog({ - title: __('Contact Us'), - fields: [ - { - fieldtype: 'Data', - label: __('Full Name'), - fieldname: 'lead_name', - reqd: 1 - }, - { - fieldtype: 'Data', - label: __('Organization Name'), - fieldname: 'company_name', - }, - { - fieldtype: 'Data', - label: __('Email'), - fieldname: 'email_id', - options: 'Email', - reqd: 1 - }, - { - fieldtype: 'Data', - label: __('Phone Number'), - fieldname: 'phone', - options: 'Phone', - reqd: 1 - }, - { - fieldtype: 'Data', - label: __('Subject'), - fieldname: 'subject', - reqd: 1 - }, - { - fieldtype: 'Text', - label: __('Message'), - fieldname: 'message', - reqd: 1 - } - ], - primary_action: send_inquiry, - primary_action_label: __('Send') - }); - - function send_inquiry() { - const values = d.get_values(); - const doc = Object.assign({}, values); - delete doc.subject; - delete doc.message; - - d.hide(); - - frappe.call('erpnext.e_commerce.shopping_cart.cart.create_lead_for_item_inquiry', { - lead: doc, - subject: values.subject, - message: values.message - }).then(r => { - if (r.message) { - d.clear(); - } - }); - } - - $('.btn-inquiry').click((e) => { - const $btn = $(e.target); - const item_code = $btn.data('item-code'); - d.set_value('subject', 'Inquiry about ' + item_code); - if (!['Administrator', 'Guest'].includes(frappe.session.user)) { - d.set_value('email_id', frappe.session.user); - d.set_value('lead_name', frappe.get_cookie('full_name')); - } - - d.show(); - }); -}); diff --git a/erpnext/templates/generators/item/item_reviews.html b/erpnext/templates/generators/item/item_reviews.html deleted file mode 100644 index c62c6f7749da..000000000000 --- a/erpnext/templates/generators/item/item_reviews.html +++ /dev/null @@ -1,88 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %} - -
- -
-
- {{ _("Customer Reviews") }} -
- -
- - {% if frappe.session.user != "Guest" and user_is_customer %} - - {% endif %} -
-
- - - {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }} - - - -
- {% if reviews %} - {{ user_review(reviews) }} - - {% if total_reviews > 4 %} - - {% endif %} - - {% else %} -
- {{ _("No Reviews") }} -
- {% endif %} -
-
- - diff --git a/erpnext/templates/generators/item/item_specifications.html b/erpnext/templates/generators/item/item_specifications.html deleted file mode 100644 index 0814d81c8ab2..000000000000 --- a/erpnext/templates/generators/item/item_specifications.html +++ /dev/null @@ -1,20 +0,0 @@ - -{% if website_specifications %} -
-
- {% if not show_tabs %} -
- Product Details -
- {% endif %} - - {% for d in website_specifications -%} - - - - - {%- endfor %} -
{{ d.label }}{{ d.description }}
-
-
-{% endif %} diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html deleted file mode 100644 index 956c3c51e6e7..000000000000 --- a/erpnext/templates/generators/item_group.html +++ /dev/null @@ -1,72 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import field_filter_section, attribute_filter_section, discount_range_filters %} -{% extends "templates/web.html" %} - -{% block header %} -
{{ _(item_group_name) }}
-{% endblock header %} - -{% block script %} - -{% endblock %} - -{% block breadcrumbs %} -
- {% include "templates/includes/breadcrumbs.html" %} -
-{% endblock %} - -{% block page_content %} -
-
- {% if slideshow %} - {{ web_block( - "Hero Slider", - values=slideshow, - add_container=0, - add_top_padding=0, - add_bottom_padding=0, - ) }} - {% endif %} - - {% if description %} -
{{ description or ""}}
- {% endif %} -
-
-
- -
- -
-
-
-
{{ _('Filters') }}
- {{ _('Clear All') }} -
- - {{ field_filter_section(field_filters) }} - - - {{ attribute_filter_section(attribute_filters) }} - -
- -
-
-
- - -{% endblock %} diff --git a/erpnext/templates/includes/cart/address_card.html b/erpnext/templates/includes/cart/address_card.html deleted file mode 100644 index 830ed649f5f4..000000000000 --- a/erpnext/templates/includes/cart/address_card.html +++ /dev/null @@ -1,17 +0,0 @@ -
-
- {{ _('Change') }} -
-
-
{{ address.title }}
-
- {{ address.display }} -
- - - - - {{ _('Edit') }} - -
-
diff --git a/erpnext/templates/includes/cart/address_picker_card.html b/erpnext/templates/includes/cart/address_picker_card.html deleted file mode 100644 index 646210e65f1a..000000000000 --- a/erpnext/templates/includes/cart/address_picker_card.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
- -
-
-
{{ address.title }}
-

- {{ address.display }} -

- {{ _('Edit') }} -
-
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html deleted file mode 100644 index a8188ec82546..000000000000 --- a/erpnext/templates/includes/cart/cart_address.html +++ /dev/null @@ -1,189 +0,0 @@ -{% from "erpnext/templates/includes/cart/cart_macros.html" import show_address %} - -{% if addresses | length == 1%} - {% set select_address = True %} -{% endif %} - -
-
-
{{ _("Shipping Address") }}
- -
- -
- {% for address in shipping_addresses %} - {% if doc.shipping_address_name == address.name %} -
-
- {% include "templates/includes/cart/address_card.html" %} -
-
- {% endif %} - {% endfor %} -
- - -
- -
- -{% if billing_addresses %} -
-
-
{{ _("Billing Address") }}
- -
- -
- {% for address in billing_addresses %} - {% if doc.customer_address == address.name %} -
-
- {% include "templates/includes/cart/address_card.html" %} -
-
- {% endif %} - {% endfor %} -
-{% endif %} - - diff --git a/erpnext/templates/includes/cart/cart_address_picker.html b/erpnext/templates/includes/cart/cart_address_picker.html deleted file mode 100644 index 66a50ecc9f3a..000000000000 --- a/erpnext/templates/includes/cart/cart_address_picker.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
{{ _("Shipping Address") }}
-
diff --git a/erpnext/templates/includes/cart/cart_dropdown.html b/erpnext/templates/includes/cart/cart_dropdown.html deleted file mode 100644 index 38ad1839169f..000000000000 --- a/erpnext/templates/includes/cart/cart_dropdown.html +++ /dev/null @@ -1,27 +0,0 @@ -
- - -
-
- {{ _("Item") }} -
-
- {{ _("Price") }} -
-
- - {% if doc.items %} -
-
- {% include "templates/includes/cart/cart_items_dropdown.html" %} -
-
- {% else %} -

{{ _("Cart is Empty") }}

- {% endif %} -
diff --git a/erpnext/templates/includes/cart/cart_items.html b/erpnext/templates/includes/cart/cart_items.html deleted file mode 100644 index 428b36e9b3c0..000000000000 --- a/erpnext/templates/includes/cart/cart_items.html +++ /dev/null @@ -1,113 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import product_image %} - -{% macro item_subtotal(item) %} -
- {{ item.get_formatted('amount') }} -
- - {% if item.is_free_item %} -
- - {{ _('FREE') }} - -
- {% else %} - - {{ _('Rate:') }} {{ item.get_formatted('rate') }} - - {% endif %} -{% endmacro %} - -{% for d in doc.items %} - - -
-
- {% if d.thumbnail %} - {{ product_image(d.thumbnail, alt="d.web_item_name", no_border=True) }} - {% else %} -
- {{ frappe.utils.get_abbr(d.web_item_name) or "NA" }} -
- {% endif %} -
- -
-
- {{ d.get("web_item_name") or d.item_name }} -
-
- {{ d.item_code }} -
- {%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %} - {% if variant_of %} - - {{ _('Variant of') }} - - {{ variant_of }} - - - {% endif %} - -
- -
-
-
- - - - -
- {% set disabled = 'disabled' if d.is_free_item else '' %} -
- - - - - - - - - -
- -
- {% if not d.is_free_item %} -
- - - -
- {% endif %} -
-
- - - - {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} -
- {{ item_subtotal(d) }} -
- {% endif %} - - - - {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} - - {{ item_subtotal(d) }} - - {% endif %} - -{% endfor %} diff --git a/erpnext/templates/includes/cart/cart_items_dropdown.html b/erpnext/templates/includes/cart/cart_items_dropdown.html deleted file mode 100644 index 5d107fc0d069..000000000000 --- a/erpnext/templates/includes/cart/cart_items_dropdown.html +++ /dev/null @@ -1,12 +0,0 @@ -{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description_cart %} - -{% for d in doc.items %} -
-
- {{ item_name_and_description_cart(d) }} -
-
- {{ d.get_formatted("amount") }} -
-
-{% endfor %} diff --git a/erpnext/templates/includes/cart/cart_items_total.html b/erpnext/templates/includes/cart/cart_items_total.html deleted file mode 100644 index c94fde462b14..000000000000 --- a/erpnext/templates/includes/cart/cart_items_total.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - {{ _("Total") }} - - - {{ doc.get_formatted("total") }} - - \ No newline at end of file diff --git a/erpnext/templates/includes/cart/cart_macros.html b/erpnext/templates/includes/cart/cart_macros.html deleted file mode 100644 index fd95dba42429..000000000000 --- a/erpnext/templates/includes/cart/cart_macros.html +++ /dev/null @@ -1,22 +0,0 @@ -{% macro show_address(address, doc, fieldname, select_address=False) %} -{% set selected=address.name==doc.get(fieldname) %} - -
-
-
-
- {{ address.name }}
-
-
-
-
-
-
{{ address.display }}
-
-
-{% endmacro %} diff --git a/erpnext/templates/includes/cart/cart_payment_summary.html b/erpnext/templates/includes/cart/cart_payment_summary.html deleted file mode 100644 index b5655a237b10..000000000000 --- a/erpnext/templates/includes/cart/cart_payment_summary.html +++ /dev/null @@ -1,84 +0,0 @@ - -{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} -
- {{ _("Payment Summary") }} -
-{% endif %} - -
-
- {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} - - - {% set total_items = frappe.utils.cstr(frappe.utils.flt(doc.total_qty, 0)) %} - - - - - - {% for d in doc.taxes %} - {% if d.base_tax_amount %} - - - - - {% endif %} - {% endfor %} -
{{ _("Net Total (") + total_items + _(" Items)") }}{{ doc.get_formatted("net_total") }}
- {{ d.description }} - - {{ d.get_formatted("base_tax_amount") }} -
- - - - - - - - - -
{{ _("Grand Total") }}{{ doc.get_formatted("grand_total") }}
- {% endif %} - - {% if cart_settings.enable_checkout %} - - {% else %} - - {% endif %} -
-
- - - \ No newline at end of file diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html deleted file mode 100644 index d7adae562ed3..000000000000 --- a/erpnext/templates/includes/navbar/navbar_items.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends 'frappe/templates/includes/navbar/navbar_items.html' %} - -{% block navbar_right_extension %} - - {% if frappe.db.get_single_value("E Commerce Settings", "enable_wishlist") %} - - {% endif %} -{% endblock %} diff --git a/erpnext/templates/includes/order/order_macros.html b/erpnext/templates/includes/order/order_macros.html deleted file mode 100644 index d95b28961c6b..000000000000 --- a/erpnext/templates/includes/order/order_macros.html +++ /dev/null @@ -1,52 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import product_image %} - -{% macro item_name_and_description(d) %} -
-
-
- {% if d.thumbnail or d.image %} - {{ product_image(d.thumbnail or d.image, no_border=True) }} - {% else %} -
- {{ frappe.utils.get_abbr(d.item_name) or "NA" }} -
- {% endif %} -
-
-
- {{ d.item_code }} -
- {{ html2text(d.description) | truncate(140) }} -
- - {{ _("Qty ") }}({{ d.get_formatted("qty") }}) - -
-
-{% endmacro %} - -{% macro item_name_and_description_cart(d) %} -
-
-
- {{ product_image_square(d.thumbnail or d.image) }} -
-
-
- {{ d.item_name|truncate(25) }} -
- - - - - - - -
-
-
-{% endmacro %} diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js deleted file mode 100644 index a3979d037b6c..000000000000 --- a/erpnext/templates/includes/product_page.js +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -frappe.ready(function() { - window.item_code = $('[itemscope] [itemprop="productID"]').text().trim(); - var qty = 0; - - frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.product_info.get_product_info_for_website", - args: { - item_code: get_item_code() - }, - callback: function(r) { - if(r.message) { - if(r.message.cart_settings.enabled) { - let hide_add_to_cart = !r.message.product_info.price - || (!r.message.product_info.in_stock && !r.message.cart_settings.allow_items_not_in_stock); - $(".item-cart, .item-price, .item-stock").toggleClass('hide', hide_add_to_cart); - } - if(r.message.cart_settings.show_price) { - $(".item-price").toggleClass("hide", false); - } - if(r.message.cart_settings.show_stock_availability) { - $(".item-stock").toggleClass("hide", false); - } - if(r.message.product_info.price) { - $(".item-price") - .html(r.message.product_info.price.formatted_price_sales_uom + "
\ - (" + r.message.product_info.price.formatted_price + " / " + r.message.product_info.uom + ")
"); - - if(r.message.product_info.in_stock===0) { - $(".item-stock").html("
{{ _("Not in stock") }}
"); - } - else if(r.message.product_info.in_stock===1 && r.message.cart_settings.show_stock_availability) { - var qty_display = "{{ _("In stock") }}"; - if (r.message.product_info.show_stock_qty) { - qty_display += " ("+r.message.product_info.stock_qty+")"; - } - $(".item-stock").html("
\ - "+qty_display+"
"); - } - - if(r.message.product_info.qty) { - qty = r.message.product_info.qty; - toggle_update_cart(r.message.product_info.qty); - } else { - toggle_update_cart(0); - } - } - } - } - }) - - $("#item-add-to-cart button").on("click", function() { - frappe.provide('erpnext.shopping_cart'); - - erpnext.shopping_cart.update_cart({ - item_code: get_item_code(), - qty: $("#item-spinner .cart-qty").val(), - callback: function(r) { - if(!r.exc) { - toggle_update_cart(1); - qty = 1; - } - }, - btn: this, - }); - }); - - $("#item-spinner").on('click', '.number-spinner button', function () { - var btn = $(this), - input = btn.closest('.number-spinner').find('input'), - oldValue = input.val().trim(), - newVal = 0; - - if (btn.attr('data-dir') == 'up') { - newVal = Number.parseInt(oldValue) + 1; - } else if (btn.attr('data-dir') == 'dwn') { - if (Number.parseInt(oldValue) > 1) { - newVal = Number.parseInt(oldValue) - 1; - } - else { - newVal = Number.parseInt(oldValue); - } - } - input.val(newVal); - }); - - $("[itemscope] .item-view-attribute .form-control").on("change", function() { - try { - var item_code = encodeURIComponent(get_item_code()); - - } catch(e) { - // unable to find variant - // then chose the closest available one - - var attribute = $(this).attr("data-attribute"); - var attribute_value = $(this).val(); - var item_code = find_closest_match(attribute, attribute_value); - - if (!item_code) { - frappe.msgprint(__("Cannot find a matching Item. Please select some other value for {0}.", [attribute])) - throw e; - } - } - - if (window.location.search == ("?variant=" + item_code) || window.location.search.includes(item_code)) { - return; - } - - window.location.href = window.location.pathname + "?variant=" + item_code; - }); - - // change the item image src when alternate images are hovered - $(document.body).on('mouseover', '.item-alternative-image', (e) => { - const $alternative_image = $(e.currentTarget); - const src = $alternative_image.find('img').prop('src'); - $('.item-image img').prop('src', src); - }); -}); - -var toggle_update_cart = function(qty) { - $("#item-add-to-cart").toggle(qty ? false : true); - $("#item-update-cart") - .toggle(qty ? true : false) - .find("input").val(qty); - $("#item-spinner").toggle(qty ? false : true); -} - -function get_item_code() { - var variant_info = window.variant_info; - if(variant_info) { - var attributes = get_selected_attributes(); - var no_of_attributes = Object.keys(attributes).length; - - for(var i in variant_info) { - var variant = variant_info[i]; - - if (variant.attributes.length < no_of_attributes) { - // the case when variant has less attributes than template - continue; - } - - var match = true; - for(var j in variant.attributes) { - if(attributes[variant.attributes[j].attribute] - != variant.attributes[j].attribute_value - ) { - match = false; - break; - } - } - if(match) { - return variant.name; - } - } - throw "Unable to match variant"; - } else { - return window.item_code; - } -} - -function find_closest_match(selected_attribute, selected_attribute_value) { - // find the closest match keeping the selected attribute in focus and get the item code - - var attributes = get_selected_attributes(); - - var previous_match_score = 0; - var previous_no_of_attributes = 0; - var matched; - - var variant_info = window.variant_info; - for(var i in variant_info) { - var variant = variant_info[i]; - var match_score = 0; - var has_selected_attribute = false; - - for(var j in variant.attributes) { - if(attributes[variant.attributes[j].attribute]===variant.attributes[j].attribute_value) { - match_score = match_score + 1; - - if (variant.attributes[j].attribute==selected_attribute && variant.attributes[j].attribute_value==selected_attribute_value) { - has_selected_attribute = true; - } - } - } - - if (has_selected_attribute - && ((match_score > previous_match_score) || (match_score==previous_match_score && previous_no_of_attributes < variant.attributes.length))) { - previous_match_score = match_score; - matched = variant; - previous_no_of_attributes = variant.attributes.length; - - - } - } - - if (matched) { - for (var j in matched.attributes) { - var attr = matched.attributes[j]; - $('[itemscope]') - .find(repl('.item-view-attribute .form-control[data-attribute="%(attribute)s"]', attr)) - .val(attr.attribute_value); - } - - return matched.name; - } -} - -function get_selected_attributes() { - var attributes = {}; - $('[itemscope]').find(".item-view-attribute .form-control").each(function() { - attributes[$(this).attr('data-attribute')] = $(this).val(); - }); - return attributes; -} diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html deleted file mode 100644 index 2b7d9e35239e..000000000000 --- a/erpnext/templates/pages/cart.html +++ /dev/null @@ -1,132 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} {{ _("Shopping Cart") }} {% endblock %} - -{% block header %}

{{ _("Shopping Cart") }}

{% endblock %} - -{% block header_actions %} -{% endblock %} - -{% block page_content %} - -{% from "templates/includes/macros.html" import item_name_and_description %} - -{% if doc.items %} -
-
- -
-
- -
- {{ _('Items') }} -
- - - - - - {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} - - {% endif %} - - - - - {% include "templates/includes/cart/cart_items.html" %} - - - {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %} - - {% include "templates/includes/cart/cart_items_total.html" %} - - {% endif %} -
{{ _('Item') }}{{ _('Quantity') }}{{ _('Subtotal') }}
- -
-
- {% if cart_settings.enable_checkout %} - - {{ _('Past Orders') }} - - {% else %} - - {{ _('Past Quotes') }} - - {% endif %} -
-
- {% if doc.items %} - - {% endif %} -
-
-
- - - {% if doc.items %} - {% if doc.terms %} -
-
{{ _("Terms and Conditions") }}
-
- {{ doc.terms }} -
-
- {% endif %} -
- - -
-
- - {% set show_coupon_code = cart_settings.show_apply_coupon_code_in_website and cart_settings.enable_checkout %} - {% if show_coupon_code == 1%} -
-
- - - -
-
- {% endif %} - -
- {% include "templates/includes/cart/cart_payment_summary.html" %} -
- - {% include "templates/includes/cart/cart_address.html" %} -
-
- {% endif %} -
-
-{% else %} -
-
- Empty State -
-
{{ _('Your cart is Empty') }}

- {% if cart_settings.enable_checkout %} - - {{ _('See past orders') }} - - {% else %} - - {{ _('See past quotations') }} - - {% endif %} -
-{% endif %} - -{% endblock %} - -{% block base_scripts %} - -{{ include_script("frappe-web.bundle.js") }} -{{ include_script("controls.bundle.js") }} -{{ include_script("dialog.bundle.js") }} -{% endblock %} diff --git a/erpnext/templates/pages/cart.js b/erpnext/templates/pages/cart.js deleted file mode 100644 index fb2d159dcf93..000000000000 --- a/erpnext/templates/pages/cart.js +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -// JS exclusive to /cart page -frappe.provide("erpnext.e_commerce.shopping_cart"); -var shopping_cart = erpnext.e_commerce.shopping_cart; - -$.extend(shopping_cart, { - show_error: function(title, text) { - $("#cart-container").html('

' + - title + '

' + text + '

'); - }, - - bind_events: function() { - shopping_cart.bind_address_picker_dialog(); - shopping_cart.bind_place_order(); - shopping_cart.bind_request_quotation(); - shopping_cart.bind_change_qty(); - shopping_cart.bind_remove_cart_item(); - shopping_cart.bind_change_notes(); - shopping_cart.bind_coupon_code(); - }, - - bind_address_picker_dialog: function() { - const d = this.get_update_address_dialog(); - this.parent.find('.btn-change-address').on('click', (e) => { - const type = $(e.currentTarget).parents('.address-container').attr('data-address-type'); - $(d.get_field('address_picker').wrapper).html( - this.get_address_template(type) - ); - d.show(); - }); - }, - - get_update_address_dialog() { - let d = new frappe.ui.Dialog({ - title: "Select Address", - fields: [{ - 'fieldtype': 'HTML', - 'fieldname': 'address_picker', - }], - primary_action_label: __('Set Address'), - primary_action: () => { - const $card = d.$wrapper.find('.address-card.active'); - const address_type = $card.closest('[data-address-type]').attr('data-address-type'); - const address_name = $card.closest('[data-address-name]').attr('data-address-name'); - frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.update_cart_address", - freeze: true, - args: { - address_type, - address_name - }, - callback: function(r) { - d.hide(); - if (!r.exc) { - $(".cart-tax-items").html(r.message.total); - shopping_cart.parent.find( - `.address-container[data-address-type="${address_type}"]` - ).html(r.message.address); - } - } - }); - } - }); - - return d; - }, - - get_address_template(type) { - return { - shipping: `
-
- {% for address in shipping_addresses %} -
- {% include "templates/includes/cart/address_picker_card.html" %} -
- {% endfor %} -
-
`, - billing: `
-
- {% for address in billing_addresses %} -
- {% include "templates/includes/cart/address_picker_card.html" %} -
- {% endfor %} -
-
`, - }[type]; - }, - - bind_place_order: function() { - $(".btn-place-order").on("click", function() { - shopping_cart.place_order(this); - }); - }, - - bind_request_quotation: function() { - $('.btn-request-for-quotation').on('click', function() { - shopping_cart.request_quotation(this); - }); - }, - - bind_change_qty: function() { - // bind update button - $(".cart-items").on("change", ".cart-qty", function() { - var item_code = $(this).attr("data-item-code"); - var newVal = $(this).val(); - shopping_cart.shopping_cart_update({item_code, qty: newVal}); - }); - - $(".cart-items").on('click', '.number-spinner button', function () { - var btn = $(this), - input = btn.closest('.number-spinner').find('input'), - oldValue = input.val().trim(), - newVal = 0; - - if (btn.attr('data-dir') == 'up') { - newVal = parseInt(oldValue) + 1; - } else { - if (oldValue > 1) { - newVal = parseInt(oldValue) - 1; - } - } - input.val(newVal); - - let notes = input.closest("td").siblings().find(".notes").text().trim(); - var item_code = input.attr("data-item-code"); - shopping_cart.shopping_cart_update({ - item_code, - qty: newVal, - additional_notes: notes - }); - }); - }, - - bind_change_notes: function() { - $('.cart-items').on('change', 'textarea', function() { - const $textarea = $(this); - const item_code = $textarea.attr('data-item-code'); - const qty = $textarea.closest('tr').find('.cart-qty').val(); - const notes = $textarea.val(); - shopping_cart.shopping_cart_update({ - item_code, - qty, - additional_notes: notes - }); - }); - }, - - bind_remove_cart_item: function() { - $(".cart-items").on("click", ".remove-cart-item", (e) => { - const $remove_cart_item_btn = $(e.currentTarget); - var item_code = $remove_cart_item_btn.data("item-code"); - - shopping_cart.shopping_cart_update({ - item_code: item_code, - qty: 0 - }); - }); - }, - - render_tax_row: function($cart_taxes, doc, shipping_rules) { - var shipping_selector; - if(shipping_rules) { - shipping_selector = ''; - } - - var $tax_row = $(repl('
\ -
\ -
\ -
' + - (shipping_selector || '

%(description)s

') + - '
\ -
\ -
\ -
\ - %(formatted_tax_amount)s

\ -
\ -
', doc)).appendTo($cart_taxes); - - if(shipping_selector) { - $tax_row.find('select option').each(function(i, opt) { - if($(opt).html() == doc.description) { - $(opt).attr("selected", "selected"); - } - }); - $tax_row.find('select').on("change", function() { - shopping_cart.apply_shipping_rule($(this).val(), this); - }); - } - }, - - apply_shipping_rule: function(rule, btn) { - return frappe.call({ - btn: btn, - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.apply_shipping_rule", - args: { shipping_rule: rule }, - callback: function(r) { - if(!r.exc) { - shopping_cart.render(r.message); - } - } - }); - }, - - place_order: function(btn) { - shopping_cart.freeze(); - - return frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.place_order", - btn: btn, - callback: function(r) { - if(r.exc) { - shopping_cart.unfreeze(); - var msg = ""; - if(r._server_messages) { - msg = JSON.parse(r._server_messages || []).join("
"); - } - - $("#cart-error") - .empty() - .html(msg || frappe._("Something went wrong!")) - .toggle(true); - } else { - $(btn).hide(); - window.location.href = '/orders/' + encodeURIComponent(r.message); - } - } - }); - }, - - request_quotation: function(btn) { - shopping_cart.freeze(); - - return frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.request_for_quotation", - btn: btn, - callback: function(r) { - if(r.exc) { - shopping_cart.unfreeze(); - var msg = ""; - if(r._server_messages) { - msg = JSON.parse(r._server_messages || []).join("
"); - } - - $("#cart-error") - .empty() - .html(msg || frappe._("Something went wrong!")) - .toggle(true); - } else { - $(btn).hide(); - window.location.href = '/quotations/' + encodeURIComponent(r.message); - } - } - }); - }, - - bind_coupon_code: function() { - $(".bt-coupon").on("click", function() { - shopping_cart.apply_coupon_code(this); - }); - }, - - apply_coupon_code: function(btn) { - return frappe.call({ - type: "POST", - method: "erpnext.e_commerce.shopping_cart.cart.apply_coupon_code", - btn: btn, - args : { - applied_code : $('.txtcoupon').val(), - applied_referral_sales_partner: $('.txtreferral_sales_partner').val() - }, - callback: function(r) { - if (r && r.message){ - location.reload(); - } - } - }); - } -}); - -frappe.ready(function() { - if (window.location.pathname === "/cart") { - $(".cart-icon").hide(); - } - shopping_cart.parent = $(".cart-container"); - shopping_cart.bind_events(); -}); - -function show_terms() { - var html = $(".cart-terms").html(); - frappe.msgprint(html); -} diff --git a/erpnext/templates/pages/cart.py b/erpnext/templates/pages/cart.py deleted file mode 100644 index cadb46f2651b..000000000000 --- a/erpnext/templates/pages/cart.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -no_cache = 1 - -from erpnext.e_commerce.shopping_cart.cart import get_cart_quotation - - -def get_context(context): - context.body_class = "product-page" - context.update(get_cart_quotation()) diff --git a/erpnext/templates/pages/customer_reviews.html b/erpnext/templates/pages/customer_reviews.html deleted file mode 100644 index 121bec378cc4..000000000000 --- a/erpnext/templates/pages/customer_reviews.html +++ /dev/null @@ -1,67 +0,0 @@ -{% extends "templates/web.html" %} -{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %} - -{% block title %} {{ _("Customer Reviews") }} {% endblock %} - -{% block page_content %} -
- {% if enable_reviews %} - -
-
- {{ _("Customer Reviews") }} -
- -
- - {% if frappe.session.user != "Guest" and user_is_customer %} - - {% endif %} -
-
- - - {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }} - - - -
- {% if reviews %} - {{ user_review(reviews) }} - - {% if not reviews | len >= total_reviews %} - - {% endif %} - - {% else %} -
- {{ _("No Reviews") }} -
- {% endif %} -
- {% else %} - -
-

- {{ _("No Reviews") }} -

-
- {% endif %} -
- -{% endblock %} - -{% block base_scripts %} - - - - - - -{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/customer_reviews.py b/erpnext/templates/pages/customer_reviews.py deleted file mode 100644 index c1f0c93f1aab..000000000000 --- a/erpnext/templates/pages/customer_reviews.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews -from erpnext.e_commerce.doctype.website_item.website_item import check_if_user_is_customer - - -def get_context(context): - context.body_class = "product-page" - context.no_cache = 1 - context.full_page = True - context.reviews = None - - if frappe.form_dict and frappe.form_dict.get("web_item"): - context.web_item = frappe.form_dict.get("web_item") - context.user_is_customer = check_if_user_is_customer() - context.enable_reviews = get_shopping_cart_settings().enable_reviews - - if context.enable_reviews: - reviews_data = get_item_reviews(context.web_item) - context.update(reviews_data) diff --git a/erpnext/templates/pages/home.html b/erpnext/templates/pages/home.html index 27d966ad42e1..08e0432dcf83 100644 --- a/erpnext/templates/pages/home.html +++ b/erpnext/templates/pages/home.html @@ -17,9 +17,6 @@

{{ homepage.description }}

{{ homepage.description }}

- {% elif homepage.hero_section_based_on == 'Slideshow' and slideshow %}
@@ -29,26 +26,6 @@

{{ homepage.description }}

{{ render_homepage_section(homepage.hero_section_doc) }} {% endif %} - {% if homepage.products %} -
-

{{ _('Products') }}

- -
- {% for item in homepage.products %} -
-
- {{ item.item_name }} -
-
{{ item.item_name }}
- {{ _('More details') }} -
-
-
- {% endfor %} -
-
- {% endif %} - {% if blogs %}

{{ _('Publications') }}

diff --git a/erpnext/templates/pages/home.py b/erpnext/templates/pages/home.py index 47fb89dea317..751a5b0b50ed 100644 --- a/erpnext/templates/pages/home.py +++ b/erpnext/templates/pages/home.py @@ -10,11 +10,6 @@ def get_context(context): homepage = frappe.get_cached_doc("Homepage") - for item in homepage.products: - route = frappe.db.get_value("Website Item", {"item_code": item.item_code}, "route") - if route: - item.route = "/" + route - homepage.title = homepage.title or homepage.company context.title = homepage.title context.homepage = homepage @@ -52,5 +47,3 @@ def get_context(context): context.metatags = context.metatags or frappe._dict({}) context.metatags.image = homepage.hero_image or None context.metatags.description = homepage.description or None - - context.explore_link = "/all-products" diff --git a/erpnext/templates/pages/order.js b/erpnext/templates/pages/order.js deleted file mode 100644 index 0574cdedc0d6..000000000000 --- a/erpnext/templates/pages/order.js +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ready(function(){ - - var loyalty_points_input = document.getElementById("loyalty-point-to-redeem"); - var loyalty_points_status = document.getElementById("loyalty-points-status"); - if (loyalty_points_input) { - loyalty_points_input.onblur = apply_loyalty_points; - } - - function apply_loyalty_points() { - var loyalty_points = parseInt(loyalty_points_input.value); - if (loyalty_points) { - frappe.call({ - method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_redeemption_factor", - args: { - "customer": doc_info.customer - }, - callback: function(r) { - if (r) { - var message = "" - let loyalty_amount = flt(r.message*loyalty_points); - if (doc_info.grand_total && doc_info.grand_total < loyalty_amount) { - let redeemable_amount = parseInt(doc_info.grand_total/r.message); - message = "You can only redeem max " + redeemable_amount + " points in this order."; - frappe.msgprint(__(message)); - } else { - message = loyalty_points + " Loyalty Points of amount "+ loyalty_amount + " is applied." - frappe.msgprint(__(message)); - var remaining_amount = flt(doc_info.grand_total) - flt(loyalty_amount); - var payment_button = document.getElementById("pay-for-order"); - payment_button.innerHTML = __("Pay Remaining"); - payment_button.href = "/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn="+doc_info.doctype_name+"&dt="+doc_info.doctype+"&loyalty_points="+loyalty_points+"&submit_doc=1&order_type=Shopping Cart"; - } - loyalty_points_status.innerHTML = message; - } - } - }); - } - } -}) diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py index 13772d31295d..d0968bf88a26 100644 --- a/erpnext/templates/pages/order.py +++ b/erpnext/templates/pages/order.py @@ -4,8 +4,6 @@ import frappe from frappe import _ -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import show_attachments - def get_context(context): context.no_cache = 1 @@ -14,17 +12,12 @@ def get_context(context): if hasattr(context.doc, "set_indicator"): context.doc.set_indicator() - if show_attachments(): - context.attachments = get_attachments(frappe.form_dict.doctype, frappe.form_dict.name) - context.parents = frappe.form_dict.parents context.title = frappe.form_dict.name context.payment_ref = frappe.db.get_value( "Payment Request", {"reference_name": frappe.form_dict.name}, "name" ) - context.enabled_checkout = frappe.get_doc("E Commerce Settings").enable_checkout - default_print_format = frappe.db.get_value( "Property Setter", dict(property="default_print_format", doc_type=frappe.form_dict.doctype), diff --git a/erpnext/templates/pages/product_search.html b/erpnext/templates/pages/product_search.html deleted file mode 100644 index 6a5425bbf8ce..000000000000 --- a/erpnext/templates/pages/product_search.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} {{ _("Product Search") }} {% endblock %} - -{% block header %}

{{ _("Product Search") }}

{% endblock %} - -{% block page_content %} - - - - -
-

{{ _("Search Results") }}

-
- -
-
- -
-
-{% endblock %} diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py deleted file mode 100644 index f40fd479f4f4..000000000000 --- a/erpnext/templates/pages/product_search.py +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -import json - -import frappe -from frappe.utils import cint, cstr -from redis.commands.search.query import Query - -from erpnext.e_commerce.redisearch_utils import ( - WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, - WEBSITE_ITEM_INDEX, - WEBSITE_ITEM_NAME_AUTOCOMPLETE, - is_redisearch_enabled, -) -from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website -from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_html - -no_cache = 1 - - -def get_context(context): - context.show_search = True - - -@frappe.whitelist(allow_guest=True) -def get_product_list(search=None, start=0, limit=12): - data = get_product_data(search, start, limit) - - for item in data: - set_product_info_for_website(item) - - return [get_item_for_list_in_html(r) for r in data] - - -def get_product_data(search=None, start=0, limit=12): - # limit = 12 because we show 12 items in the grid view - # base query - query = """ - SELECT - web_item_name, item_name, item_code, brand, route, - website_image, thumbnail, item_group, - description, web_long_description as website_description, - website_warehouse, ranking - FROM `tabWebsite Item` - WHERE published = 1 - """ - - # search term condition - if search: - query += """ and (item_name like %(search)s - or web_item_name like %(search)s - or brand like %(search)s - or web_long_description like %(search)s)""" - search = "%" + cstr(search) + "%" - - # order by - query += """ ORDER BY ranking desc, modified desc limit %s offset %s""" % ( - cint(limit), - cint(start), - ) - - return frappe.db.sql(query, {"search": search}, as_dict=1) # nosemgrep - - -@frappe.whitelist(allow_guest=True) -def search(query): - product_results = product_search(query) - category_results = get_category_suggestions(query) - - return { - "product_results": product_results.get("results") or [], - "category_results": category_results.get("results") or [], - } - - -@frappe.whitelist(allow_guest=True) -def product_search(query, limit=10, fuzzy_search=True): - search_results = {"from_redisearch": True, "results": []} - - if not is_redisearch_enabled(): - # Redisearch module not enabled - search_results["from_redisearch"] = False - search_results["results"] = get_product_data(query, 0, limit) - return search_results - - if not query: - return search_results - - redis = frappe.cache() - query = clean_up_query(query) - - # TODO: Check perf/correctness with Suggestions & Query vs only Query - # TODO: Use Levenshtein Distance in Query (max=3) - redisearch = redis.ft(WEBSITE_ITEM_INDEX) - suggestions = redisearch.sugget( - WEBSITE_ITEM_NAME_AUTOCOMPLETE, - query, - num=limit, - fuzzy=fuzzy_search and len(query) > 3, - ) - - # Build a query - query_string = query - - for s in suggestions: - query_string += f"|('{clean_up_query(s.string)}')" - - q = Query(query_string) - results = redisearch.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 - - -def clean_up_query(query): - return "".join(c for c in query if c.isalnum() or c.isspace()) - - -def convert_to_dict(redis_search_doc): - return redis_search_doc.__dict__ - - -@frappe.whitelist(allow_guest=True) -def get_category_suggestions(query): - search_results = {"results": []} - - if not is_redisearch_enabled(): - # Redisearch module not enabled, query db - categories = frappe.db.get_all( - "Item Group", - filters={"name": ["like", "%{0}%".format(query)], "show_in_website": 1}, - fields=["name", "route"], - ) - search_results["results"] = categories - return search_results - - if not query: - return search_results - - ac = frappe.cache().ft() - suggestions = ac.sugget(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE, query, num=10, with_payloads=True) - - results = [json.loads(s.payload) for s in suggestions] - - search_results["results"] = results - - return search_results diff --git a/erpnext/templates/pages/wishlist.html b/erpnext/templates/pages/wishlist.html deleted file mode 100644 index 7a81dedb49f7..000000000000 --- a/erpnext/templates/pages/wishlist.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "templates/web.html" %} - -{% block title %} {{ _("Wishlist") }} {% endblock %} - -{% block header %}

{{ _("Wishlist") }}

{% endblock %} - -{% block page_content %} -{% if items %} -
-
-
- {% from "erpnext/templates/includes/macros.html" import wishlist_card %} - {% for item in items %} - {{ wishlist_card(item, settings) }} - {% endfor %} -
-
-
-{% else %} -
-
- Empty Cart -
-
{{ _('Wishlist is empty!') }}

-
-{% endif %} - -{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/wishlist.py b/erpnext/templates/pages/wishlist.py deleted file mode 100644 index 17607e45f919..000000000000 --- a/erpnext/templates/pages/wishlist.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -import frappe - -from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ( - get_shopping_cart_settings, -) -from erpnext.e_commerce.shopping_cart.cart import _set_price_list -from erpnext.utilities.product import get_price - - -def get_context(context): - is_guest = frappe.session.user == "Guest" - - settings = get_shopping_cart_settings() - items = get_wishlist_items() if not is_guest else [] - selling_price_list = _set_price_list(settings) if not is_guest else None - - items = set_stock_price_details(items, settings, selling_price_list) - - context.body_class = "product-page" - context.items = items - context.settings = settings - context.no_cache = 1 - - -def get_stock_availability(item_code, warehouse): - from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses - - if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: - warehouses = get_child_warehouses(warehouse) - else: - warehouses = [warehouse] if warehouse else [] - - stock_qty = 0.0 - for warehouse in warehouses: - stock_qty += frappe.utils.flt( - frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty") - ) - - return bool(stock_qty) - - -def get_wishlist_items(): - if not frappe.db.exists("Wishlist", frappe.session.user): - return [] - - return frappe.db.get_all( - "Wishlist Item", - filters={"parent": frappe.session.user}, - fields=[ - "web_item_name", - "item_code", - "item_name", - "website_item", - "warehouse", - "image", - "item_group", - "route", - ], - ) - - -def set_stock_price_details(items, settings, selling_price_list): - for item in items: - if settings.show_stock_availability: - item.available = get_stock_availability(item.item_code, item.get("warehouse")) - - price_details = get_price( - item.item_code, selling_price_list, settings.default_customer_group, settings.company - ) - - if price_details: - item.formatted_price = price_details.get("formatted_price") - item.formatted_mrp = price_details.get("formatted_mrp") - if item.formatted_mrp: - item.discount = price_details.get("formatted_discount_percent") or price_details.get( - "formatted_discount_rate" - ) - - return items diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index e967f7061bb1..7897c15e94f7 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -2,93 +2,12 @@ # License: GNU General Public License v3. See license.txt import frappe -from frappe.utils import cint, flt, fmt_money, getdate, nowdate +from frappe.utils import cint, flt, fmt_money from erpnext.accounts.doctype.pricing_rule.pricing_rule import get_pricing_rule_for_item -from erpnext.stock.doctype.batch.batch import get_batch_qty -from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses -def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None): - in_stock, stock_qty = 0, "" - template_item_code, is_stock_item = frappe.db.get_value( - "Item", item_code, ["variant_of", "is_stock_item"] - ) - - if not warehouse: - warehouse = frappe.db.get_value("Website Item", {"item_code": item_code}, item_warehouse_field) - - if not warehouse and template_item_code and template_item_code != item_code: - warehouse = frappe.db.get_value( - "Website Item", {"item_code": template_item_code}, item_warehouse_field - ) - - if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1: - warehouses = get_child_warehouses(warehouse) - else: - warehouses = [warehouse] if warehouse else [] - - total_stock = 0.0 - if warehouses: - for warehouse in warehouses: - stock_qty = frappe.db.sql( - """ - select GREATEST(S.actual_qty - S.reserved_qty - S.reserved_qty_for_production - S.reserved_qty_for_sub_contract, 0) / IFNULL(C.conversion_factor, 1) - from tabBin S - inner join `tabItem` I on S.item_code = I.Item_code - left join `tabUOM Conversion Detail` C on I.sales_uom = C.uom and C.parent = I.Item_code - where S.item_code=%s and S.warehouse=%s""", - (item_code, warehouse), - ) - - if stock_qty: - total_stock += adjust_qty_for_expired_items(item_code, stock_qty, warehouse) - - in_stock = total_stock > 0 and 1 or 0 - - return frappe._dict( - {"in_stock": in_stock, "stock_qty": total_stock, "is_stock_item": is_stock_item} - ) - - -def adjust_qty_for_expired_items(item_code, stock_qty, warehouse): - batches = frappe.get_all("Batch", filters=[{"item": item_code}], fields=["expiry_date", "name"]) - expired_batches = get_expired_batches(batches) - stock_qty = [list(item) for item in stock_qty] - - for batch in expired_batches: - if warehouse: - stock_qty[0][0] = max(0, stock_qty[0][0] - get_batch_qty(batch, warehouse)) - else: - stock_qty[0][0] = max(0, stock_qty[0][0] - qty_from_all_warehouses(get_batch_qty(batch))) - - if not stock_qty[0][0]: - break - - return stock_qty[0][0] if stock_qty else 0 - - -def get_expired_batches(batches): - """ - :param batches: A list of dict in the form [{'expiry_date': datetime.date(20XX, 1, 1), 'name': 'batch_id'}, ...] - """ - return [b.name for b in batches if b.expiry_date and b.expiry_date <= getdate(nowdate())] - - -def qty_from_all_warehouses(batch_info): - """ - :param batch_info: A list of dict in the form [{u'warehouse': u'Stores - I', u'qty': 0.8}, ...] - """ - qty = 0 - for batch in batch_info: - qty = qty + batch.qty - - return qty - - -def get_price(item_code, price_list, customer_group, company, qty=1): - from erpnext.e_commerce.shopping_cart.cart import get_party - +def get_price(item_code, price_list, customer_group, company, qty=1, party=None): template_item_code = frappe.db.get_value("Item", item_code, "variant_of") if price_list: @@ -106,7 +25,6 @@ def get_price(item_code, price_list, customer_group, company, qty=1): ) if price: - party = get_party() pricing_rule_dict = frappe._dict( { "item_code": item_code, @@ -187,16 +105,62 @@ def get_price(item_code, price_list, customer_group, company, qty=1): return price_obj -def get_non_stock_item_status(item_code, item_warehouse_field): - # if item is a product bundle, check if its bundle items are in stock - if frappe.db.exists("Product Bundle", item_code): - items = frappe.get_doc("Product Bundle", item_code).get_all_children() - bundle_warehouse = frappe.db.get_value( - "Website Item", {"item_code": item_code}, item_warehouse_field - ) - return all( - get_web_item_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock - for d in items +def get_item_codes_by_attributes(attribute_filters, template_item_code=None): + items = [] + + for attribute, values in attribute_filters.items(): + attribute_values = values + + if not isinstance(attribute_values, list): + attribute_values = [attribute_values] + + if not attribute_values: + continue + + wheres = [] + query_values = [] + for attribute_value in attribute_values: + wheres.append("( attribute = %s and attribute_value = %s )") + query_values += [attribute, attribute_value] + + attribute_query = " or ".join(wheres) + + if template_item_code: + variant_of_query = "AND t2.variant_of = %s" + query_values.append(template_item_code) + else: + variant_of_query = "" + + query = """ + SELECT + t1.parent + FROM + `tabItem Variant Attribute` t1 + WHERE + 1 = 1 + AND ( + {attribute_query} + ) + AND EXISTS ( + SELECT + 1 + FROM + `tabItem` t2 + WHERE + t2.name = t1.parent + {variant_of_query} + ) + GROUP BY + t1.parent + ORDER BY + NULL + """.format( + attribute_query=attribute_query, variant_of_query=variant_of_query ) - else: - return 1 + + item_codes = set([r[0] for r in frappe.db.sql(query, query_values)]) + items.append(item_codes) + + res = list(set.intersection(*items)) + + return res diff --git a/erpnext/www/all-products/__init__.py b/erpnext/www/all-products/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html deleted file mode 100644 index 04fc74c08ff4..000000000000 --- a/erpnext/www/all-products/index.html +++ /dev/null @@ -1,51 +0,0 @@ -{% from "erpnext/templates/includes/macros.html" import attribute_filter_section, field_filter_section, discount_range_filters %} -{% extends "templates/web.html" %} - -{% block title %}{{ _('All Products') }}{% endblock %} -{% block header %} -
{{ _('All Products') }}
-{% endblock header %} - -{% block page_content %} -
- -
- -
- - -
-
-
-
{{ _('Filters') }}
- {{ _('Clear All') }} -
- - {% if field_filters %} - {{ field_filter_section(field_filters) }} - {% endif %} - - - {% if attribute_filters %} - {{ attribute_filter_section(attribute_filters) }} - {% endif %} -
- -
-
- - - -{% endblock %} diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js deleted file mode 100644 index 98a84415252c..000000000000 --- a/erpnext/www/all-products/index.js +++ /dev/null @@ -1,27 +0,0 @@ -$(() => { - class ProductListing { - constructor() { - let me = this; - let is_item_group_page = $(".item-group-content").data("item-group"); - this.item_group = is_item_group_page || null; - - let view_type = localStorage.getItem("product_view") || "List View"; - - // Render Product Views, Filters & Search - new erpnext.ProductView({ - view_type: view_type, - products_section: $('#product-listing'), - item_group: me.item_group - }); - - this.bind_card_actions(); - } - - bind_card_actions() { - erpnext.e_commerce.shopping_cart.bind_add_to_cart_action(); - erpnext.e_commerce.wishlist.bind_wishlist_action(); - } - } - - new ProductListing(); -}); diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py deleted file mode 100644 index fbf0dce0590b..000000000000 --- a/erpnext/www/all-products/index.py +++ /dev/null @@ -1,22 +0,0 @@ -import frappe -from frappe.utils import cint - -from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder - -sitemap = 1 - - -def get_context(context): - # Add homepage as parent - context.body_class = "product-page" - context.parents = [{"name": frappe._("Home"), "route": "/"}] - - filter_engine = ProductFiltersBuilder() - context.field_filters = filter_engine.get_field_filters() - context.attribute_filters = filter_engine.get_attribute_filters() - - context.page_length = ( - cint(frappe.db.get_single_value("E Commerce Settings", "products_per_page")) or 20 - ) - - context.no_cache = 1 diff --git a/erpnext/www/all-products/not_found.html b/erpnext/www/all-products/not_found.html deleted file mode 100644 index 91989a9ef48c..000000000000 --- a/erpnext/www/all-products/not_found.html +++ /dev/null @@ -1 +0,0 @@ -
{{ _('No products found') }}
diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/erpnext/www/shop-by-category/category_card_section.html b/erpnext/www/shop-by-category/category_card_section.html deleted file mode 100644 index 56cb63a5b6fe..000000000000 --- a/erpnext/www/shop-by-category/category_card_section.html +++ /dev/null @@ -1,30 +0,0 @@ -{%- macro card(title, image, type, url=None, text_primary=False) -%} - -
- {% if image %} - {{ title }} - {% else %} -
- - {{ frappe.utils.get_abbr(title) }} - -
- {% endif %} -
- {{ title or '' }} -
- -
-{%- endmacro -%} - -
-
- {%- for row in data -%} - {%- set title = row.name -%} - {%- set image = row.get("image") -%} - {%- if title -%} - {{ card(title, image, type, row.get("route")) }} - {%- endif -%} - {%- endfor -%} -
-
\ No newline at end of file diff --git a/erpnext/www/shop-by-category/index.html b/erpnext/www/shop-by-category/index.html deleted file mode 100644 index 04d2d578cbb9..000000000000 --- a/erpnext/www/shop-by-category/index.html +++ /dev/null @@ -1,48 +0,0 @@ -{% extends "templates/web.html" %} -{% block title %}{{ _('Shop by Category') }}{% endblock %} - -{% block head_include %} - -{% endblock %} - -{% block script %} - -{% endblock %} - -{% block page_content %} -
-
- {% if slideshow %} - - {{ web_block( - "Hero Slider", - values=slideshow, - add_container=0, - add_top_padding=0, - add_bottom_padding=0, - ) }} - {% endif %} -
-
- {% if tabs %} - - {{ web_block( - "Section with Tabs", - values=tabs, - add_container=0, - add_top_padding=0, - add_bottom_padding=0 - ) }} - {% endif %} -
-
-{% endblock %} \ No newline at end of file diff --git a/erpnext/www/shop-by-category/index.js b/erpnext/www/shop-by-category/index.js deleted file mode 100644 index 1b3116f5baeb..000000000000 --- a/erpnext/www/shop-by-category/index.js +++ /dev/null @@ -1,12 +0,0 @@ -$(() => { - $('.category-card').on('click', (e) => { - let category_type = e.currentTarget.dataset.type; - let category_name = e.currentTarget.dataset.name; - - if (category_type != "item_group") { - let filters = {}; - filters[category_type] = [category_name]; - window.location.href = "/all-products?field_filters=" + JSON.stringify(filters); - } - }); -}); \ No newline at end of file diff --git a/erpnext/www/shop-by-category/index.py b/erpnext/www/shop-by-category/index.py deleted file mode 100644 index 913c1836acdf..000000000000 --- a/erpnext/www/shop-by-category/index.py +++ /dev/null @@ -1,91 +0,0 @@ -import frappe -from frappe import _ - -sitemap = 1 - - -def get_context(context): - context.body_class = "product-page" - - settings = frappe.get_cached_doc("E Commerce Settings") - context.categories_enabled = settings.enable_field_filters - - if context.categories_enabled: - categories = [row.fieldname for row in settings.filter_fields] - context.tabs = get_tabs(categories) - - if settings.slideshow: - context.slideshow = get_slideshow(settings.slideshow) - - context.no_cache = 1 - - -def get_slideshow(slideshow): - values = {"show_indicators": 1, "show_controls": 1, "rounded": 1, "slider_name": "Categories"} - slideshow = frappe.get_cached_doc("Website Slideshow", slideshow) - slides = slideshow.get({"doctype": "Website Slideshow Item"}) - for index, slide in enumerate(slides, start=1): - values[f"slide_{index}_image"] = slide.image - values[f"slide_{index}_title"] = slide.heading - values[f"slide_{index}_subtitle"] = slide.description - values[f"slide_{index}_theme"] = slide.get("theme") or "Light" - values[f"slide_{index}_content_align"] = slide.get("content_align") or "Centre" - values[f"slide_{index}_primary_action"] = slide.url - - return values - - -def get_tabs(categories): - tab_values = { - "title": _("Shop by Category"), - } - - categorical_data = get_category_records(categories) - for index, tab in enumerate(categorical_data, start=1): - tab_values[f"tab_{index + 1}_title"] = frappe.unscrub(tab) - # pre-render cards for each tab - tab_values[f"tab_{index + 1}_content"] = frappe.render_template( - "erpnext/www/shop-by-category/category_card_section.html", - {"data": categorical_data[tab], "type": tab}, - ) - return tab_values - - -def get_category_records(categories: list): - categorical_data = {} - website_item_meta = frappe.get_meta("Website Item", cached=True) - - for c in categories: - if c == "item_group": - categorical_data["item_group"] = frappe.db.get_all( - "Item Group", - filters={"parent_item_group": "All Item Groups", "show_in_website": 1}, - fields=["name", "parent_item_group", "is_group", "image", "route"], - ) - - continue - - field_type = website_item_meta.get_field(c).fieldtype - - if field_type == "Table MultiSelect": - child_doc = website_item_meta.get_field(c).options - for field in frappe.get_meta(child_doc, cached=True).fields: - if field.fieldtype == "Link" and field.reqd: - doctype = field.options - else: - doctype = website_item_meta.get_field(c).options - - fields = ["name"] - - try: - meta = frappe.get_meta(doctype, cached=True) - if meta.get_field("image"): - fields += ["image"] - - data = frappe.db.get_all(doctype, fields=fields) - categorical_data[c] = data - except BaseException: - frappe.throw(_("DocType {} not found").format(doctype)) - continue - - return categorical_data