Merge pull request #37552 from frappe/addr_fetch
fix: Ignore addr permission in internal code
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index ce15bcf..5f0b434 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 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_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 75223c2..f6e5c56 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
@@ -22,7 +22,7 @@
@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 @@
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 87c5e6d..ac0dd51 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 @@
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 @@
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 e68ee90..c8785a5 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 @@
@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 @@
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 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 e69de29..0000000
--- a/erpnext/e_commerce/__init__.py
+++ /dev/null
diff --git a/erpnext/e_commerce/api.py b/erpnext/e_commerce/api.py
deleted file mode 100644
index bfada0f..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/doctype/__init__.py
+++ /dev/null
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 e69de29..0000000
--- a/erpnext/e_commerce/doctype/e_commerce_settings/__init__.py
+++ /dev/null
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 c37fa2f..0000000
--- 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(
- `<div>${__("Follow these steps to create a landing page for your store")}:
- <a href="https://docs.erpnext.com/docs/user/manual/en/website/store-landing-page"
- style="color: var(--gray-600)">
- docs/store-landing-page
- </a>
- </div>`
- );
- }
-
- 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 e6f08f7..0000000
--- 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 <b>Shop by Category</b> 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": "<p class=\"alert alert-warning\">Redisearch is not loaded. If you want to use the advanced product search feature, refer <a class=\"alert-link\" href=\"https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/articles/installing-redisearch\" target=\"_blank\">here</a>.</p>"
- },
- {
- "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. <b>E.g.: /login</b>",
- "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 c27d29a..0000000
--- 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 662db4d..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/doctype/item_review/__init__.py
+++ /dev/null
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 a57c370..0000000
--- 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 57f719f..0000000
--- 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 3e540e3..0000000
--- 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 8a4befc..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/doctype/recommended_items/__init__.py
+++ /dev/null
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 1821532..0000000
--- 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 16b6e52..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/doctype/website_item/__init__.py
+++ /dev/null
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 db12309..0000000
--- a/erpnext/e_commerce/doctype/website_item/templates/website_item.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block page_content %}
-<h1>{{ title }}</h1>
-{% endblock %}
-
-<!-- this is a sample default web page template -->
\ 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 d7014b4..0000000
--- a/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<div>
- <a href="{{ doc.route }}">{{ doc.title or doc.name }}</a>
-</div>
-<!-- this is a sample default list template -->
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 2ba84c0..0000000
--- 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 b6595cc..0000000
--- 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 6f551a0..0000000
--- 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 81b8eca..0000000
--- 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 b9dd921..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/doctype/website_item_tabbed_section/__init__.py
+++ /dev/null
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 6601dd8..0000000
--- 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 91148b8..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/doctype/website_offer/__init__.py
+++ /dev/null
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 627d548..0000000
--- 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 8c92f75..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/doctype/wishlist/__init__.py
+++ /dev/null
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 9d27126..0000000
--- 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 d96e552..0000000
--- 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 922924e..0000000
--- 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 eb74027..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/doctype/wishlist_item/__init__.py
+++ /dev/null
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 c0414a7..0000000
--- 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 75ebccb..0000000
--- 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 ef8e86d..0000000
--- 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 e5e5e97..0000000
--- 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 975f876..0000000
--- 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 45bc20e..0000000
--- 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 c3b6ed5..0000000
--- 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 20a6c30..0000000
--- 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 += `<div class="col-sm-4 item-card"><div class="card text-left">`;
- html += me.get_image_html(item, title);
- html += me.get_card_body_html(item, title, me.settings);
- html += `</div></div>`;
- });
-
- let $product_wrapper = this.products_section;
- $product_wrapper.append(html);
- }
-
- get_image_html(item, title) {
- let image = item.website_image;
-
- if (image) {
- return `
- <div class="card-img-container">
- <a href="/${ item.route || '#' }" style="text-decoration: none;">
- <img class="card-img" src="${ image }" alt="${ title }">
- </a>
- </div>
- `;
- } else {
- return `
- <div class="card-img-container">
- <a href="/${ item.route || '#' }" style="text-decoration: none;">
- <div class="card-img-top no-image">
- ${ frappe.get_abbr(title) }
- </div>
- </a>
- </div>
- `;
- }
- }
-
- get_card_body_html(item, title, settings) {
- let body_html = `
- <div class="card-body text-left card-body-flex" style="width:100%">
- <div style="margin-top: 1rem; display: flex;">
- `;
- 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 += `</div>`;
- body_html += `<div class="product-category">${ item.item_group || '' }</div>`;
-
- 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 += `</div>`; // close div on line 49
-
- return body_html;
- }
-
- get_title(item, title) {
- let title_html = `
- <a href="/${ item.route || '#' }">
- <div class="product-title">
- ${ title || '' }
- </div>
- </a>
- `;
- return title_html;
- }
-
- get_wishlist_icon(item) {
- let icon_class = item.wished ? "wished" : "not-wished";
- return `
- <div class="like-action ${ item.wished ? "like-action-wished" : ''}"
- data-item-code="${ item.item_code }">
- <svg class="icon sm">
- <use class="${ icon_class } wish-icon" href="#icon-heart"></use>
- </svg>
- </div>
- `;
- }
-
- get_cart_indicator(item) {
- return `
- <div class="cart-indicator ${item.in_cart ? '' : 'hidden'}" data-item-code="${ item.item_code }">
- 1
- </div>
- `;
- }
-
- get_price_html(item) {
- let price_html = `
- <div class="product-price">
- ${ item.formatted_price || '' }
- `;
-
- if (item.formatted_mrp) {
- price_html += `
- <small class="striked-price">
- <s>${ item.formatted_mrp ? item.formatted_mrp.replace(/ +/g, "") : "" }</s>
- </small>
- <small class="ml-1 product-info-green">
- ${ item.discount } OFF
- </small>
- `;
- }
- price_html += `</div>`;
- return price_html;
- }
-
- get_stock_availability(item, settings) {
- if (settings.show_stock_availability && !item.has_variants) {
- if (item.on_backorder) {
- return `
- <span class="out-of-stock mb-2 mt-1" style="color: var(--primary-color)">
- ${ __("Available on backorder") }
- </span>
- `;
- } else if (!item.in_stock) {
- return `
- <span class="out-of-stock mb-2 mt-1">
- ${ __("Out of stock") }
- </span>
- `;
- }
- }
-
- return ``;
- }
-
- get_primary_button(item, settings) {
- if (item.has_variants) {
- return `
- <a href="/${ item.route || '#' }">
- <div class="btn btn-sm btn-explore-variants w-100 mt-4">
- ${ __('Explore') }
- </div>
- </a>
- `;
- } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) {
- return `
- <div id="${ item.name }" class="btn
- btn-sm btn-primary btn-add-to-cart-list
- w-100 mt-2 ${ item.in_cart ? 'hidden' : '' }"
- data-item-code="${ item.item_code }">
- <span class="mr-2">
- <svg class="icon icon-md">
- <use href="#icon-assets"></use>
- </svg>
- </span>
- ${ settings.enable_checkout ? __('Add to Cart') : __('Add to Quote') }
- </div>
-
- <a href="/cart">
- <div id="${ item.name }" class="btn
- btn-sm btn-primary btn-add-to-cart-list
- w-100 mt-4 go-to-cart-grid
- ${ item.in_cart ? '' : 'hidden' }"
- data-item-code="${ item.item_code }">
- ${ settings.enable_checkout ? __('Go to Cart') : __('Go to Quote') }
- </div>
- </a>
- `;
- } 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 c8fd767..0000000
--- 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 = `<br><br>`;
-
- 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 += `<div class='row list-row w-100 mb-4'>`;
- html += me.get_image_html(item, title, me.settings);
- html += me.get_row_body_html(item, title, me.settings);
- html += `</div>`;
- });
-
- 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 += `
- <div class="col-2 border text-center rounded list-image">
- <a class="product-link product-list-link" href="/${ item.route || '#' }">
- <img itemprop="image" class="website-image h-100 w-100" alt="${ title }"
- src="${ image }">
- </a>
- ${ wishlist_enabled ? this.get_wishlist_icon(item): '' }
- </div>
- `;
- } else {
- image_html += `
- <div class="col-2 border text-center rounded list-image">
- <a class="product-link product-list-link" href="/${ item.route || '#' }"
- style="text-decoration: none">
- <div class="card-img-top no-image-list">
- ${ frappe.get_abbr(title) }
- </div>
- </a>
- ${ wishlist_enabled ? this.get_wishlist_icon(item): '' }
- </div>
- `;
- }
-
- return image_html;
- }
-
- get_row_body_html(item, title, settings) {
- let body_html = `<div class='col-10 text-left'>`;
- body_html += this.get_title_html(item, title, settings);
- body_html += this.get_item_details(item, settings);
- body_html += `</div>`;
- return body_html;
- }
-
- get_title_html(item, title, settings) {
- let title_html = `<div style="display: flex; margin-left: -15px;">`;
- title_html += `
- <div class="col-8" style="margin-right: -15px;">
- <a href="/${ item.route || '#' }">
- <div class="product-title">
- ${ title }
- </div>
- </a>
- </div>
- `;
-
- if (settings.enabled) {
- title_html += `<div class="col-4 cart-action-container ${item.in_cart ? 'd-flex' : ''}">`;
- title_html += this.get_primary_button(item, settings);
- title_html += `</div>`;
- }
- title_html += `</div>`;
-
- return title_html;
- }
-
- get_item_details(item, settings) {
- let details = `
- <p class="product-code">
- ${ item.item_group } | Item Code : ${ item.item_code }
- </p>
- <div class="mt-2" style="color: var(--gray-600) !important; font-size: 13px;">
- ${ item.short_description || '' }
- </div>
- <div class="product-price">
- ${ item.formatted_price || '' }
- `;
-
- if (item.formatted_mrp) {
- details += `
- <small class="striked-price">
- <s>${ item.formatted_mrp ? item.formatted_mrp.replace(/ +/g, "") : "" }</s>
- </small>
- <small class="ml-1 product-info-green">
- ${ item.discount } OFF
- </small>
- `;
- }
-
- details += this.get_stock_availability(item, settings);
- details += `</div>`;
-
- return details;
- }
-
- get_stock_availability(item, settings) {
- if (settings.show_stock_availability && !item.has_variants) {
- if (item.on_backorder) {
- return `
- <br>
- <span class="out-of-stock mt-2" style="color: var(--primary-color)">
- ${ __("Available on backorder") }
- </span>
- `;
- } else if (!item.in_stock) {
- return `
- <br>
- <span class="out-of-stock mt-2">${ __("Out of stock") }</span>
- `;
- }
- }
- return ``;
- }
-
- get_wishlist_icon(item) {
- let icon_class = item.wished ? "wished" : "not-wished";
-
- return `
- <div class="like-action-list ${ item.wished ? "like-action-wished" : ''}"
- data-item-code="${ item.item_code }">
- <svg class="icon sm">
- <use class="${ icon_class } wish-icon" href="#icon-heart"></use>
- </svg>
- </div>
- `;
- }
-
- get_primary_button(item, settings) {
- if (item.has_variants) {
- return `
- <a href="/${ item.route || '#' }">
- <div class="btn btn-sm btn-explore-variants btn mb-0 mt-0">
- ${ __('Explore') }
- </div>
- </a>
- `;
- } else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) {
- return `
- <div id="${ item.name }" class="btn
- btn-sm btn-primary btn-add-to-cart-list mb-0
- ${ item.in_cart ? 'hidden' : '' }"
- data-item-code="${ item.item_code }"
- style="margin-top: 0px !important; max-height: 30px; float: right;
- padding: 0.25rem 1rem; min-width: 135px;">
- <span class="mr-2">
- <svg class="icon icon-md">
- <use href="#icon-assets"></use>
- </svg>
- </span>
- ${ settings.enable_checkout ? __('Add to Cart') : __('Add to Quote') }
- </div>
-
- <div class="cart-indicator list-indicator ${item.in_cart ? '' : 'hidden'}">
- 1
- </div>
-
- <a href="/cart">
- <div id="${ item.name }" class="btn
- btn-sm btn-primary btn-add-to-cart-list
- ml-4 go-to-cart mb-0 mt-0
- ${ item.in_cart ? '' : 'hidden' }"
- data-item-code="${ item.item_code }"
- style="padding: 0.25rem 1rem; min-width: 135px;">
- ${ settings.enable_checkout ? __('Go to Cart') : __('Go to Quote') }
- </div>
- </a>
- `;
- } 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 1688cc1..0000000
--- 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(`
- <div class="overflow-hidden shadow dropdown-menu w-100 hidden"
- id="search-results-container"
- aria-labelledby="dropdownMenuSearch"
- style="display: flex; flex-direction: column;">
- </div>
- `).find("#search-results-container");
-
- this.setupCategoryContainer();
- this.setupProductsContainer();
- this.setupRecentsContainer();
- }
-
- setupProductsContainer() {
- this.products_container = this.search_dropdown.append(`
- <div id="product-results mt-2">
- <div id="product-scroll" style="overflow: scroll; max-height: 300px">
- </div>
- </div>
- `).find("#product-scroll");
- }
-
- setupCategoryContainer() {
- this.category_container = this.search_dropdown.append(`
- <div class="category-container mt-2 mb-1">
- <div class="category-chips">
- </div>
- </div>
- `).find(".category-chips");
- }
-
- setupRecentsContainer() {
- let $recents_section = this.search_dropdown.append(`
- <div class="mb-2 mt-2 recent-searches">
- <div>
- <b>${ __("Recent") }</b>
- </div>
- </div>
- `).find(".recent-searches");
-
- this.recents_container = $recents_section.append(`
- <div id="recents" style="padding: .25rem 0 1rem 0;">
- </div>
- `).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(`<span class=""text-muted">No searches yet.</span>`);
- return;
- }
-
- let html = "";
- recents.forEach((key) => {
- html += `
- <div class="recent-search mr-1" style="font-size: 13px">
- <span class="mr-2">
- <svg width="20" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="var(--gray-500)"" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M8.00027 5.20947V8.00017L10 10" stroke="var(--gray-500)" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- </span>
- ${ key }
- </div>
- `;
- });
-
- 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 += `
- <div class="dropdown-item" style="display: flex;">
- <img class="item-thumb col-2" src=${encodeURI(thumbnail)} />
- <div class="col-9" style="white-space: normal;">
- <a href="/${res.route}">${res.web_item_name}</a><br>
- <span class="brand-line">${res.brand ? "by " + res.brand : ""}</span>
- </div>
- </div>
- `;
- });
-
- this.products_container.html(html);
- }
-
- populateCategoriesList(category_results) {
- if (!category_results || category_results.length === 0) {
- let empty_html = `
- <div class="category-container mt-2">
- <div class="category-chips">
- </div>
- </div>
- `;
- this.category_container.html(empty_html);
- return;
- }
-
- let html = `
- <div class="mb-2">
- <b>${ __("Categories") }</b>
- </div>
- `;
-
- category_results.forEach((category) => {
- html += `
- <a href="/${category.route}" class="btn btn-sm category-chip mr-2 mb-2"
- style="font-size: 13px" role="button">
- ${ category.name }
- </button>
- `;
- });
-
- 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 fb63b21..0000000
--- 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(`
- <div class="toolbar d-flex">
- </div>
- `);
- 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(`
- <br>
- <div id="products-${view}-area" class="row products-list ${ top_margin } ${ left_margin }"></div>
- `);
- }
-
- 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 = `
- <div class="row product-paging-area mt-5">
- <div class="col-3">
- </div>
- <div class="col-9 text-right">
- `;
- 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 += `
- <button class="btn btn-default btn-prev" data-start="${ start - page_length }"
- style="float: left" ${prev_disable}>
- ${ __("Prev") }
- </button>`;
-
- paging_html += `
- <button class="btn btn-default btn-next" data-start="${ start + page_length }"
- ${next_disable}>
- ${ __("Next") }
- </button>
- `;
-
- paging_html += `</div></div>`;
-
- $(".page_content").append(paging_html);
- this.bind_paging_action();
- }
- }
-
- prepare_search() {
- $(".toolbar").append(`
- <div class="input-group col-8 p-0">
- <div class="dropdown w-100" id="dropdownMenuSearch">
- <input type="search" name="query" id="search-box" class="form-control font-md"
- placeholder="Search for Products"
- aria-label="Product" aria-describedby="button-addon2">
- <div class="search-icon">
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor" stroke-width="2" stroke-linecap="round"
- stroke-linejoin="round"
- class="feather feather-search">
- <circle cx="11" cy="11" r="8"></circle>
- <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
- </svg>
- </div>
- <!-- Results dropdown rendered in product_search.js -->
- </div>
- </div>
- `);
- }
-
- render_view_toggler() {
- $(".toolbar").append(`<div class="toggle-container col-4 p-0"></div>`);
-
- ["btn-list-view", "btn-grid-view"].forEach(view => {
- let icon = view === "btn-list-view" ? "list" : "image-view";
- $(".toggle-container").append(`
- <div class="form-group mb-0" id="toggle-view">
- <button id="${ icon }" class="btn ${ view } mr-2">
- <span>
- <svg class="icon icon-md">
- <use href="#icon-${ icon }"></use>
- </svg>
- </span>
- </button>
- </div>
- `);
- });
- }
-
- 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(`
- <div id="discount-filters" class="mb-4 filter-block pb-5">
- <div class="filter-label mb-3">${ __("Discounts") }</div>
- </div>
- `);
-
- let html = `<div class="filter-options">`;
- filter_data.forEach(filter => {
- html += `
- <div class="checkbox">
- <label data-value="${ filter[0] }">
- <input type="radio"
- class="product-filter discount-filter"
- name="discount" id="${ filter[0] }"
- data-filter-name="discount"
- data-filter-value="${ filter[0] }"
- style="width: 14px !important"
- >
- <span class="label-area" for="${ filter[0] }">
- ${ filter[1] }
- </span>
- </label>
- </div>
- `;
- });
- html += `</div>`;
-
- $("#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 = `
- <div class="mt-4 w-100 alert alert-error font-md">
- Something went wrong. Please refresh or contact us.
- </div>
- `;
- let no_results_section = `
- <div class="cart-empty frappe-card mt-4">
- <div class="cart-empty-state">
- <img src="/assets/erpnext/images/ui-states/cart-empty-state.png" alt="Empty Cart">
- </div>
- <div class="cart-empty-message mt-4">${ __('No products found') }</p>
- </div>
- `;
-
- this.products_section.append(error ? error_section : no_results_section);
- }
-
- render_item_sub_categories(categories) {
- if (categories && categories.length) {
- let sub_group_html = `
- <div class="sub-category-container scroll-categories">
- `;
-
- categories.forEach(category => {
- sub_group_html += `
- <a href="/${ category.route || '#' }" style="text-decoration: none;">
- <div class="category-pill">
- ${ category.name }
- </div>
- </a>
- `;
- });
- sub_group_html += `</div>`;
-
- $("#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 87ca9bd..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/shopping_cart/__init__.py
+++ /dev/null
diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py
deleted file mode 100644
index 7c7e169..0000000
--- 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="""
- <div>
- <h5>{subject}</h5>
- <p>{message}</p>
- </div>
- """.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 0248ca7..0000000
--- 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 8210f97..0000000
--- 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 3d48c28..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/variant_selector/__init__.py
+++ /dev/null
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 f8439d5..0000000
--- 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 8eb497c..0000000
--- 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 88356f5..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/web_template/__init__.py
+++ /dev/null
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 e69de29..0000000
--- a/erpnext/e_commerce/web_template/hero_slider/__init__.py
+++ /dev/null
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 fe4fee3..0000000
--- 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',
-}) -%}
-<div class="carousel-item {{ 'active' if index=='1' else ''}}" style="height: 450px;">
- <img class="d-block h-100 w-100" style="object-fit: cover;" src="{{ image }}" alt="{{ title }}">
- {%- if title or subtitle -%}
- <div class="carousel-body container d-flex {{ align_class }}">
- <div class="carousel-content align-self-center">
- {%- if title -%}<h1 class="{{ heading_class }}">{{ title }}</h1>{%- endif -%}
- {%- if subtitle -%}<p class="{{ heading_class }} mt-2">{{ subtitle }}</p>{%- endif -%}
- {%- if action -%}
- <a href="{{ action }}" class="btn btn-primary mt-3">
- {{ label }}
- </a>
- {%- endif -%}
- </div>
- </div>
- {%- endif -%}
-</div>
-{%- endmacro -%}
-
-{%- set hero_slider_id = 'id-' + frappe.utils.generate_hash('HeroSlider', 12) -%}
-
-<div id="{{ hero_slider_id }}" class="section-carousel carousel slide" data-ride="carousel">
- {%- if show_indicators -%}
- <ol class="carousel-indicators">
- {%- for index in ['1', '2', '3', '4', '5'] -%}
- {%- if values['slide_' + index + '_image'] -%}
- <li data-target="#{{ hero_slider_id }}" data-slide-to="{{ frappe.utils.cint(index) - 1 }}" class="{{ 'active' if index=='1' else ''}}"></li>
- {%- endif -%}
- {%- endfor -%}
- </ol>
- {%- endif -%}
- <div class="carousel-inner {{ resolve_class({'rounded-carousel': rounded }) }}">
- {%- for index in ['1', '2', '3', '4', '5'] -%}
- {%- set image = values['slide_' + index + '_image'] -%}
- {%- set title = values['slide_' + index + '_title'] -%}
- {%- set subtitle = values['slide_' + index + '_subtitle'] -%}
- {%- set primary_action = values['slide_' + index + '_primary_action'] -%}
- {%- set primary_action_label = values['slide_' + index + '_primary_action_label'] -%}
- {%- set align = values['slide_' + index + '_content_align'] -%}
- {%- set theme = values['slide_' + index + '_theme'] -%}
-
- {%- if image -%}
- {{ slide(image, title, subtitle, primary_action, primary_action_label, index, align, theme) }}
- {%- endif -%}
-
- {%- endfor -%}
- </div>
- {%- if show_controls -%}
- <a class="carousel-control-prev" href="#{{ hero_slider_id }}" role="button" data-slide="prev">
- <div class="carousel-control">
- <svg class="mr-1" width="20" height="20" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M11.625 3.75L6.375 9L11.625 14.25" stroke="#4C5A67" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- </div>
- <span class="sr-only">Previous</span>
- </a>
- <a class="carousel-control-next" href="#{{ hero_slider_id }}" role="button" data-slide="next">
- <div class="carousel-control">
- <svg class="ml-1" width="20" height="20" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M6.375 14.25L11.625 9L6.375 3.75" stroke="#4C5A67" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- </div>
- <span class="sr-only">Next</span>
- </a>
- {%- endif -%}
-</div>
-
-<script>
- frappe.ready(function () {
- $('.carousel').carousel({
- interval: false,
- pause: "hover",
- wrap: true
- })
- });
-</script>
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 39b2b3e..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/web_template/item_card_group/__init__.py
+++ /dev/null
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 07952f0..0000000
--- 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 %}
-
-<div class="section-with-cards item-card-group-section">
- <div class="item-group-header d-flex justify-content-between">
- <div class="title-section">
- {%- if title -%}
- <h2 class="section-title">{{ title }}</h2>
- {%- endif -%}
- {%- if subtitle -%}
- <p class="section-description">{{ subtitle }}</p>
- {%- endif -%}
- </div>
- <div class="primary-action-section">
- {%- if primary_action -%}
- <a href="{{ action }}" class="btn btn-primary pull-right">
- {{ primary_action_label }}
- </a>
- {%- endif -%}
- </div>
- </div>
-
- <div class="row">
- {%- 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 -%}
- </div>
-</div>
-
-<style>
-</style>
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 ad9e2a7..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/web_template/product_card/__init__.py
+++ /dev/null
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 e69de29..0000000
--- a/erpnext/e_commerce/web_template/product_card/product_card.html
+++ /dev/null
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 2eb7374..0000000
--- 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 e69de29..0000000
--- a/erpnext/e_commerce/web_template/product_category_cards/__init__.py
+++ /dev/null
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 6d75a8b..0000000
--- 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',
-}) -%}
-<div class="card h-100">
- {% if image %}
- <img class="card-img-top" src="{{ image }}" alt="{{ title }}" style="max-height: 200px;">
- {% else %}
- <div class="placeholder-div" style="max-height: 200px;">
- <span class="placeholder">
- {{ frappe.utils.get_abbr(title or '') }}
- </span>
- </div>
- {% endif %}
-
- <div class="card-body text-center text-muted small">
- {{ title or '' }}
- </div>
- <a href="{{ url or '#' }}" class="stretched-link"></a>
-</div>
-{%- endmacro -%}
-
-<div class="section-with-cards product-category-section">
- {%- if title -%}
- <h2 class="section-title">{{ title }}</h2>
- {%- endif -%}
- {%- if subtitle -%}
- <p class="section-description">{{ subtitle }}</p>
- {%- endif -%}
- <!-- {%- set card_size = card_size or 'Small' -%} -->
- <div class="{{ resolve_class({'mt-6': title}) }}">
- <div class="card-grid">
- {%- 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 -%}
- </div>
- </div>
-</div>
-
-<style>
-</style>
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 0202165..0000000
--- 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 2155699..7446f2c 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 dcb4212..c53cdf4 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -17,5 +17,4 @@
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 8f2d076..aebad55 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -223,9 +223,6 @@
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.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.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.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.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
@@ -346,4 +340,4 @@
execute:frappe.delete_doc("Page", "welcome-to-erpnext")
erpnext.patches.v15_0.delete_payment_gateway_doctypes
# 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 1bac0fd..0000000
--- 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 4ad572f..0000000
--- 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 b010f0e..0000000
--- 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 9197d86..0000000
--- 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 50bfd35..0000000
--- 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 ecf512b..0000000
--- 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 35710a9..0000000
--- 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 = """<div class="ql-editor read-mode"><p>You are seeing this message because Shopping Cart is enabled on your site. </p><p><br></p><p>Shopping Cart Settings and Products settings are now merged into "E Commerce Settings". </p><p><br></p><p>You can learn about new and improved E-Commerce features in the official documentation.</p><ol><li data-list="bullet"><span class="ql-ui" contenteditable="false"></span><a href="https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce" rel="noopener noreferrer">https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce</a></li></ol><p><br></p></div>"""
- 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 a53adf1..9a2a39f 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 @@
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 0000000..af03987
--- /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 59f808a..6797904 100644
--- a/erpnext/portal/doctype/homepage/homepage.js
+++ b/erpnext/portal/doctype/homepage/homepage.js
@@ -19,12 +19,3 @@
});
},
});
-
-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 73f816d..2b891f7 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 0d2e360..c0a0c07 100644
--- a/erpnext/portal/doctype/homepage/homepage.py
+++ b/erpnext/portal/doctype/homepage/homepage.py
@@ -12,26 +12,3 @@
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 e69de29..0000000
--- a/erpnext/portal/doctype/homepage_featured_product/__init__.py
+++ /dev/null
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 63789e3..0000000
--- 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 c21461d..0000000
--- 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 c8b03e6..903d4a6 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 @@
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/projects/doctype/task_depends_on/task_depends_on.json b/erpnext/projects/doctype/task_depends_on/task_depends_on.json
index 5102986..3300b7e 100644
--- a/erpnext/projects/doctype/task_depends_on/task_depends_on.json
+++ b/erpnext/projects/doctype/task_depends_on/task_depends_on.json
@@ -24,6 +24,7 @@
},
{
"fetch_from": "task.subject",
+ "fetch_if_empty": 1,
"fieldname": "subject",
"fieldtype": "Text",
"in_list_view": 1,
@@ -31,7 +32,6 @@
"read_only": 1
},
{
- "fetch_from": "task.project",
"fieldname": "project",
"fieldtype": "Text",
"label": "Project",
@@ -40,7 +40,7 @@
],
"istable": 1,
"links": [],
- "modified": "2023-10-09 11:34:14.335853",
+ "modified": "2023-10-17 12:45:21.536165",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task Depends On",
diff --git a/erpnext/public/js/customer_reviews.js b/erpnext/public/js/customer_reviews.js
deleted file mode 100644
index e13ded6..0000000
--- 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(`
- <div class="mb-3 review">
- <div class="d-flex">
- <p class="mr-4 user-review-title">
- <span>${__(review.review_title)}</span>
- </p>
- <div class="rating">
- ${me.get_review_stars(review.rating)}
- </div>
- </div>
-
- <div class="product-description mb-4">
- <p>
- ${__(review.comment)}
- </p>
- </div>
- <div class="review-signature mb-2">
- <span class="reviewer">${__(review.customer)}</span>
- <span class="indicator grey" style="--text-on-gray: var(--gray-300);"></span>
- <span class="reviewer">${__(review.published_on)}</span>
- </div>
- </div>
- `);
- });
- }
-
- get_review_stars(rating) {
- let stars = ``;
- for (let i = 1; i < 6; i++) {
- let fill_class = i <= rating ? 'star-click' : '';
- stars += `
- <svg class="icon icon-sm ${fill_class}">
- <use href="#icon-star"></use>
- </svg>
- `;
- }
- 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 cbe899d..45c6a64 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 d14740c..0000000
--- 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('<i class="fa fa-fixed-width fa fa-user"></i> ' + 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 = `
- <div class="text-center w-100 intermediate-empty-cart mt-4 mb-4 text-muted">
- ${ __("Cart is Empty") }
- </div>
- `;
- $(".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 = $('<div id="freeze" class="modal-backdrop fade"></div>')
- .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 f6599e9..0000000
--- 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(`
- <div class="cart-empty frappe-card">
- <div class="cart-empty-state">
- <img src="/assets/erpnext/images/ui-states/cart-empty-state.png" alt="Empty Cart">
- </div>
- <div class="cart-empty-message mt-4">${ __('Wishlist is empty !') }</p>
- </div>
- `);
- }
-
-});
-
-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 6ef1892..18d7c6c 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 6ae464d..0000000
--- 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 8ff681b..95d2d2c 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -26,7 +26,6 @@
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 @@
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 @@
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 @@
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 @@
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 5623a12..590cd3d 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -161,15 +161,6 @@
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 4b04ac1..d6eb11f 100644
--- a/erpnext/setup/doctype/item_group/item_group.js
+++ b/erpnext/setup/doctype/item_group/item_group.js
@@ -71,20 +71,6 @@
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 e0f5090..dfa5a8e 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": [
{
@@ -107,54 +94,6 @@
"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",
"hidden": 1,
@@ -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 cc67c69..fe7a241 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 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 @@
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 @@
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 ace5cca..d4aac5e 100644
--- a/erpnext/setup/setup_wizard/operations/company_setup.py
+++ b/erpnext/setup/setup_wizard/operations/company_setup.py
@@ -33,20 +33,6 @@
).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 ae6881b..2205924 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -454,7 +454,6 @@
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 @@
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 5806fd1..2f9cec4 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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Settings</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t</b></span>\",\"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\":\"<span class=\\\"h4\\\"><b>Settings</b></span>\",\"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 4ae9bf5..6e810e5 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -125,36 +125,6 @@
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('<a href="/app/website-item/%(item_encoded)s" class="strong">%(item)s</a>', {
- 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 1bcddfa..54491bb 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": [
@@ -816,14 +815,6 @@
"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",
"fieldtype": "Check",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index aff9587..9e28199 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 @@
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 @@
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 @@
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 @@
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 @@
)
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 @@
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 34bb4d1..88ae34f 100644
--- a/erpnext/stock/doctype/item/item_dashboard.py
+++ b/erpnext/stock/doctype/item/item_dashboard.py
@@ -32,6 +32,5 @@
{"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 e77d53a..21c0f18 100644
--- a/erpnext/stock/doctype/price_list/price_list.py
+++ b/erpnext/stock/doctype/price_list/price_list.py
@@ -13,9 +13,6 @@
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 @@
(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 358c1c5..0000000
--- 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 %}
-<div class="item-breadcrumbs small text-muted">
- {% include "templates/includes/breadcrumbs.html" %}
-</div>
-{% endblock %}
-
-{% block page_content %}
-<div class="product-container item-main">
- {% from "erpnext/templates/includes/macros.html" import product_image %}
- <div class="item-content">
- <div class="product-page-content" itemscope itemtype="http://schema.org/Product">
- <!-- Image, Description, Add to Cart -->
- <div class="row mb-5">
- {% include "templates/generators/item/item_image.html" %}
- {% include "templates/generators/item/item_details.html" %}
- </div>
- </div>
- </div>
-</div>
-
-<!-- Additional Info/Reviews, Recommendations -->
-<div class="d-flex">
- {% 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 '' %}
-
- <div class="product-container mt-4 {{ padding_top }} {{ info_col }}">
- <div class="item-content {{ 'mt-minus-2' if (show_tabs and tabs) else '' }}">
- <div class="product-page-content">
- <!-- Product Specifications Table Section -->
- {% if show_tabs and tabs %}
- <div class="category-tabs">
- <!-- tabs -->
- {{ web_block("Section with Tabs", values=tabs, add_container=0,
- add_top_padding=0, add_bottom_padding=0)
- }}
- </div>
- {% elif website_specifications %}
- {% include "templates/generators/item/item_specifications.html"%}
- {% endif %}
-
- <!-- Advanced Custom Website Content -->
- {{ doc.website_content or '' }}
-
- <!-- Reviews and Comments -->
- {% if shopping_cart.cart_settings.enable_reviews and not doc.has_variants %}
- {% include "templates/generators/item/item_reviews.html"%}
- {% endif %}
- </div>
- </div>
- </div>
-
- <!-- Recommended Items -->
- {% if show_recommended_items %}
- <div class="mt-4 col-3 recommended-item-section">
- <span class="recommendation-header">Recommended</span>
- <div class="product-container mt-2 recommendation-container">
- {% for item in recommended_items %}
- {{ recommended_item_row(item) }}
- {% endfor %}
- </div>
- </div>
- {% endif %}
-
-</div>
-{% endblock %}
-
-{% block base_scripts %}
-<!-- js should be loaded in body! -->
-<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
-{{ 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 9bd3f75..0000000
--- 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 %}
-
-<div class="item-cart row mt-2" data-variant-item-code="{{ item_code }}">
- <div class="col-md-12">
- <!-- Price and Availability -->
- {% if cart_settings.show_price and product_info.price %}
- {% set price_info = product_info.price %}
-
- <div class="product-price">
- <!-- Final Price -->
- <span itemprop="offers" itemscope itemtype="https://schema.org/Offer">
- <span itemprop="price" content="{{ price_info.price_list_rate }}">{{ price_info.formatted_price_sales_uom }}</span>
- <span style="display:none;" itemprop="priceCurrency" content="{{ price_info.currency }}">{{ price_info.currency }}</span>
- </span>
-
- <!-- Striked Price and Discount -->
- {% if price_info.formatted_mrp %}
- <small class="formatted-price">
- <s>MRP {{ price_info.formatted_mrp }}</s>
- </small>
- <small class="ml-1 formatted-price in-green">
- -{{ price_info.get("formatted_discount_percent") or price_info.get("formatted_discount_rate")}}
- </small>
- {% endif %}
-
- <!-- Price per UOM -->
- <small class="formatted-price ml-2">
- ({{ price_info.formatted_price }} / {{ product_info.uom }})
- </small>
- </div>
- {% else %}
- {{ _("UOM") }} : {{ product_info.uom }}
- {% endif %}
-
- {% if cart_settings.show_stock_availability %}
- <div class="mt-2">
- {% if product_info.get("on_backorder") %}
- <span class="no-stock out-of-stock" style="color: var(--primary-color);">
- {{ _('Available on backorder') }}
- </span>
- {% elif product_info.in_stock == 0 %}
- <span class="no-stock out-of-stock">
- {{ _('Out of stock') }}
- </span>
- {% elif product_info.in_stock == 1 %}
- <span class="in-green has-stock">
- {{ _('In stock') }}
- {% if product_info.show_stock_qty and product_info.stock_qty %}
- ({{ product_info.stock_qty }})
- {% endif %}
- </span>
- {% endif %}
- </div>
- {% endif %}
-
- <!-- Offers -->
- {% if doc.offers %}
- <br>
- <div class="offers-heading mb-4">
- <span class="mr-1 tag-icon">
- <svg class="icon icon-lg"><use href="#icon-tag"></use></svg>
- </span>
- <b>Available Offers</b>
- </div>
- <div class="offer-container">
- {% for offer in doc.offers %}
- <div class="mt-2 d-flex">
- <div class="mr-2" >
- <svg width="24" height="24" viewBox="0 0 24 24" stroke="var(--yellow-500)" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M19 15.6213C19 15.2235 19.158 14.842 19.4393 14.5607L20.9393 13.0607C21.5251 12.4749 21.5251 11.5251 20.9393 10.9393L19.4393 9.43934C19.158 9.15804 19 8.7765 19 8.37868V6.5C19 5.67157 18.3284 5 17.5 5H15.6213C15.2235 5 14.842 4.84196 14.5607 4.56066L13.0607 3.06066C12.4749 2.47487 11.5251 2.47487 10.9393 3.06066L9.43934 4.56066C9.15804 4.84196 8.7765 5 8.37868 5H6.5C5.67157 5 5 5.67157 5 6.5V8.37868C5 8.7765 4.84196 9.15804 4.56066 9.43934L3.06066 10.9393C2.47487 11.5251 2.47487 12.4749 3.06066 13.0607L4.56066 14.5607C4.84196 14.842 5 15.2235 5 15.6213V17.5C5 18.3284 5.67157 19 6.5 19H8.37868C8.7765 19 9.15804 19.158 9.43934 19.4393L10.9393 20.9393C11.5251 21.5251 12.4749 21.5251 13.0607 20.9393L14.5607 19.4393C14.842 19.158 15.2235 19 15.6213 19H17.5C18.3284 19 19 18.3284 19 17.5V15.6213Z" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M15 9L9 15" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M10.5 9.5C10.5 10.0523 10.0523 10.5 9.5 10.5C8.94772 10.5 8.5 10.0523 8.5 9.5C8.5 8.94772 8.94772 8.5 9.5 8.5C10.0523 8.5 10.5 8.94772 10.5 9.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M15.5 14.5C15.5 15.0523 15.0523 15.5 14.5 15.5C13.9477 15.5 13.5 15.0523 13.5 14.5C13.5 13.9477 13.9477 13.5 14.5 13.5C15.0523 13.5 15.5 13.9477 15.5 14.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- </div>
- <p class="mr-1 mb-1">
- {{ _(offer.offer_title) }}:
- {{ _(offer.offer_subtitle) if offer.offer_subtitle else '' }}
- <a class="offer-details" href="#"
- data-offer-title="{{ offer.offer_title }}" data-offer-id="{{ offer.name }}"
- role="button">
- {{ _("More") }}
- </a>
- </p>
- </div>
- {% endfor %}
- </div>
- {% endif %}
-
- <!-- Add to Cart / View in Cart, Contact Us -->
- <div class="mt-6 mb-5">
- <div class="mb-4 d-flex">
- <!-- Add to Cart -->
- {% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %}
- <a href="/cart" class="btn btn-light btn-view-in-cart hidden mr-2 font-md"
- role="button">
- {{ _("View in Cart") if cart_settings.enable_checkout else _("View in Quote") }}
- </a>
- <button
- data-item-code="{{item_code}}"
- class="btn btn-primary btn-add-to-cart mr-2 w-30-40"
- >
- <span class="mr-2">
- <svg class="icon icon-md">
- <use href="#icon-assets"></use>
- </svg>
- </span>
- {{ _("Add to Cart") if cart_settings.enable_checkout else _("Add to Quote") }}
- </button>
- {% endif %}
-
- <!-- Contact Us -->
- {% if cart_settings.show_contact_us_button %}
- {% include "templates/generators/item/item_inquiry.html" %}
- {% endif %}
- </div>
- </div>
- </div>
-</div>
-
-<script>
- frappe.ready(() => {
- $('.page_content').on('click', '.btn-add-to-cart', (e) => {
- // Bind action on add to cart button
- const $btn = $(e.currentTarget);
- $btn.prop('disabled', true);
- const item_code = $btn.data('item-code');
- erpnext.e_commerce.shopping_cart.update_cart({
- item_code,
- qty: 1,
- callback(r) {
- $btn.prop('disabled', false);
- if (r.message) {
- $('.btn-add-to-cart, .btn-view-in-cart').toggleClass('hidden');
- }
- }
- });
- });
-
- $('.page_content').on('click', '.offer-details', (e) => {
- // Bind action on More link in Offers
- const $btn = $(e.currentTarget);
- $btn.prop('disabled', true);
-
- var d = new frappe.ui.Dialog({
- title: __($btn.data('offer-title')),
- fields: [
- {
- fieldname: 'offer_details',
- fieldtype: 'HTML'
- },
- {
- fieldname: 'section_break',
- fieldtype: 'Section Break'
- }
- ]
- });
-
- frappe.call({
- method: 'erpnext.e_commerce.doctype.website_offer.website_offer.get_offer_details',
- args: {
- offer_id: $btn.data('offer-id')
- },
- callback: (value) => {
- d.set_value("offer_details", value.message);
- d.show();
- $btn.prop('disabled', false);
- }
- })
-
- });
- });
-
-
-</script>
-
-{% endif %}
diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html
deleted file mode 100644
index e97a275..0000000
--- 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 %}
-
-<div class="mt-5 mb-6">
- {% if cart_settings.enable_variants | int %}
- <button class="btn btn-primary-light btn-configure font-md mr-2"
- data-item-code="{{ doc.item_code }}"
- data-item-name="{{ doc.web_item_name }}"
- >
- {{ _('Select Variant') }}
- </button>
- {% endif %}
- {% if cart_settings.show_contact_us_button %}
- {% include "templates/generators/item/item_inquiry.html" %}
- {% endif %}
-</div>
-<script>
-{% include "templates/generators/item/item_configure.js" %}
-</script>
-{% endif %}
diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js
deleted file mode 100644
index 9beba3f..0000000
--- 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 = $(`<a href>${__("Clear")}</a>`);
- $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 = $('<div class="range-selector">')
- .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(`
- <div class="alert alert-warning d-flex justify-content-between align-items-center" role="alert">
- ${__('Loading...')}
- </div>
- `);
- }
-
- 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 ? `
- <button data-item-code="${one_item}"
- class="btn btn-primary btn-add-to-cart w-100"
- data-action="btn_add_to_cart"
- >
- <span class="mr-2">
- ${frappe.utils.icon('assets', 'md')}
- </span>
- ${__("Add to Cart")}
- </button>
- ` : '';
-
- 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
- ? `<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
- <div><div>
- ${one_item}
- ${product_info && product_info.price && !$.isEmptyObject(product_info.price)
- ? '(' + product_info.price.formatted_price_sales_uom + ')'
- : ''
- }
-
- ${available_qty === 0 && product_info && product_info?.is_stock_item
- ? '<span class="text-danger">(' + __('Out of Stock') + ')</span>' : ''}
-
- </div></div>
- <a href data-action="btn_clear_values" data-item-code="${one_item}">
- ${__('Clear Values')}
- </a>
- </div>`
- : `<div class="alert alert-warning d-flex justify-content-between align-items-center" role="alert">
- <span>
- ${items_found}
- </span>
- <a href data-action="btn_clear_values">
- ${__('Clear values')}
- </a>
- </div>`;
- /* 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 = $('<div class="status-area mt-5">');
- 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 028936b..0000000
--- 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 {} %}
-
-<div class="col-md-7 product-details {{ width_class }}">
- <div class="d-flex">
- <!-- title -->
- <div class="product-title col-11" itemprop="name">
- {{ doc.web_item_name }}
- </div>
-
- <!-- Wishlist -->
- {% if cart_settings.enable_wishlist %}
- <div class="like-action-item-fp like-action {{ 'like-action-wished' if wished else ''}} ml-2"
- data-item-code="{{ doc.item_code }}">
- <svg class="icon sm">
- <use class="{{ 'wished' if wished else 'not-wished' }} wish-icon" href="#icon-heart"></use>
- </svg>
- </div>
- {% endif %}
- </div>
-
- <p class="product-code">
- <span class="product-item-group">
- {{ _(doc.item_group) }}
- </span>
- <span class="product-item-code">
- {{ _("Item Code") }}:
- </span>
- <span itemprop="productID">{{ doc.item_code }}</span>
- </p>
- {% if has_variants %}
- <!-- configure template -->
- {% include "templates/generators/item/item_configure.html" %}
- {% else %}
- <!-- add variant to cart -->
- {% include "templates/generators/item/item_add_to_cart.html" %}
- {% endif %}
- <!-- description -->
- <div class="product-description" itemprop="description">
- {% 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 %}
- </div>
-</div>
-
-{% block base_scripts %}
-<!-- js should be loaded in body! -->
-<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
-{% endblock %}
-
-<script>
- $('.page_content').on('click', '.like-action-item-fp', (e) => {
- // Bind action on wishlist button
- const $btn = $(e.currentTarget);
- erpnext.e_commerce.wishlist.wishlist_action($btn);
- });
-</script>
\ 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 e1bb3b9..0000000
--- a/erpnext/templates/generators/item/item_image.html
+++ /dev/null
@@ -1,108 +0,0 @@
-{% set column_size = 5 if slides else 4 %}
-<div class="col-md-{{ column_size }} h-100 d-flex mb-4">
- {% if slides %}
- <div class="item-slideshow d-flex flex-column mr-3">
- {% for item in slides %}
- <img class="item-slideshow-image mb-2 {% if loop.first %}active{% endif %}"
- src="{{ item.image }}" alt="{{ item.heading }}">
- {% endfor %}
- </div>
- {{ product_image(slides[0].image, 'product-image') }}
- <!-- Simple image slideshow -->
- <script>
- frappe.ready(() => {
- $('.page_content').on('click', '.item-slideshow-image', (e) => {
- const $img = $(e.currentTarget);
- const link = $img.prop('src');
- const $product_image = $('.product-image');
- $product_image.find('a').prop('href', link);
- $product_image.find('img').prop('src', link);
-
- $('.item-slideshow-image').removeClass('active');
- $img.addClass('active');
- });
- })
- </script>
- {% else %}
- {{ product_image(doc.website_image, alt=doc.website_image_alt or doc.item_name) }}
- {% endif %}
-
- <!-- Simple image preview -->
-
- <div class="image-zoom-view" style="display: none;">
- <button type="button" class="close" aria-label="Close">
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
- stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x">
- <line x1="18" y1="6" x2="6" y2="18"></line>
- <line x1="6" y1="6" x2="18" y2="18"></line>
- </svg>
- </button>
- </div>
-</div>
-<style>
- .website-image {
- cursor: pointer;
- }
-
- .image-zoom-view {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- height: 100vh;
- width: 100vw;
- display: flex;
- justify-content: center;
- align-items: center;
- background: rgba(0, 0, 0, 0.8);
- z-index: 1080;
- }
-
- .image-zoom-view img {
- max-height: 100%;
- max-width: 100%;
- }
-
- .image-zoom-view button {
- position: absolute;
- right: 3rem;
- top: 2rem;
- }
-
- .image-zoom-view svg {
- color: var(--white);
- }
-</style>
-<script>
- frappe.ready(() => {
- const $zoom_wrapper = $('.image-zoom-view');
-
- $('.website-image').on('click', (e) => {
- e.preventDefault();
- const $img = $(e.target);
- const src = $img.prop('src');
- if (!src) return;
- show_preview(src);
- });
-
- $zoom_wrapper.on('click', 'button', hide_preview);
-
- $(document).on('keydown', (e) => {
- if (e.key === 'Escape') {
- hide_preview();
- }
- });
-
- function show_preview(src) {
- $zoom_wrapper.show();
- const $img = $(`<img src="${src}">`)
- $zoom_wrapper.append($img);
- }
-
- function hide_preview() {
- $zoom_wrapper.find('img').remove();
- $zoom_wrapper.hide();
- }
- })
-</script>
diff --git a/erpnext/templates/generators/item/item_inquiry.html b/erpnext/templates/generators/item/item_inquiry.html
deleted file mode 100644
index af636f1..0000000
--- 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 %}
- <button class="btn btn-inquiry font-md w-30-40" data-item-code="{{ doc.name }}">
- {{ _('Contact Us') }}
- </button>
- {% endif %}
-<script>
-{% include "templates/generators/item/item_inquiry.js" %}
-</script>
-{% endif %}
diff --git a/erpnext/templates/generators/item/item_inquiry.js b/erpnext/templates/generators/item/item_inquiry.js
deleted file mode 100644
index 0aee996..0000000
--- 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 c62c6f7..0000000
--- 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 %}
-
-<div class="mt-4 ratings-reviews-section">
- <!-- Title and Action -->
- <div class="w-100 mt-4 mb-2 d-flex">
- <div class="reviews-header col-9">
- {{ _("Customer Reviews") }}
- </div>
-
- <div class="write-a-review-btn col-3">
- <!-- Write a Review for legitimate users -->
- {% if frappe.session.user != "Guest" and user_is_customer %}
- <button class="btn btn-write-review"
- data-web-item="{{ doc.name }}">
- {{ _("Write a Review") }}
- </button>
- {% endif %}
- </div>
- </div>
-
- <!-- Summary -->
- {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }}
-
-
- <!-- Reviews and Comments -->
- <div class="mt-8">
- {% if reviews %}
- {{ user_review(reviews) }}
-
- {% if total_reviews > 4 %}
- <div class="mt-6 mb-6"style="color: var(--primary);">
- <a href="/customer_reviews?web_item={{ doc.name }}">{{ _("View all reviews") }}</a>
- </div>
- {% endif %}
-
- {% else %}
- <h6 class="text-muted mt-6">
- {{ _("No Reviews") }}
- </h6>
- {% endif %}
- </div>
-</div>
-
-<script>
- frappe.ready(() => {
- $('.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() {
- var data = d.get_values();
- frappe.call({
- method: "erpnext.e_commerce.doctype.item_review.item_review.add_item_review",
- args: {
- web_item: "{{ doc.name }}",
- title: data.title,
- rating: data.rating,
- comment: data.comment
- },
- freeze: true,
- freeze_message: __("Submitting Review ..."),
- callback: function(r) {
- if(!r.exc) {
- frappe.msgprint({
- message: __("Thank you for the review"),
- title: __("Review Submitted"),
- indicator: "green"
- });
- d.hide();
- location.reload();
- }
- }
- });
- },
- primary_action_label: __('Submit')
- });
- d.show();
- });
- });
-</script>
diff --git a/erpnext/templates/generators/item/item_specifications.html b/erpnext/templates/generators/item/item_specifications.html
deleted file mode 100644
index 0814d81..0000000
--- a/erpnext/templates/generators/item/item_specifications.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!-- Is reused to render within tabs as well as independently -->
-{% if website_specifications %}
-<div class="{{ 'mt-2' if not show_tabs else 'mt-5'}} item-website-specification">
- <div class="col-md-11">
- {% if not show_tabs %}
- <div class="product-title mb-5 mt-4">
- Product Details
- </div>
- {% endif %}
- <table class="table">
- {% for d in website_specifications -%}
- <tr>
- <td class="spec-label">{{ d.label }}</td>
- <td class="spec-content">{{ d.description }}</td>
- </tr>
- {%- endfor %}
- </table>
- </div>
-</div>
-{% endif %}
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
deleted file mode 100644
index 956c3c51..0000000
--- 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 %}
-<div class="mb-6">{{ _(item_group_name) }}</div>
-{% endblock header %}
-
-{% block script %}
-<script type="text/javascript" src="/all-products/index.js"></script>
-{% endblock %}
-
-{% block breadcrumbs %}
-<div class="item-breadcrumbs small text-muted">
- {% include "templates/includes/breadcrumbs.html" %}
-</div>
-{% endblock %}
-
-{% block page_content %}
-<div class="item-group-content" itemscope itemtype="http://schema.org/Product"
- data-item-group="{{ name }}">
- <div class="item-group-slideshow">
- {% if slideshow %} <!-- slideshow -->
- {{ web_block(
- "Hero Slider",
- values=slideshow,
- add_container=0,
- add_top_padding=0,
- add_bottom_padding=0,
- ) }}
- {% endif %}
-
- {% if description %} <!-- description -->
- <div class="item-group-description text-muted mb-5" itemprop="description">{{ description or ""}}</div>
- {% endif %}
- </div>
- <div class="row">
- <div id="product-listing" class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
- <!-- Products Rendered in all-products/index.js-->
- </div>
-
- <div class="col-12 order-1 col-md-3 order-md-1">
- <div class="collapse d-md-block mr-4 filters-section" id="product-filters">
- <div class="d-flex justify-content-between align-items-center mb-5 title-section">
- <div class="mb-4 filters-title" > {{ _('Filters') }} </div>
- <a class="mb-4 clear-filters" href="/{{ doc.route }}">{{ _('Clear All') }}</a>
- </div>
- <!-- field filters -->
- {{ field_filter_section(field_filters) }}
-
- <!-- attribute filters -->
- {{ attribute_filter_section(attribute_filters) }}
-
- </div>
-
- </div>
- </div>
-</div>
-
-<script>
- frappe.ready(() => {
- $('.btn-prev, .btn-next').click((e) => {
- const $btn = $(e.target);
- $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;
- });
- });
-</script>
-{% endblock %}
diff --git a/erpnext/templates/includes/cart/address_card.html b/erpnext/templates/includes/cart/address_card.html
deleted file mode 100644
index 830ed64..0000000
--- a/erpnext/templates/includes/cart/address_card.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<div class="card address-card h-100">
- <div class="btn btn-sm btn-default btn-change-address font-md" style="position: absolute; right: 0; top: 0;">
- {{ _('Change') }}
- </div>
- <div class="card-body p-0">
- <div class="card-title">{{ address.title }}</div>
- <div class="card-text mb-2">
- {{ address.display }}
- </div>
- <a href="/addresses?name={{address.name}}" class="card-link">
- <svg class="icon icon-sm">
- <use href="#icon-edit"></use>
- </svg>
- {{ _('Edit') }}
- </a>
- </div>
-</div>
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 646210e..0000000
--- a/erpnext/templates/includes/cart/address_picker_card.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<div class="card address-card h-100">
- <div class="check" style="position: absolute; right: 15px; top: 15px;">
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
- </div>
- <div class="card-body">
- <h5 class="card-title">{{ address.title }}</h5>
- <p class="card-text text-muted">
- {{ address.display }}
- </p>
- <a href="/addresses?name={{address.name}}" class="card-link">{{ _('Edit') }}</a>
- </div>
-</div>
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
deleted file mode 100644
index a8188ec..0000000
--- 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 %}
-
-<div class="mb-3 frappe-card p-5" data-section="shipping-address">
- <div class="d-flex">
- <div class="col-6 address-header"><h6>{{ _("Shipping Address") }}</h6></div>
- <div class="col-6" style="padding: 0;">
- <a class="ml-4 btn-new-address" role="button">{{ _("Add a new address") }}</a>
- </div>
- </div>
-
- <hr>
- {% for address in shipping_addresses %}
- {% if doc.shipping_address_name == address.name %}
- <div class="row no-gutters" data-fieldname="shipping_address_name">
- <div class="w-100 address-container" data-address-name="{{address.name}}" data-address-type="shipping" data-active>
- {% include "templates/includes/cart/address_card.html" %}
- </div>
- </div>
- {% endif %}
- {% endfor %}
-</div>
-
-<!-- Billing Address -->
-<div class="checkbox ml-1 mb-2">
- <label for="input_same_billing">
- <input type="checkbox" class="product-filter" id="input_same_billing" checked style="width: 14px !important">
- <span class="label-area font-md">{{ _('Billing Address is same as Shipping Address') }}</span>
- </label>
-</div>
-
-{% if billing_addresses %}
- <div class="mb-3 frappe-card p-5" data-section="billing-address">
- <div class="d-flex">
- <div class="col-6 address-header"><h6>{{ _("Billing Address") }}</h6></div>
- <div class="col-6" style="padding: 0;">
- <a class="ml-4 btn-new-address" role="button">{{ _("Add a new address") }}</a>
- </div>
- </div>
-
- <hr>
- {% for address in billing_addresses %}
- {% if doc.customer_address == address.name %}
- <div class="row no-gutters" data-fieldname="customer_address">
- <div class="w-100 address-container" data-address-name="{{address.name}}" data-address-type="billing" data-active>
- {% include "templates/includes/cart/address_card.html" %}
- </div>
- </div>
- {% endif %}
- {% endfor %}
- </div>
-{% endif %}
-
-<script>
-frappe.boot = {{ boot }}
-frappe.ready(() => {
- $(document).on('click', '.address-card', (e) => {
- const $target = $(e.currentTarget);
- const $section = $target.closest('[data-section]');
- $section.find('.address-card').removeClass('active');
- $target.addClass('active');
- });
-
- $('#input_same_billing').change((e) => {
- const $check = $(e.target);
- toggle_billing_address_section(!$check.is(':checked'));
- });
-
- $('.btn-new-address').click(() => {
- const d = new frappe.ui.Dialog({
- title: __('New Address'),
- fields: [
- {
- label: __('Address Title'),
- fieldname: 'address_title',
- fieldtype: 'Data',
- reqd: 1
- },
- {
- label: __('Address Line 1'),
- fieldname: 'address_line1',
- fieldtype: 'Data',
- reqd: 1
- },
- {
- label: __('Address Line 2'),
- fieldname: 'address_line2',
- fieldtype: 'Data'
- },
- {
- label: __('City/Town'),
- fieldname: 'city',
- fieldtype: 'Data',
- reqd: 1
- },
- {
- label: __('State'),
- fieldname: 'state',
- fieldtype: 'Data'
- },
- {
- label: __('Country'),
- fieldname: 'country',
- fieldtype: 'Link',
- options: 'Country',
- only_select: true,
- reqd: 1
- },
- {
- fieldname: "column_break0",
- fieldtype: "Column Break",
- width: "50%"
- },
- {
- label: __('Address Type'),
- fieldname: 'address_type',
- fieldtype: 'Select',
- options: [
- 'Billing',
- 'Shipping'
- ],
- reqd: 1
- },
- {
- label: __('Postal Code'),
- fieldname: 'pincode',
- fieldtype: 'Data'
- },
- {
- fieldname: "phone",
- fieldtype: "Data",
- label: "Phone",
- reqd: 1
- },
- ],
- primary_action_label: __('Save'),
- primary_action: (values) => {
- frappe.call('erpnext.e_commerce.shopping_cart.cart.add_new_address', { doc: values })
- .then(r => {
- frappe.call({
- method: "erpnext.e_commerce.shopping_cart.cart.update_cart_address",
- args: {
- address_type: r.message.address_type,
- address_name: r.message.name
- },
- callback: function (r) {
- d.hide();
- window.location.reload();
- }
- });
- });
-
- }
- })
-
- d.show();
- });
-
- function setup_state() {
- const shipping_address = $('[data-section="shipping-address"]')
- .find('[data-address-name][data-active]').attr('data-address-name');
-
- const billing_address = $('[data-section="billing-address"]')
- .find('[data-address-name][data-active]').attr('data-address-name');
-
- $('#input_same_billing').prop('checked', shipping_address === billing_address).trigger('change');
-
- if (!shipping_address && !billing_address) {
- $('#input_same_billing').prop('checked', true).trigger('change');
- }
-
- if (shipping_address) {
- $(`[data-section="shipping-address"] [data-address-name="${shipping_address}"] .address-card`).addClass('active');
- }
- if (billing_address) {
- $(`[data-section="billing-address"] [data-address-name="${billing_address}"] .address-card`).addClass('active');
- }
- }
-
- setup_state();
-
- function toggle_billing_address_section(flag) {
- $('[data-section="billing-address"]').toggle(flag);
- }
-});
-</script>
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 66a50ec..0000000
--- a/erpnext/templates/includes/cart/cart_address_picker.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<div class="mb-3 frappe-card p-5" data-section="shipping-address">
- <h6>{{ _("Shipping Address") }}</h6>
-</div>
diff --git a/erpnext/templates/includes/cart/cart_dropdown.html b/erpnext/templates/includes/cart/cart_dropdown.html
deleted file mode 100644
index 38ad183..0000000
--- a/erpnext/templates/includes/cart/cart_dropdown.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<div class="cart-dropdown-container">
- <div id="cart-error" class="alert alert-danger"
- style="display: none;"></div>
- <div class="row checkout-btn">
- <div class="col-sm-12 col-xs-12">
- <a href="/cart" class="btn btn-block btn-primary">{{ _("Checkout") }}</a>
- </div>
- </div>
- <div class="row cart-items-dropdown cart-item-header text-muted">
- <div class="col-sm-6 col-xs-6 h6 text-uppercase">
- {{ _("Item") }}
- </div>
- <div class="col-sm-6 col-xs-6 text-right h6 text-uppercase">
- {{ _("Price") }}
- </div>
- </div>
-
- {% if doc.items %}
- <div class="row cart-items-dropdown">
- <div class="col-sm-12 col-xs-12">
- {% include "templates/includes/cart/cart_items_dropdown.html" %}
- </div>
- </div>
- {% else %}
- <p>{{ _("Cart is Empty") }}</p>
- {% endif %}
-</div>
diff --git a/erpnext/templates/includes/cart/cart_items.html b/erpnext/templates/includes/cart/cart_items.html
deleted file mode 100644
index 428b36e..0000000
--- 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) %}
- <div>
- {{ item.get_formatted('amount') }}
- </div>
-
- {% if item.is_free_item %}
- <div class="text-success mt-4">
- <span class="free-tag">
- {{ _('FREE') }}
- </span>
- </div>
- {% else %}
- <span class="item-rate">
- {{ _('Rate:') }} {{ item.get_formatted('rate') }}
- </span>
- {% endif %}
-{% endmacro %}
-
-{% for d in doc.items %}
- <tr data-name="{{ d.name }}">
- <td style="width: 60%;">
- <div class="d-flex">
- <div class="cart-item-image mr-4">
- {% if d.thumbnail %}
- {{ product_image(d.thumbnail, alt="d.web_item_name", no_border=True) }}
- {% else %}
- <div class = "no-image-cart-item">
- {{ frappe.utils.get_abbr(d.web_item_name) or "NA" }}
- </div>
- {% endif %}
- </div>
-
- <div class="d-flex w-100" style="flex-direction: column;">
- <div class="item-title mb-1 mr-3">
- {{ d.get("web_item_name") or d.item_name }}
- </div>
- <div class="item-subtitle mr-2">
- {{ d.item_code }}
- </div>
- {%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %}
- {% if variant_of %}
- <span class="item-subtitle mr-2">
- {{ _('Variant of') }}
- <a href="{{frappe.db.get_value('Website Item', {'item_code': variant_of}, 'route') or '#'}}">
- {{ variant_of }}
- </a>
- </span>
- {% endif %}
-
- <div class="mt-2 notes">
- <textarea data-item-code="{{d.item_code}}" class="form-control" rows="2" placeholder="{{ _('Add notes') }}">
- {{d.additional_notes or ''}}
- </textarea>
- </div>
- </div>
- </div>
- </td>
-
- <!-- Qty column -->
- <td class="text-right" style="width: 25%;">
- <div class="d-flex">
- {% set disabled = 'disabled' if d.is_free_item else '' %}
- <div class="input-group number-spinner mt-1 mb-4">
- <span class="input-group-prepend d-sm-inline-block">
- <button class="btn cart-btn" data-dir="dwn" {{ disabled }}>
- {{ '–' if not d.is_free_item else ''}}
- </button>
- </span>
-
- <input class="form-control text-center cart-qty" value="{{ d.get_formatted('qty') }}" data-item-code="{{ d.item_code }}"
- style="max-width: 70px;" {{ disabled }}>
-
- <span class="input-group-append d-sm-inline-block">
- <button class="btn cart-btn" data-dir="up" {{ disabled }}>
- {{ '+' if not d.is_free_item else ''}}
- </button>
- </span>
- </div>
-
- <div>
- {% if not d.is_free_item %}
- <div class="remove-cart-item column-sm-view d-flex" data-item-code="{{ d.item_code }}">
- <span>
- <svg class="icon sm remove-cart-item-logo"
- width="18" height="18" viewBox="0 0 18 18"
- xmlns="http://www.w3.org/2000/svg" id="icon-close">
- <path fill-rule="evenodd" clip-rule="evenodd" d="M4.146 11.217a.5.5 0 1 0 .708.708l3.182-3.182 3.181 3.182a.5.5 0 1 0 .708-.708l-3.182-3.18 3.182-3.182a.5.5 0 1 0-.708-.708l-3.18 3.181-3.183-3.182a.5.5 0 0 0-.708.708l3.182 3.182-3.182 3.181z" stroke-width="0"></path>
- </svg>
- </span>
- </div>
- {% endif %}
- </div>
- </div>
-
-
- <!-- Shown on mobile view, else hidden -->
- {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
- <div class="text-right sm-item-subtotal">
- {{ item_subtotal(d) }}
- </div>
- {% endif %}
- </td>
-
- <!-- Subtotal column -->
- {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
- <td class="text-right item-subtotal column-sm-view w-100">
- {{ item_subtotal(d) }}
- </td>
- {% endif %}
- </tr>
-{% 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 5d107fc..0000000
--- 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 %}
-<div class="row cart-dropdown">
- <div class="col-sm-8 col-xs-8 col-name-description">
- {{ item_name_and_description_cart(d) }}
- </div>
- <div class="col-sm-4 col-xs-4 text-right col-amount">
- {{ d.get_formatted("amount") }}
- </div>
-</div>
-{% 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 c94fde4..0000000
--- a/erpnext/templates/includes/cart/cart_items_total.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!-- Total at the end of the cart items -->
-<tr>
- <th></th>
- <th class="text-left item-grand-total" colspan="1">
- {{ _("Total") }}
- </th>
- <th class="text-left item-grand-total totals" colspan="3">
- {{ doc.get_formatted("total") }}
- </th>
-</tr>
\ 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 fd95dba..0000000
--- 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) %}
-
-<div class="panel panel-default">
- <div class="panel-heading">
- <div class="row">
- <div class="col-sm-10 address-title"
- data-address-name="{{ address.name }}">
- <strong>{{ address.name }}</strong></div>
- <div class="col-sm-2 text-right">
- <input type="checkbox"
- data-fieldname="{{ fieldname }}"
- data-address-name="{{ address.name}}"
- {{ "checked" if selected else "" }}></div>
- </div>
- </div>
- <div class="panel-collapse"
- data-address-name="{{ address.name }}">
- <div class="panel-body text-muted small">{{ address.display }}</div>
- </div>
-</div>
-{% 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 b5655a2..0000000
--- a/erpnext/templates/includes/cart/cart_payment_summary.html
+++ /dev/null
@@ -1,84 +0,0 @@
-<!-- Payment -->
-{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
-<h6>
- {{ _("Payment Summary") }}
-</h6>
-{% endif %}
-
-<div class="card h-100">
- <div class="card-body p-0">
- {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
- <table class="table w-100">
- <tr>
- {% set total_items = frappe.utils.cstr(frappe.utils.flt(doc.total_qty, 0)) %}
- <td class="bill-label">{{ _("Net Total (") + total_items + _(" Items)") }}</td>
- <td class="bill-content net-total text-right">{{ doc.get_formatted("net_total") }}</td>
- </tr>
-
- <!-- taxes -->
- {% for d in doc.taxes %}
- {% if d.base_tax_amount %}
- <tr>
- <td class="bill-label">
- {{ d.description }}
- </td>
- <td class="bill-content text-right">
- {{ d.get_formatted("base_tax_amount") }}
- </td>
- </tr>
- {% endif %}
- {% endfor %}
- </table>
-
- <!-- TODO: Apply Coupon Dialog-->
- <!-- {% set show_coupon_code = cart_settings.show_apply_coupon_code_in_website and cart_settings.enable_checkout %}
- {% if show_coupon_code %}
- <button class="btn btn-coupon-code w-100 text-left">
- <svg width="24" height="24" viewBox="0 0 24 24" stroke="var(--gray-600)" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M19 15.6213C19 15.2235 19.158 14.842 19.4393 14.5607L20.9393 13.0607C21.5251 12.4749 21.5251 11.5251 20.9393 10.9393L19.4393 9.43934C19.158 9.15804 19 8.7765 19 8.37868V6.5C19 5.67157 18.3284 5 17.5 5H15.6213C15.2235 5 14.842 4.84196 14.5607 4.56066L13.0607 3.06066C12.4749 2.47487 11.5251 2.47487 10.9393 3.06066L9.43934 4.56066C9.15804 4.84196 8.7765 5 8.37868 5H6.5C5.67157 5 5 5.67157 5 6.5V8.37868C5 8.7765 4.84196 9.15804 4.56066 9.43934L3.06066 10.9393C2.47487 11.5251 2.47487 12.4749 3.06066 13.0607L4.56066 14.5607C4.84196 14.842 5 15.2235 5 15.6213V17.5C5 18.3284 5.67157 19 6.5 19H8.37868C8.7765 19 9.15804 19.158 9.43934 19.4393L10.9393 20.9393C11.5251 21.5251 12.4749 21.5251 13.0607 20.9393L14.5607 19.4393C14.842 19.158 15.2235 19 15.6213 19H17.5C18.3284 19 19 18.3284 19 17.5V15.6213Z" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M15 9L9 15" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M10.5 9.5C10.5 10.0523 10.0523 10.5 9.5 10.5C8.94772 10.5 8.5 10.0523 8.5 9.5C8.5 8.94772 8.94772 8.5 9.5 8.5C10.0523 8.5 10.5 8.94772 10.5 9.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
- <path d="M15.5 14.5C15.5 15.0523 15.0523 15.5 14.5 15.5C13.9477 15.5 13.5 15.0523 13.5 14.5C13.5 13.9477 13.9477 13.5 14.5 13.5C15.0523 13.5 15.5 13.9477 15.5 14.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
- </svg>
- <span class="ml-2">Apply Coupon</span>
- </button>
- {% endif %} -->
-
- <table class="table w-100 grand-total mt-6">
- <tr>
- <td class="bill-content net-total">{{ _("Grand Total") }}</td>
- <td class="bill-content net-total text-right">{{ doc.get_formatted("grand_total") }}</td>
- </tr>
- </table>
- {% endif %}
-
- {% if cart_settings.enable_checkout %}
- <button class="btn btn-primary btn-place-order font-md w-100" type="button">
- {{ _('Place Order') }}
- </button>
- {% else %}
- <button class="btn btn-primary btn-request-for-quotation font-md w-100" type="button">
- {{ _('Request for Quote') }}
- </button>
- {% endif %}
- </div>
-</div>
-
-<!-- TODO: Apply Coupon Dialog-->
-<!-- <script>
- frappe.ready(() => {
- $('.btn-coupon-code').click((e) => {
- const $btn = $(e.currentTarget);
- const d = new frappe.ui.Dialog({
- title: __('Coupons'),
- fields: [
- {
- fieldname: 'coupons_area',
- fieldtype: 'HTML'
- }
- ]
- });
- d.show();
- });
- });
-</script> -->
\ 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 d7adae5..0000000
--- 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 %}
- <li class="shopping-cart cart-icon hidden">
- <a class="nav-link" href="/cart">
- <svg class="icon icon-lg">
- <use href="#icon-assets"></use>
- </svg>
- <span class="badge badge-primary shopping-badge" id="cart-count"></span>
- </a>
- </li>
- {% if frappe.db.get_single_value("E Commerce Settings", "enable_wishlist") %}
- <li class="wishlist wishlist-icon hidden">
- <a class="nav-link" href="/wishlist">
- <svg class="icon icon-lg">
- <use href="#icon-heart"></use>
- </svg>
- <span class="badge badge-primary shopping-badge" id="wish-count"></span>
- </a>
- </li>
- {% 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 d95b289..0000000
--- 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) %}
- <div class="row item_name_and_description">
- <div class="col-xs-4 col-sm-2 order-image-col">
- <div class="order-image h-100">
- {% if d.thumbnail or d.image %}
- {{ product_image(d.thumbnail or d.image, no_border=True) }}
- {% else %}
- <div class="no-image-cart-item" style="min-height: 100px;">
- {{ frappe.utils.get_abbr(d.item_name) or "NA" }}
- </div>
- {% endif %}
- </div>
- </div>
- <div class="col-xs-8 col-sm-10">
- {{ d.item_code }}
- <div class="text-muted small item-description">
- {{ html2text(d.description) | truncate(140) }}
- </div>
- <span class="text-muted mt-2 d-l-n order-qty">
- {{ _("Qty ") }}({{ d.get_formatted("qty") }})
- </span>
- </div>
- </div>
-{% endmacro %}
-
-{% macro item_name_and_description_cart(d) %}
- <div class="row item_name_dropdown">
- <div class="col-xs-4 col-sm-4 order-image-col">
- <div class="order-image">
- {{ product_image_square(d.thumbnail or d.image) }}
- </div>
- </div>
- <div class="col-xs-8 col-sm-8">
- {{ d.item_name|truncate(25) }}
- <div class="input-group number-spinner">
- <span class="input-group-btn">
- <button class="btn btn-light cart-btn" data-dir="dwn">
- –</button>
- </span>
- <input class="form-control text-right cart-qty"
- value = "{{ d.get_formatted('qty') }}"
- data-item-code="{{ d.item_code }}">
- <span class="input-group-btn">
- <button class="btn btn-light cart-btn" data-dir="up">
- +</button>
- </span>
- </div>
- </div>
- </div>
-{% endmacro %}
diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js
deleted file mode 100644
index a3979d0..0000000
--- 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 + "<div style='font-size: small'>\
- (" + r.message.product_info.price.formatted_price + " / " + r.message.product_info.uom + ")</div>");
-
- if(r.message.product_info.in_stock===0) {
- $(".item-stock").html("<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>");
- }
- 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("<div style='color: green'>\
- <i class='fa fa-check'></i> "+qty_display+"</div>");
- }
-
- 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 2b7d9e3..0000000
--- a/erpnext/templates/pages/cart.html
+++ /dev/null
@@ -1,132 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %} {{ _("Shopping Cart") }} {% endblock %}
-
-{% block header %}<h3 class="shopping-cart-header mt-2 mb-6">{{ _("Shopping Cart") }}</h1>{% endblock %}
-
-{% block header_actions %}
-{% endblock %}
-
-{% block page_content %}
-
-{% from "templates/includes/macros.html" import item_name_and_description %}
-
-{% if doc.items %}
-<div class="cart-container">
- <div class="row m-0">
- <!-- Left section -->
- <div class="col-md-8">
- <div class="frappe-card p-5 mb-4">
- <div id="cart-error" class="alert alert-danger" style="display: none;"></div>
- <div class="cart-items-header">
- {{ _('Items') }}
- </div>
- <table class="table mt-3 cart-table">
- <thead>
- <tr>
- <th class="item-column">{{ _('Item') }}</th>
- <th width="20%">{{ _('Quantity') }}</th>
- {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
- <th width="20" class="text-right column-sm-view">{{ _('Subtotal') }}</th>
- {% endif %}
- <th width="10%" class="column-sm-view"></th>
- </tr>
- </thead>
- <tbody class="cart-items">
- {% include "templates/includes/cart/cart_items.html" %}
- </tbody>
-
- {% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
- <tfoot class="cart-tax-items">
- {% include "templates/includes/cart/cart_items_total.html" %}
- </tfoot>
- {% endif %}
- </table>
-
- <div class="row mt-2">
- <div class="col-3">
- {% if cart_settings.enable_checkout %}
- <a class="btn btn-primary-light font-md" href="/orders">
- {{ _('Past Orders') }}
- </a>
- {% else %}
- <a class="btn btn-primary-light font-md" href="/quotations">
- {{ _('Past Quotes') }}
- </a>
- {% endif %}
- </div>
- <div class="col-9">
- {% if doc.items %}
- <div class="place-order-container">
- <a class="btn btn-primary-light mr-2 font-md" href="/all-products">
- {{ _('Continue Shopping') }}
- </a>
- </div>
- {% endif %}
- </div>
- </div>
- </div>
-
- <!-- Terms and Conditions -->
- {% if doc.items %}
- {% if doc.terms %}
- <div class="t-and-c-container mt-4 frappe-card">
- <h5>{{ _("Terms and Conditions") }}</h5>
- <div class="t-and-c-terms mt-2">
- {{ doc.terms }}
- </div>
- </div>
- {% endif %}
- </div>
-
- <!-- Right section -->
- <div class="col-md-4">
- <div class="cart-payment-addresses">
- <!-- Apply Coupon Code -->
- {% set show_coupon_code = cart_settings.show_apply_coupon_code_in_website and cart_settings.enable_checkout %}
- {% if show_coupon_code == 1%}
- <div class="mb-3">
- <div class="row no-gutters">
- <input type="text" class="txtcoupon form-control mr-3 w-50 font-md" placeholder="Enter Coupon Code" name="txtcouponcode" ></input>
- <button class="btn btn-primary btn-sm bt-coupon font-md">{{ _("Apply Coupon Code") }}</button>
- <input type="hidden" class="txtreferral_sales_partner font-md" placeholder="Enter Sales Partner" name="txtreferral_sales_partner" type="text"></input>
- </div>
- </div>
- {% endif %}
-
- <div class="mb-3 frappe-card p-5 payment-summary">
- {% include "templates/includes/cart/cart_payment_summary.html" %}
- </div>
-
- {% include "templates/includes/cart/cart_address.html" %}
- </div>
- </div>
- {% endif %}
- </div>
-</div>
-{% else %}
-<div class="cart-empty frappe-card">
- <div class="cart-empty-state">
- <img src="/assets/erpnext/images/ui-states/cart-empty-state.png" alt="Empty State">
- </div>
- <div class="cart-empty-message mt-4">{{ _('Your cart is Empty') }}</p>
- {% if cart_settings.enable_checkout %}
- <a class="btn btn-outline-primary" href="/orders" style="font-size: 16px;">
- {{ _('See past orders') }}
- </a>
- {% else %}
- <a class="btn btn-outline-primary" href="/quotations" style="font-size: 16px;">
- {{ _('See past quotations') }}
- </a>
- {% endif %}
-</div>
-{% endif %}
-
-{% endblock %}
-
-{% block base_scripts %}
-<!-- js should be loaded in body! -->
-{{ 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 fb2d159..0000000
--- 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('<div class="msg-box"><h4>' +
- title + '</h4><p class="text-muted">' + text + '</p></div>');
- },
-
- 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: `<div class="mb-3" data-section="shipping-address">
- <div class="row no-gutters" data-fieldname="shipping_address_name">
- {% for address in shipping_addresses %}
- <div class="mr-3 mb-3 w-100" data-address-name="{{address.name}}" data-address-type="shipping"
- {% if doc.shipping_address_name == address.name %} data-active {% endif %}>
- {% include "templates/includes/cart/address_picker_card.html" %}
- </div>
- {% endfor %}
- </div>
- </div>`,
- billing: `<div class="mb-3" data-section="billing-address">
- <div class="row no-gutters" data-fieldname="customer_address">
- {% for address in billing_addresses %}
- <div class="mr-3 mb-3 w-100" data-address-name="{{address.name}}" data-address-type="billing"
- {% if doc.shipping_address_name == address.name %} data-active {% endif %}>
- {% include "templates/includes/cart/address_picker_card.html" %}
- </div>
- {% endfor %}
- </div>
- </div>`,
- }[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 = '<select class="form-control">' + $.map(shipping_rules, function(rule) {
- return '<option value="' + rule[0] + '">' + rule[1] + '</option>' }).join("\n") +
- '</select>';
- }
-
- var $tax_row = $(repl('<div class="row">\
- <div class="col-md-9 col-sm-9">\
- <div class="row">\
- <div class="col-md-9 col-md-offset-3">' +
- (shipping_selector || '<p>%(description)s</p>') +
- '</div>\
- </div>\
- </div>\
- <div class="col-md-3 col-sm-3 text-right">\
- <p' + (shipping_selector ? ' style="margin-top: 5px;"' : "") + '>%(formatted_tax_amount)s</p>\
- </div>\
- </div>', 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("<br>");
- }
-
- $("#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("<br>");
- }
-
- $("#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 cadb46f..0000000
--- 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 121bec3..0000000
--- 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 %}
-<div class="product-container reviews-full-page col-md-12">
- {% if enable_reviews %}
- <!-- Title and Action -->
- <div class="w-100 mb-6 d-flex">
- <div class="reviews-header col-9">
- {{ _("Customer Reviews") }}
- </div>
-
- <div class="write-a-review-btn col-3">
- <!-- Write a Review for legitimate users -->
- {% if frappe.session.user != "Guest" and user_is_customer %}
- <button class="btn btn-write-review"
- data-web-item="{{ web_item }}">
- {{ _("Write a Review") }}
- </button>
- {% endif %}
- </div>
- </div>
-
- <!-- Summary -->
- {{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }}
-
-
- <!-- Reviews and Comments -->
- <div class="mt-8">
- {% if reviews %}
- {{ user_review(reviews) }}
-
- {% if not reviews | len >= total_reviews %}
- <button class="btn btn-light btn-view-more mr-2 mt-4 mb-4 w-30"
- data-web-item="{{ web_item }}">
- {{ _("View More") }}
- </button>
- {% endif %}
-
- {% else %}
- <h6 class="text-muted mt-6">
- {{ _("No Reviews") }}
- </h6>
- {% endif %}
- </div>
- {% else %}
- <!-- If reviews are disabled -->
- <div class="text-center">
- <h3 class="text-muted mt-8">
- {{ _("No Reviews") }}
- </h3>
- </div>
- {% endif %}
-</div>
-
-{% endblock %}
-
-{% block base_scripts %}
-<!-- js should be loaded in body! -->
-<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
-<script type="text/javascript" src="/assets/js/frappe-web.min.js"></script>
-<script type="text/javascript" src="/assets/js/control.min.js"></script>
-<script type="text/javascript" src="/assets/js/dialog.min.js"></script>
-<script type="text/javascript" src="/assets/js/bootstrap-4-web.min.js"></script>
-{% 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 c1f0c93..0000000
--- 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 27d966a..08e0432 100644
--- a/erpnext/templates/pages/home.html
+++ b/erpnext/templates/pages/home.html
@@ -17,9 +17,6 @@
<h3 class="d-block d-sm-none">{{ homepage.description }}</h3>
</div>
- <div class="container">
- <a href="{{ explore_link }}" class="mb-5 btn btn-primary">{{ _('Explore') }}</a>
- </div>
</section>
{% elif homepage.hero_section_based_on == 'Slideshow' and slideshow %}
<section class="hero-section">
@@ -29,26 +26,6 @@
{{ render_homepage_section(homepage.hero_section_doc) }}
{% endif %}
- {% if homepage.products %}
- <section class="container section-products my-5">
- <h3>{{ _('Products') }}</h3>
-
- <div class="row">
- {% for item in homepage.products %}
- <div class="col-md-4 mb-4">
- <div class="card h-100 justify-content-between">
- <img class="card-img-top website-image-extra-large" src="{{ item.image }}" loading="lazy" alt="{{ item.item_name }}"></img>
- <div class="card-body flex-grow-0">
- <h5 class="card-title">{{ item.item_name }}</h5>
- <a href="{{ item.route }}" class="card-link">{{ _('More details') }}</a>
- </div>
- </div>
- </div>
- {% endfor %}
- </div>
- </section>
- {% endif %}
-
{% if blogs %}
<section class="container my-5">
<h3>{{ _('Publications') }}</h3>
diff --git a/erpnext/templates/pages/home.py b/erpnext/templates/pages/home.py
index 47fb89d..751a5b0 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 @@
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 0574cde..0000000
--- 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 13772d3..d0968bf 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 @@
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 6a5425b..0000000
--- a/erpnext/templates/pages/product_search.html
+++ /dev/null
@@ -1,32 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %} {{ _("Product Search") }} {% endblock %}
-
-{% block header %}<h2>{{ _("Product Search") }}</h2>{% endblock %}
-
-{% block page_content %}
-<script>{% include "templates/includes/product_list.js" %}</script>
-
-<script>
-frappe.ready(function() {
- var txt = frappe.utils.get_url_arg("search");
- $(".search-results").html('{{ _("Search results for") + ": " + html2text(frappe.form_dict.search or "") | e | trim }}');
- window.search = txt;
- window.start = 0;
- window.get_product_list();
-});
-</script>
-
-<div class="product-search-content">
- <h3 class="search-results">{{ _("Search Results") }}</h3>
- <div id="search-list" class="row">
-
- </div>
- <div style="text-align: center;">
- <div class="more-btn"
- style="display: none; text-align: center;">
- <button class="btn btn-light">{{ _("More...") }}</button>
- </div>
- </div>
-</div>
-{% endblock %}
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py
deleted file mode 100644
index f40fd47..0000000
--- 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 7a81ded..0000000
--- a/erpnext/templates/pages/wishlist.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %} {{ _("Wishlist") }} {% endblock %}
-
-{% block header %}<h3 class="shopping-cart-header mt-2 mb-6">{{ _("Wishlist") }}</h1>{% endblock %}
-
-{% block page_content %}
-{% if items %}
- <div class="row">
- <div class="col-md-12 item-card-group-section">
- <div class="row products-list">
- {% from "erpnext/templates/includes/macros.html" import wishlist_card %}
- {% for item in items %}
- {{ wishlist_card(item, settings) }}
- {% endfor %}
- </div>
- </div>
- </div>
-{% else %}
- <div class="cart-empty frappe-card">
- <div class="cart-empty-state">
- <img src="/assets/erpnext/images/ui-states/cart-empty-state.png" alt="Empty Cart">
- </div>
- <div class="cart-empty-message mt-4">{{ _('Wishlist is empty!') }}</p>
- </div>
-{% 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 17607e4..0000000
--- 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 e967f70..7897c15 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 @@
)
if price:
- party = get_party()
pricing_rule_dict = frappe._dict(
{
"item_code": item_code,
@@ -187,16 +105,62 @@
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
+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
)
- return all(
- get_web_item_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock
- for d in items
- )
- 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 e69de29..0000000
--- a/erpnext/www/all-products/__init__.py
+++ /dev/null
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html
deleted file mode 100644
index 04fc74c..0000000
--- 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 %}
-<div class="mb-6">{{ _('All Products') }}</div>
-{% endblock header %}
-
-{% block page_content %}
-<div class="row">
- <!-- Items section -->
- <div id="product-listing" class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
- <!-- Rendered via JS -->
- </div>
-
- <!-- Filters Section -->
- <div class="col-12 order-1 col-md-3 order-md-1">
- <div class="collapse d-md-block mr-4 filters-section" id="product-filters">
- <div class="d-flex justify-content-between align-items-center mb-5 title-section">
- <div class="mb-4 filters-title" > {{ _('Filters') }} </div>
- <a class="mb-4 clear-filters" href="/all-products">{{ _('Clear All') }}</a>
- </div>
- <!-- field filters -->
- {% if field_filters %}
- {{ field_filter_section(field_filters) }}
- {% endif %}
-
- <!-- attribute filters -->
- {% if attribute_filters %}
- {{ attribute_filter_section(attribute_filters) }}
- {% endif %}
- </div>
-
- </div>
-</div>
-
-<script>
- frappe.ready(() => {
- $('.btn-prev, .btn-next').click((e) => {
- const $btn = $(e.target);
- $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;
- });
- });
-</script>
-
-{% endblock %}
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
deleted file mode 100644
index 98a8441..0000000
--- 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 fbf0dce..0000000
--- 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 91989a9..0000000
--- a/erpnext/www/all-products/not_found.html
+++ /dev/null
@@ -1 +0,0 @@
-<div class="d-flex justify-content-center p-3 text-muted">{{ _('No products found') }}</div>
diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/www/shop-by-category/__init__.py
+++ /dev/null
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 56cb63a..0000000
--- 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) -%}
-<!-- style defined at shop-by-category index -->
-<div class="card category-card" data-type="{{ type }}" data-name="{{ title }}">
- {% if image %}
- <img class="card-img-top" src="{{ image }}" alt="{{ title }}" style="height: 80%;">
- {% else %}
- <div class="placeholder-div">
- <span class="placeholder">
- {{ frappe.utils.get_abbr(title) }}
- </span>
- </div>
- {% endif %}
- <div class="card-body text-center text-muted">
- {{ title or '' }}
- </div>
- <a href="{{ url or '#' }}" class="stretched-link"></a>
-</div>
-{%- endmacro -%}
-
-<div class="col-12 item-card-group-section">
- <div class="row products-list product-category-section">
- {%- for row in data -%}
- {%- set title = row.name -%}
- {%- set image = row.get("image") -%}
- {%- if title -%}
- {{ card(title, image, type, row.get("route")) }}
- {%- endif -%}
- {%- endfor -%}
- </div>
-</div>
\ 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 04d2d57..0000000
--- 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 %}
-<style>
- .category-slideshow {
- margin-bottom: 2rem;
- }
- .category-card {
- height: 300px !important;
- width: 300px !important;
- margin: 30px !important;
- }
-</style>
-{% endblock %}
-
-{% block script %}
-<script type="text/javascript" src="/shop-by-category/index.js"></script>
-{% endblock %}
-
-{% block page_content %}
-<div class="shop-by-category-content">
- <div class="category-slideshow">
- {% if slideshow %}
- <!-- slideshow -->
- {{ web_block(
- "Hero Slider",
- values=slideshow,
- add_container=0,
- add_top_padding=0,
- add_bottom_padding=0,
- ) }}
- {% endif %}
- </div>
- <div class="category-tabs">
- {% if tabs %}
- <!-- tabs -->
- {{ web_block(
- "Section with Tabs",
- values=tabs,
- add_container=0,
- add_top_padding=0,
- add_bottom_padding=0
- ) }}
- {% endif %}
- </div>
-</div>
-{% 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 1b3116f..0000000
--- 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 913c183..0000000
--- 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