chore: Drive E-commerce via Website Item
- Removed Shopping Cart Settings
- Portal fully driven via E Commerce Settings
- All Item listing querying will happen via ProductQuery engine only
- Product Listing via Website Items
- removed redundant code
- Moved all website logic from Item to Website Item
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 2c96749..c5b8b54 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -293,7 +293,7 @@
if not status:
return
- shopping_cart_settings = frappe.get_doc("Shopping Cart Settings")
+ shopping_cart_settings = frappe.get_doc("E Commerce Settings")
if status in ["Authorized", "Completed"]:
redirect_to = None
@@ -443,7 +443,7 @@
return get_payment_gateway_account(args.get("payment_gateway_account"))
if args.order_type == "Shopping Cart":
- payment_gateway_account = frappe.get_doc("Shopping Cart Settings").payment_gateway_account
+ payment_gateway_account = frappe.get_doc("E Commerce Settings").payment_gateway_account
return get_payment_gateway_account(payment_gateway_account)
gateway_account = get_payment_gateway_account({"is_default": 1})
diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py
index 150498d..9a63dfe 100644
--- a/erpnext/accounts/doctype/tax_rule/tax_rule.py
+++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py
@@ -102,7 +102,7 @@
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('Shopping Cart Settings', 'enabled'))
+ 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
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
index d970f04..131a5e4 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js
@@ -1,13 +1,34 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('E Commerce Settings', {
+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) {
- frappe.model.with_doctype('Item', () => {
+ 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("Item", () => {
const item_meta = frappe.get_meta('Item');
const valid_fields = item_meta.fields.filter(
- df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden
+ df => ["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden
).map(df => ({ label: df.label, value: df.fieldname }));
frm.fields_dict.filter_fields.grid.update_docfield_property(
@@ -17,5 +38,16 @@
'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', '');
+ }
}
});
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
index 1a45adf..b1b1cae 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
@@ -10,6 +10,30 @@
"hide_variants",
"column_break_4",
"products_per_page",
+ "display_settings_section",
+ "show_attachments",
+ "show_price",
+ "show_stock_availability",
+ "enable_variants",
+ "column_break_13",
+ "show_contact_us_button",
+ "show_quantity_in_website",
+ "show_apply_coupon_code_in_website",
+ "allow_items_not_in_stock",
+ "section_break_18",
+ "company",
+ "price_list",
+ "enabled",
+ "store_page_docs",
+ "column_break_21",
+ "default_customer_group",
+ "quotation_series",
+ "checkout_settings_section",
+ "enable_checkout",
+ "save_quotations_as_draft",
+ "column_break_27",
+ "payment_gateway_account",
+ "payment_success_url",
"filter_categories_section",
"enable_field_filters",
"filter_fields",
@@ -37,6 +61,7 @@
"label": "Products per Page"
},
{
+ "collapsible": 1,
"fieldname": "filter_categories_section",
"fieldtype": "Section Break",
"label": "Filters"
@@ -76,12 +101,169 @@
"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 Variants"
+ },
+ {
+ "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"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-02-10 19:22:47.154104",
+ "modified": "2021-02-11 18:22:14.556880",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "E Commerce Settings",
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
index 90596d6..3fabc1a 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
@@ -2,15 +2,18 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
-from __future__ import unicode_literals
-
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint
+from frappe.utils import get_datetime, get_datetime_str, now_datetime
+class ShoppingCartSetupError(frappe.ValidationError): pass
class ECommerceSettings(Document):
+ def onload(self):
+ self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series")
+
def validate(self):
if self.home_page_is_products:
frappe.db.set_value("Website Settings", None, "home_page", "products")
@@ -19,6 +22,9 @@
self.validate_field_filters()
self.validate_attribute_filters()
+ if self.enabled:
+ self.validate_exchange_rates_exist()
+
frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings")
def validate_field_filters(self):
@@ -37,6 +43,79 @@
# if attribute filters are enabled, hide_variants should be disabled
self.hide_variants = 0
+ def validate_exchange_rates_exist(self):
+ """check if exchange rates exist for all Price List currencies (to company's currency)"""
+ company_currency = frappe.get_cached_value('Company', self.company, "default_currency")
+ if not company_currency:
+ msgprint(_("Please specify currency in Company") + ": " + self.company,
+ raise_exception=ShoppingCartSetupError)
+
+ price_list_currency_map = frappe.db.get_values("Price List",
+ [self.price_list], "currency")
+
+ price_list_currency_map = dict(price_list_currency_map)
+
+ # check if all price lists have a currency
+ for price_list, currency in price_list_currency_map.items():
+ if not currency:
+ frappe.throw(_("Currency is required for Price List {0}").format(price_list))
+
+ expected_to_exist = [currency + "-" + company_currency
+ for currency in price_list_currency_map.values()
+ if currency != company_currency]
+
+ # manqala 20/09/2016: set up selection parameters for query from tabCurrency Exchange
+ from_currency = [currency for currency in price_list_currency_map.values() if currency != company_currency]
+ to_currency = company_currency
+ # manqala end
+
+ if expected_to_exist:
+ # manqala 20/09/2016: modify query so that it uses date in the selection from Currency Exchange.
+ # exchange rates defined with date less than the date on which this document is being saved will be selected
+ exists = frappe.db.sql_list("""select CONCAT(from_currency,'-',to_currency) from `tabCurrency Exchange`
+ where from_currency in (%s) and to_currency = "%s" and date <= curdate()""" % (", ".join(["%s"]*len(from_currency)), to_currency), tuple(from_currency))
+ # manqala end
+
+ missing = list(set(expected_to_exist).difference(exists))
+
+ if missing:
+ msgprint(_("Missing Currency Exchange Rates for {0}").format(comma_and(missing)),
+ raise_exception=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 validate_cart_settings(doc, method):
+ frappe.get_doc("E Commerce Settings", "E Commerce Settings").run_method("validate")
+
+def get_shopping_cart_settings():
+ if not getattr(frappe.local, "shopping_cart_settings", None):
+ frappe.local.shopping_cart_settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
+
+ return frappe.local.shopping_cart_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
def home_page_is_products(doc, method):
"""Called on saving Website Settings."""
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
index cf23266..798529b 100644
--- 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
@@ -2,9 +2,40 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
-
-# import frappe
+import frappe
import unittest
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import ShoppingCartSetupError
+
class TestECommerceSettings(unittest.TestCase):
- pass
+ def setUp(self):
+ frappe.db.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """)
+
+ def get_cart_settings(self):
+ return frappe.get_doc({"doctype": "E Commerce Settings",
+ "company": "_Test Company"})
+
+ def test_exchange_rate_exists(self):
+ frappe.db.sql("""delete from `tabCurrency Exchange`""")
+
+ cart_settings = self.get_cart_settings()
+ cart_settings.price_list = "_Test Price List Rest of the World"
+ self.assertRaises(ShoppingCartSetupError, cart_settings.validate_exchange_rates_exist)
+
+ from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records as \
+ currency_exchange_records
+ frappe.get_doc(currency_exchange_records[0]).insert()
+ cart_settings.validate_exchange_rates_exist()
+
+ def test_tax_rule_validation(self):
+ frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
+ frappe.db.commit()
+
+ cart_settings = self.get_cart_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")
+
+test_dependencies = ["Tax Rule"]
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json
index 85a83e6..02717ea 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.json
+++ b/erpnext/e_commerce/doctype/website_item/website_item.json
@@ -2,12 +2,13 @@
"actions": [],
"allow_guest_to_view": 1,
"allow_import": 1,
- "autoname": "field:item_code",
+ "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",
@@ -28,6 +29,7 @@
"thumbnail",
"section_break_17",
"website_warehouse",
+ "description",
"website_specifications",
"copy_from_item_group",
"column_break_27",
@@ -60,8 +62,8 @@
"fieldtype": "Link",
"label": "Item Code",
"options": "Item",
- "reqd": 1,
- "unique": 1
+ "read_only_depends_on": "eval:!doc.__islocal",
+ "reqd": 1
},
{
"fetch_from": "item_code.item_name",
@@ -246,13 +248,30 @@
{
"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
}
],
"has_web_view": 1,
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-02-10 14:22:41.628232",
+ "modified": "2021-02-12 16:49:42.275517",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "Website Item",
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py
index 1e0b12b..55436f2 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.py
+++ b/erpnext/e_commerce/doctype/website_item/website_item.py
@@ -5,13 +5,17 @@
from __future__ import unicode_literals
import frappe
import json
+import itertools
+from frappe import _
+from six import iteritems
from frappe.website.doctype.website_slideshow.website_slideshow import \
get_slideshow
from frappe.website.render import clear_cache
from frappe.website.website_generator import WebsiteGenerator
+from frappe.utils import cstr, random_string, cint
-from frappe.utils import cstr, random_string
+from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
class WebsiteItem(WebsiteGenerator):
website = frappe._dict(
@@ -30,13 +34,23 @@
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)
+ def on_update(self):
+ self.update_template_item()
+
def on_trash(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
@@ -48,6 +62,18 @@
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):
+ """Set Show in Website for Template Item if True for its Variant"""
+ if self.variant_of:
+ if self.published:
+ # show template
+ template_item = frappe.get_doc("Item", self.variant_of)
+
+ if not template_item.published:
+ template_item.published = 1
+ template_item.flags.ignore_permissions = True
+ template_item.save()
+
def validate_website_image(self):
if frappe.flags.in_import:
return
@@ -133,6 +159,164 @@
self.thumbnail = file_doc.thumbnail_url
+ def get_context(self, context):
+ print(context)
+ context.show_search = True
+ context.search_link = '/search'
+
+ context.parents = get_parent_item_groups(self.item_group)
+ context.body_class = "product-page"
+ self.attributes = frappe.get_all("Item Variant Attribute",
+ fields=["attribute", "attribute_value"],
+ filters={"parent": self.item_code})
+ self.set_variant_context(context)
+ self.set_attribute_context(context)
+ self.set_disabled_attributes(context)
+ self.set_metatags(context)
+ self.set_shopping_cart_data(context)
+ print("IN WEB ITEM")
+
+ return context
+
+ def set_variant_context(self, context):
+ if self.has_variants:
+ context.no_cache = True
+
+ # load variants
+ # also used in set_attribute_context
+ context.variants = frappe.get_all("Item",
+ filters={"variant_of": self.name, "show_variant_in_website": 1},
+ order_by="name asc")
+
+ variant = frappe.form_dict.variant
+ if not variant and context.variants:
+ # the case when the item is opened for the first time from its list
+ variant = context.variants[0]
+
+ if variant:
+ context.variant = frappe.get_doc("Item", variant)
+
+ for fieldname in ("website_image", "website_image_alt", "web_long_description", "description",
+ "website_specifications"):
+ if context.variant.get(fieldname):
+ value = context.variant.get(fieldname)
+ if isinstance(value, list):
+ value = [d.as_dict() for d in value]
+
+ context[fieldname] = value
+
+ def set_attribute_context(self, context):
+ if self.has_variants:
+ attribute_values_available = {}
+ context.attribute_values = {}
+ context.selected_attributes = {}
+
+ # load attributes
+ for v in context.variants:
+ v.attributes = frappe.get_all("Item Variant Attribute",
+ fields=["attribute", "attribute_value"],
+ filters={"parent": v.name})
+ # make a map for easier access in templates
+ v.attribute_map = frappe._dict({})
+ for attr in v.attributes:
+ v.attribute_map[attr.attribute] = attr.attribute_value
+
+ for attr in v.attributes:
+ values = attribute_values_available.setdefault(attr.attribute, [])
+ if attr.attribute_value not in values:
+ values.append(attr.attribute_value)
+
+ if v.name == context.variant.name:
+ context.selected_attributes[attr.attribute] = attr.attribute_value
+
+ # filter attributes, order based on attribute table
+ for attr in self.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)
+
+ context.variant_info = json.dumps(context.variants)
+
+ def set_disabled_attributes(self, context):
+ """Disable selection options of attribute combinations that do not result in a variant"""
+
+ if not self.attributes or not self.has_variants:
+ return
+
+ context.disabled_attributes = {}
+ attributes = [attr.attribute for attr in self.attributes]
+
+ def find_variant(combination):
+ for variant in context.variants:
+ if len(variant.attributes) < len(attributes):
+ continue
+
+ if "combination" not in variant:
+ ref_combination = []
+
+ for attr in variant.attributes:
+ idx = attributes.index(attr.attribute)
+ ref_combination.insert(idx, attr.attribute_value)
+
+ variant["combination"] = ref_combination
+
+ if not (set(combination) - set(variant["combination"])):
+ # check if the combination is a subset of a variant combination
+ # eg. [Blue, 0.5] is a possible combination if exists [Blue, Large, 0.5]
+ return True
+
+ for i, attr in enumerate(self.attributes):
+ if i == 0:
+ continue
+
+ combination_source = []
+
+ # loop through previous attributes
+ for prev_attr in self.attributes[:i]:
+ combination_source.append([context.selected_attributes.get(prev_attr.attribute)])
+
+ combination_source.append(context.attribute_values[attr.attribute])
+
+ for combination in itertools.product(*combination_source):
+ if not find_variant(combination):
+ context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
+
+ 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.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.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 make_website_item(doc):
if not doc:
@@ -141,13 +325,13 @@
if frappe.db.exists("Website Item", {"item_code": doc.get("item_code")}):
message = _("Website Item already exists against {0}").format(frappe.bold(doc.get("item_code")))
- frappe.throw(message, title=_("Already Published"), indicator="blue")
+ 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", "image",
- "has_variants", "variant_of"]
+ "has_variants", "variant_of", "description"]
for field in fields_to_map:
website_item.update({field: doc.get(field)})
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 684b13a..d0142d3 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -240,8 +240,8 @@
"erpnext.support.doctype.issue.issue.set_first_response_time"
]
},
- "Sales Taxes and Charges Template": {
- "on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
+ ("Sales Taxes and Charges Template", "Price List"): {
+ "on_update": "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.validate_cart_settings"
},
"Website Settings": {
"validate": "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.home_page_is_products"
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index b0c5c37..0de9729 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -5,166 +5,6 @@
from erpnext.setup.doctype.item_group.item_group import get_child_groups
from erpnext.shopping_cart.product_info import get_product_info_for_website
-
-def get_field_filter_data():
- e_commerce_settings = get_e_commerce_settings()
- filter_fields = [row.fieldname for row in e_commerce_settings.filter_fields]
-
- meta = frappe.get_meta('Item')
- fields = [df for df in meta.fields if df.fieldname in filter_fields]
-
- filter_data = []
- for f in fields:
- doctype = f.get_link_doctype()
-
- # apply enable/disable/show_in_website filter
- meta = frappe.get_meta(doctype)
- 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
-
- values = [d.name for d in frappe.get_all(doctype, filters)]
- filter_data.append([f, values])
-
- return filter_data
-
-
-def get_attribute_filter_data():
- e_commerce_settings = get_e_commerce_settings()
- attributes = [row.attribute for row in e_commerce_settings.filter_attributes]
- attribute_docs = [
- frappe.get_doc('Item Attribute', attribute) for attribute in attributes
- ]
-
- # mark attribute values as checked if they are present in the request url
- if frappe.form_dict:
- for attr in attribute_docs:
- if attr.name in frappe.form_dict:
- value = frappe.form_dict[attr.name]
- if value:
- enabled_values = value.split(',')
- else:
- enabled_values = []
-
- for v in enabled_values:
- for item_attribute_row in attr.item_attribute_values:
- if v == item_attribute_row.attribute_value:
- item_attribute_row.checked = True
-
- return attribute_docs
-
-
-def get_products_for_website(field_filters=None, attribute_filters=None, search=None):
- if attribute_filters:
- item_codes = get_item_codes_by_attributes(attribute_filters)
- items_by_attributes = get_items([['name', 'in', item_codes]])
-
- if field_filters:
- items_by_fields = get_items_by_fields(field_filters)
-
- if attribute_filters and not field_filters:
- return items_by_attributes
-
- if field_filters and not attribute_filters:
- return items_by_fields
-
- if field_filters and attribute_filters:
- items_intersection = []
- item_codes_in_attribute = [item.name for item in items_by_attributes]
-
- for item in items_by_fields:
- if item.name in item_codes_in_attribute:
- items_intersection.append(item)
-
- return items_intersection
-
- if search:
- return get_items(search=search)
-
- return get_items()
-
-
-@frappe.whitelist(allow_guest=True)
-def get_products_html_for_website(field_filters=None, attribute_filters=None):
- field_filters = frappe.parse_json(field_filters)
- attribute_filters = frappe.parse_json(attribute_filters)
- set_item_group_filters(field_filters)
-
- items = get_products_for_website(field_filters, attribute_filters)
- html = ''.join(get_html_for_items(items))
-
- if not items:
- html = frappe.render_template('erpnext/www/all-products/not_found.html', {})
-
- return html
-
-def set_item_group_filters(field_filters):
- if field_filters is not None and 'item_group' in field_filters:
- field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])]
-
-
-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)])
- 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.
@@ -278,140 +118,6 @@
return set.intersection(*items)
-
-def get_items_by_fields(field_filters):
- meta = frappe.get_meta('Item')
- filters = []
- for fieldname, values in field_filters.items():
- if not values: continue
-
- _doctype = 'Item'
- _fieldname = fieldname
-
- df = meta.get_field(fieldname)
- if df.fieldtype == 'Table MultiSelect':
- child_doctype = df.options
- child_meta = frappe.get_meta(child_doctype)
- fields = child_meta.get("fields", { "fieldtype": "Link", "in_list_view": 1 })
- if fields:
- _doctype = child_doctype
- _fieldname = fields[0].fieldname
-
- if len(values) == 1:
- filters.append([_doctype, _fieldname, '=', values[0]])
- else:
- filters.append([_doctype, _fieldname, 'in', values])
-
- return get_items(filters)
-
-
-def get_items(filters=None, search=None):
- start = frappe.form_dict.start or 0
- e_commerce_settings = get_e_commerce_settings()
- page_length = e_commerce_settings.products_per_page
-
- filters = filters or []
- # convert to list of filters
- if isinstance(filters, dict):
- filters = [['Item', fieldname, '=', value] for fieldname, value in filters.items()]
-
- enabled_items_filter = get_conditions({ 'disabled': 0 }, 'and')
-
- show_in_website_condition = ''
- if e_commerce_settings.hide_variants:
- show_in_website_condition = get_conditions({'show_in_website': 1 }, 'and')
- else:
- show_in_website_condition = get_conditions([
- ['show_in_website', '=', 1],
- ['show_variant_in_website', '=', 1]
- ], 'or')
-
- search_condition = ''
- if search:
- # Default fields to search from
- default_fields = {'name', 'item_name', 'description', 'item_group'}
-
- # Get meta search fields
- meta = frappe.get_meta("Item")
- meta_fields = set(meta.get_search_fields())
-
- # Join the meta fields and default fields set
- search_fields = default_fields.union(meta_fields)
- try:
- if frappe.db.count('Item', cache=True) > 50000:
- search_fields.remove('description')
- except KeyError:
- pass
-
- # Build or filters for query
- search = '%{}%'.format(search)
- or_filters = [[field, 'like', search] for field in search_fields]
-
- search_condition = get_conditions(or_filters, 'or')
-
- filter_condition = get_conditions(filters, 'and')
-
- where_conditions = ' and '.join(
- [condition for condition in [enabled_items_filter, show_in_website_condition, \
- search_condition, filter_condition] if condition]
- )
-
- left_joins = []
- for f in filters:
- if len(f) == 4 and f[0] != 'Item':
- left_joins.append(f[0])
-
- left_join = ' '.join(['LEFT JOIN `tab{0}` on (`tab{0}`.parent = `tabItem`.name)'.format(l) for l in left_joins])
-
- results = frappe.db.sql('''
- SELECT
- `tabItem`.`name`, `tabItem`.`item_name`, `tabItem`.`item_code`,
- `tabItem`.`website_image`, `tabItem`.`image`,
- `tabItem`.`web_long_description`, `tabItem`.`description`,
- `tabItem`.`route`, `tabItem`.`item_group`
- FROM
- `tabItem`
- {left_join}
- WHERE
- {where_conditions}
- GROUP BY
- `tabItem`.`name`
- ORDER BY
- `tabItem`.`weightage` DESC
- LIMIT
- {page_length}
- OFFSET
- {start}
- '''.format(
- where_conditions=where_conditions,
- start=start,
- page_length=page_length,
- left_join=left_join
- )
- , as_dict=1)
-
- for r in results:
- r.description = r.web_long_description or r.description
- r.image = r.website_image or r.image
- product_info = get_product_info_for_website(r.item_code, skip_quotation_creation=True).get('product_info')
- if product_info:
- r.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None
-
- return results
-
-
-def get_conditions(filter_list, and_or='and'):
- from frappe.model.db_query import DatabaseQuery
-
- if not filter_list:
- return ''
-
- conditions = []
- DatabaseQuery('Item').build_filter_conditions(filter_list, conditions, ignore_permissions=True)
- join_by = ' {0} '.format(and_or)
-
- return '(' + join_by.join(conditions) + ')'
-
# utilities
def get_item_attributes(item_code):
diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py
index bae8f35..3ee2c88 100644
--- a/erpnext/portal/utils.py
+++ b/erpnext/portal/utils.py
@@ -1,10 +1,8 @@
-from __future__ import unicode_literals
-
import frappe
from frappe.utils.nestedset import get_root_of
from erpnext.shopping_cart.cart import get_debtors_account
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
get_shopping_cart_settings,
)
diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js
index 6a923ae..227881a 100644
--- a/erpnext/public/js/shopping_cart.js
+++ b/erpnext/public/js/shopping_cart.js
@@ -180,7 +180,7 @@
show_cart_navbar: function () {
frappe.call({
- method: "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.is_cart_enabled",
+ 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);
}
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 99c43bf..e9644cc 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -274,7 +274,7 @@
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("Shopping Cart Settings", None,
+ customer.customer_group = frappe.db.get_value("E Commerce Settings", None,
"default_customer_group")
try:
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 2f03ee7..3965d82 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -120,9 +120,8 @@
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.theme or "Light"
- values[f"slide_{index + 1}_content_align"] = slide.content_align or "Centre"
- values[f"slide_{index + 1}_primary_action_label"] = slide.label
+ 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
@@ -175,7 +174,7 @@
data = frappe.db.sql(query, {"product_group": product_group,"search": search, "today": nowdate()}, as_dict=1)
data = adjust_qty_for_expired_items(data)
- if cint(frappe.db.get_single_value("Shopping Cart Settings", "enabled")):
+ if cint(frappe.db.get_single_value("E Commerce Settings", "enabled")):
for item in data:
set_product_info_for_website(item)
diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py
index bea3906..be94994 100644
--- a/erpnext/setup/setup_wizard/operations/company_setup.py
+++ b/erpnext/setup/setup_wizard/operations/company_setup.py
@@ -33,7 +33,7 @@
def enable_shopping_cart(args):
# Needs price_lists
frappe.get_doc({
- "doctype": "Shopping Cart Settings",
+ "doctype": "E Commerce Settings",
"enabled": 1,
'company': args.get('company_name') ,
'price_list': frappe.db.get_value("Price List", {"selling": 1}),
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index c473395..fbfcb10 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -534,7 +534,7 @@
pass
def update_shopping_cart_settings(args):
- shopping_cart = frappe.get_doc("Shopping Cart Settings")
+ shopping_cart = frappe.get_doc("E Commerce Settings")
shopping_cart.update({
"enabled": 1,
'company': args.company_name,
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index 3f1dfde..cd9f1e8 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -1,8 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
-
import frappe
import frappe.defaults
from frappe import _, throw
@@ -11,10 +9,8 @@
from frappe.utils import cint, cstr, flt, get_fullname
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.accounts.utils import get_account_name
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
- get_shopping_cart_settings,
-)
from erpnext.utilities.product import get_qty_in_stock
@@ -22,7 +18,7 @@
pass
def set_cart_count(quotation=None):
- if cint(frappe.db.get_singles_value("Shopping Cart Settings", "enabled")):
+ if cint(frappe.db.get_singles_value("E Commerce Settings", "enabled")):
if not quotation:
quotation = _get_cart_quotation()
cart_count = cstr(len(quotation.get("items")))
@@ -49,7 +45,7 @@
"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("Shopping Cart Settings")
+ "cart_settings": frappe.get_cached_doc("E Commerce Settings")
}
@frappe.whitelist()
@@ -73,7 +69,7 @@
@frappe.whitelist()
def place_order():
quotation = _get_cart_quotation()
- cart_settings = frappe.db.get_value("Shopping Cart Settings", None,
+ cart_settings = frappe.db.get_value("E Commerce Settings", None,
["company", "allow_items_not_in_stock"], as_dict=1)
quotation.company = cart_settings.company
@@ -263,7 +259,7 @@
territory = frappe.db.get_value("Territory", geoip_country)
return territory or \
- frappe.db.get_value("Shopping Cart Settings", None, "territory") or \
+ frappe.db.get_value("E Commerce Settings", None, "territory") or \
get_root_of("Territory")
def decorate_quotation_doc(doc):
@@ -286,7 +282,7 @@
if quotation:
qdoc = frappe.get_doc("Quotation", quotation[0].name)
else:
- company = frappe.db.get_value("Shopping Cart Settings", None, ["company"])
+ 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-",
@@ -341,7 +337,7 @@
if not quotation:
quotation = _get_cart_quotation(party)
- cart_settings = frappe.get_doc("Shopping Cart Settings")
+ cart_settings = frappe.get_doc("E Commerce Settings")
set_price_list_and_rate(quotation, cart_settings)
@@ -418,7 +414,7 @@
party_doctype = contact.links[0].link_doctype
party = contact.links[0].link_name
- cart_settings = frappe.get_doc("Shopping Cart Settings")
+ cart_settings = frappe.get_doc("E Commerce Settings")
debtors_account = ''
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/__init__.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/__init__.py
+++ /dev/null
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
deleted file mode 100644
index b38828e..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.ui.form.on("Shopping Cart 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>`
- );
- }
- },
- 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', '');
- }
- }
-});
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
deleted file mode 100644
index 7a4bb20..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
+++ /dev/null
@@ -1,212 +0,0 @@
-{
- "actions": [],
- "creation": "2013-06-19 15:57:32",
- "description": "Default settings for Shopping Cart",
- "doctype": "DocType",
- "document_type": "System",
- "engine": "InnoDB",
- "field_order": [
- "enabled",
- "store_page_docs",
- "display_settings",
- "show_attachments",
- "show_price",
- "show_stock_availability",
- "enable_variants",
- "column_break_7",
- "show_contact_us_button",
- "show_quantity_in_website",
- "show_apply_coupon_code_in_website",
- "allow_items_not_in_stock",
- "section_break_2",
- "company",
- "price_list",
- "column_break_4",
- "default_customer_group",
- "quotation_series",
- "section_break_8",
- "enable_checkout",
- "save_quotations_as_draft",
- "column_break_11",
- "payment_gateway_account",
- "payment_success_url"
- ],
- "fields": [
- {
- "default": "0",
- "fieldname": "enabled",
- "fieldtype": "Check",
- "in_list_view": 1,
- "label": "Enable Shopping Cart"
- },
- {
- "fieldname": "display_settings",
- "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": "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"
- },
- {
- "depends_on": "enabled",
- "fieldname": "section_break_2",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "company",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Company",
- "mandatory_depends_on": "eval: doc.enabled === 1",
- "options": "Company",
- "remember_last_selected_value": 1
- },
- {
- "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_4",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "default_customer_group",
- "fieldtype": "Link",
- "ignore_user_permissions": 1,
- "label": "Default Customer Group",
- "mandatory_depends_on": "eval: doc.enabled === 1",
- "options": "Customer Group"
- },
- {
- "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": "section_break_8",
- "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_11",
- "fieldtype": "Column Break"
- },
- {
- "depends_on": "enable_checkout",
- "fieldname": "payment_gateway_account",
- "fieldtype": "Link",
- "label": "Payment Gateway Account",
- "mandatory_depends_on": "enable_checkout",
- "options": "Payment Gateway Account"
- },
- {
- "fieldname": "column_break_7",
- "fieldtype": "Column Break"
- },
- {
- "default": "0",
- "fieldname": "enable_variants",
- "fieldtype": "Check",
- "label": "Enable Variants"
- },
- {
- "default": "0",
- "depends_on": "eval: doc.enable_checkout == 0",
- "fieldname": "save_quotations_as_draft",
- "fieldtype": "Check",
- "label": "Save Quotations as Draft"
- },
- {
- "depends_on": "doc.enabled",
- "fieldname": "store_page_docs",
- "fieldtype": "HTML"
- }
- ],
- "icon": "fa fa-shopping-cart",
- "idx": 1,
- "issingle": 1,
- "links": [],
- "modified": "2021-03-02 17:34:57.642565",
- "modified_by": "Administrator",
- "module": "Shopping Cart",
- "name": "Shopping Cart Settings",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "email": 1,
- "print": 1,
- "read": 1,
- "role": "Website Manager",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "ASC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
deleted file mode 100644
index 8f4afda..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from frappe.utils import flt
-
-
-class ShoppingCartSetupError(frappe.ValidationError): pass
-
-class ShoppingCartSettings(Document):
- def onload(self):
- self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series")
-
- def validate(self):
- if self.enabled:
- self.validate_price_list_exchange_rate()
-
- 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 validate_cart_settings(doc=None, method=None):
- frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings").run_method("validate")
-
-def get_shopping_cart_settings():
- if not getattr(frappe.local, "shopping_cart_settings", None):
- frappe.local.shopping_cart_settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings")
-
- return frappe.local.shopping_cart_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/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py
deleted file mode 100644
index f8a22b0..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-
-import unittest
-
-import frappe
-
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
- ShoppingCartSetupError,
-)
-
-
-class TestShoppingCartSettings(unittest.TestCase):
- def setUp(self):
- frappe.db.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """)
-
- def get_cart_settings(self):
- return frappe.get_doc({"doctype": "Shopping Cart Settings",
- "company": "_Test Company"})
-
- # NOTE: Exchangrate API has all enabled currencies that ERPNext supports.
- # We aren't checking just currency exchange record anymore
- # while validating price list currency exchange rate to that of company.
- # The API is being used to fetch the rate which again almost always
- # gives back a valid value (for valid currencies).
- # This makes the test obsolete.
- # Commenting because im not sure if there's a better test we can write
-
- # def test_exchange_rate_exists(self):
- # frappe.db.sql("""delete from `tabCurrency Exchange`""")
-
- # cart_settings = self.get_cart_settings()
- # cart_settings.price_list = "_Test Price List Rest of the World"
- # self.assertRaises(ShoppingCartSetupError, cart_settings.validate_price_list_exchange_rate)
-
- # from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records as \
- # currency_exchange_records
- # frappe.get_doc(currency_exchange_records[0]).insert()
- # cart_settings.validate_price_list_exchange_rate()
-
- def test_tax_rule_validation(self):
- frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
- frappe.db.commit()
-
- cart_settings = self.get_cart_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")
-
-test_dependencies = ["Tax Rule"]
diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py
index fa68636..dd77536 100644
--- a/erpnext/shopping_cart/product_info.py
+++ b/erpnext/shopping_cart/product_info.py
@@ -1,17 +1,14 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
-
import frappe
from erpnext.shopping_cart.cart import _get_cart_quotation, _set_price_list
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
get_shopping_cart_settings,
- show_quantity_in_website,
+ show_quantity_in_website
)
-from erpnext.utilities.product import get_non_stock_item_status, get_price, get_qty_in_stock
-
+from erpnext.utilities.product import get_price, get_qty_in_stock, get_non_stock_item_status
@frappe.whitelist(allow_guest=True)
def get_product_info_for_website(item_code, skip_quotation_creation=False):
diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py
index d105ab8..140e1c6 100644
--- a/erpnext/shopping_cart/product_query.py
+++ b/erpnext/shopping_cart/product_query.py
@@ -10,26 +10,22 @@
"""Query engine for product listing
Attributes:
- cart_settings (Document): Settings for Cart
fields (list): Fields to fetch in query
- filters (TYPE): Description
- or_filters (list): Description
+ 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
- filters (list)
- or_filters (list)
"""
def __init__(self):
self.settings = frappe.get_doc("E Commerce Settings")
- self.cart_settings = frappe.get_doc("Shopping Cart Settings")
self.page_length = self.settings.products_per_page or 20
- self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants',
- 'item_group', 'image', 'web_long_description', 'description', 'route', 'weightage']
- self.filters = []
- self.or_filters = [['show_in_website', '=', 1]]
- if not self.settings.get('hide_variants'):
- self.or_filters.append(['show_variant_in_website', '=', 1])
+ self.fields = ['wi.name', 'wi.item_name', 'wi.item_code', 'wi.website_image', 'wi.variant_of',
+ 'wi.has_variants', 'wi.item_group', 'wi.image', 'wi.web_long_description', 'wi.description',
+ 'wi.route']
+ self.conditions = ""
+ self.or_conditions = ""
+ self.substitutions = []
def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
"""Summary
@@ -57,51 +53,14 @@
filters=[["Website Item Group", "item_group", "=", item_group]]
)
+ self.query_fields = (", ").join(self.fields)
if attributes:
- all_items = []
- for attribute, values in attributes.items():
- if not isinstance(values, list):
- values = [values]
-
- items = frappe.get_all(
- "Item",
- fields=self.fields,
- filters=[
- *self.filters,
- ["Item Variant Attribute", "attribute", "=", attribute],
- ["Item Variant Attribute", "attribute_value", "in", values],
- ],
- or_filters=self.or_filters,
- start=start,
- limit=self.page_length,
- order_by="weightage desc"
- )
-
- items_dict = {item.name: item for item in items}
-
- all_items.append(set(items_dict.keys()))
-
- result = [items_dict.get(item) for item in list(set.intersection(*all_items))]
+ result = self.query_items_with_attributes(attributes, start)
else:
- result = frappe.get_all(
- "Item",
- fields=self.fields,
- filters=self.filters,
- or_filters=self.or_filters,
- start=start,
- limit=self.page_length,
- order_by="weightage desc"
- )
+ result = self.query_items(self.conditions, self.or_conditions,
+ self.substitutions, start=start)
- # Combine results having context of website item groups into item results
- if item_group and website_item_groups:
- items_list = {row.name for row in result}
- for row in website_item_groups:
- if row.wig_parent not in items_list:
- result.append(row)
-
- result = sorted(result, key=lambda x: x.get("weightage"), reverse=True)
-
+ # add price info in results
for item in result:
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
if product_info:
@@ -109,6 +68,51 @@
return result
+ def query_items(self, conditions, or_conditions, substitutions, start=0):
+ """Build a query to fetch Website Items based on field filters."""
+ return frappe.db.sql("""
+ select distinct {query_fields}
+ from
+ `tabWebsite Item` wi, `tabItem Variant Attribute` iva
+ where
+ wi.published = 1
+ {conditions}
+ {or_conditions}
+ limit {limit} offset {start}
+ """.format(
+ query_fields=self.query_fields,
+ conditions=conditions,
+ or_conditions=or_conditions,
+ limit=self.page_length,
+ start=start),
+ tuple(substitutions),
+ as_dict=1)
+
+ def query_items_with_attributes(self, attributes, start=0):
+ """Build a query to fetch Website Items based on field & attribute filters."""
+ all_items = []
+ self.conditions += " and iva.parent = wi.item_code"
+
+ for attribute, values in attributes.items():
+ if not isinstance(values, list): values = [values]
+
+ conditions_copy = self.conditions
+ substitutions_copy = self.substitutions.copy()
+
+ conditions_copy += " and iva.attribute = '{0}' and iva.attribute_value in ({1})" \
+ .format(attribute, (", ").join(['%s'] * len(values)))
+ substitutions_copy.extend(values)
+
+ items = self.query_items(conditions_copy, self.or_conditions, substitutions_copy, start=start)
+
+ items_dict = {item.name: item for item in items}
+ # TODO: Replace Variants by their parent templates
+
+ all_items.append(set(items_dict.keys()))
+
+ result = [items_dict.get(item) for item in list(set.intersection(*all_items))]
+ return result
+
def build_fields_filters(self, filters):
"""Build filters for field values
@@ -130,10 +134,11 @@
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])
+ self.conditions += " and wi.{0} in ({1})".format(field, (', ').join(['%s'] * len(values)))
+ self.substitutions.extend(values)
else:
# `=` will be faster than `IN` for most cases
- self.filters.append([field, '=', values])
+ self.conditions += " and wi.{0} = '{1}'".format(field, values)
def build_search_filters(self, search_term):
"""Query search term in specified fields
@@ -158,4 +163,5 @@
# Build or filters for query
search = '%{}%'.format(search_term)
- self.or_filters += [[field, 'like', search] for field in search_fields]
+ for field in search_fields:
+ self.or_conditions += " or {0} like '{1}'".format(field, search)
diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/shopping_cart/test_shopping_cart.py
index d1284cd..63166c6 100644
--- a/erpnext/shopping_cart/test_shopping_cart.py
+++ b/erpnext/shopping_cart/test_shopping_cart.py
@@ -167,7 +167,7 @@
# helper functions
def enable_shopping_cart(self):
- settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings")
+ settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
settings.update({
"enabled": 1,
@@ -197,7 +197,7 @@
frappe.local.shopping_cart_settings = None
def disable_shopping_cart(self):
- settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings")
+ settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
settings.enabled = 0
settings.save()
frappe.local.shopping_cart_settings = None
diff --git a/erpnext/shopping_cart/utils.py b/erpnext/shopping_cart/utils.py
index f412e61..98b5229 100644
--- a/erpnext/shopping_cart/utils.py
+++ b/erpnext/shopping_cart/utils.py
@@ -1,14 +1,9 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
-
import frappe
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
- is_cart_enabled,
-)
-
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import is_cart_enabled
def show_cart_count():
if (is_cart_enabled() and
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 8cc9f74..2de4689 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -21,7 +21,6 @@
strip,
)
from frappe.utils.html_utils import clean_html
-from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
from frappe.website.utils import clear_cache
from frappe.website.website_generator import WebsiteGenerator
@@ -131,8 +130,6 @@
self.validate_attributes()
self.validate_variant_attributes()
self.validate_variant_based_on_change()
- self.validate_website_image()
- self.make_thumbnail()
self.validate_fixed_asset()
self.validate_retain_sample()
self.validate_uom_conversion_factor()
@@ -141,7 +138,6 @@
self.validate_item_defaults()
self.validate_auto_reorder_enabled_in_stock_settings()
self.cant_change()
- self.update_show_in_website()
self.validate_item_tax_net_rate_range()
set_item_tax_from_hsn_code(self)
@@ -156,7 +152,6 @@
self.validate_name_with_item_group()
self.update_variants()
self.update_item_price()
- self.update_template_item()
def validate_description(self):
'''Clean HTML description if set'''
@@ -218,95 +213,6 @@
stock_entry.add_comment("Comment", _("Opening Stock"))
- def make_route(self):
- if not self.route:
- return cstr(frappe.db.get_value('Item Group', self.item_group,
- 'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5))
-
- def validate_website_image(self):
- if frappe.flags.in_import:
- return
-
- """Validate if the website image is a public file"""
- auto_set_website_image = False
- if not self.website_image and self.image:
- auto_set_website_image = True
- self.website_image = self.image
-
- if not self.website_image:
- return
-
- # find if website image url exists as public
- file_doc = frappe.get_all("File", filters={
- "file_url": self.website_image
- }, fields=["name", "is_private"], order_by="is_private asc", limit_page_length=1)
-
- if file_doc:
- file_doc = file_doc[0]
-
- if not file_doc:
- if not auto_set_website_image:
- frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found").format(self.website_image, self.name))
-
- self.website_image = None
-
- elif file_doc.is_private:
- if not auto_set_website_image:
- frappe.msgprint(_("Website Image should be a public file or website URL"))
-
- self.website_image = None
-
- def make_thumbnail(self):
- if frappe.flags.in_import:
- return
-
- """Make a thumbnail of `website_image`"""
- import requests.exceptions
-
- if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
- self.thumbnail = None
-
- if self.website_image and not self.thumbnail:
- file_doc = None
-
- try:
- file_doc = frappe.get_doc("File", {
- "file_url": self.website_image,
- "attached_to_doctype": "Item",
- "attached_to_name": self.name
- })
- except frappe.DoesNotExistError:
- # cleanup
- frappe.local.message_log.pop()
-
- except requests.exceptions.HTTPError:
- frappe.msgprint(_("Warning: Invalid attachment {0}").format(self.website_image))
- self.website_image = None
-
- except requests.exceptions.SSLError:
- frappe.msgprint(
- _("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
- self.website_image = None
-
- # for CSV import
- if self.website_image and not file_doc:
- try:
- file_doc = frappe.get_doc({
- "doctype": "File",
- "file_url": self.website_image,
- "attached_to_doctype": "Item",
- "attached_to_name": self.name
- }).save()
-
- except IOError:
- self.website_image = None
-
- if file_doc:
- if not file_doc.thumbnail_url:
- file_doc.make_thumbnail()
-
- self.thumbnail = file_doc.thumbnail_url
-
def validate_fixed_asset(self):
if self.is_fixed_asset:
if self.is_stock_item:
@@ -330,167 +236,6 @@
frappe.throw(_("{0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(
self.item_code))
- def get_context(self, context):
- context.show_search = True
- context.search_link = '/product_search'
-
- context.parents = get_parent_item_groups(self.item_group)
- context.body_class = "product-page"
-
- self.set_variant_context(context)
- self.set_attribute_context(context)
- self.set_disabled_attributes(context)
- self.set_metatags(context)
- self.set_shopping_cart_data(context)
-
- return context
-
- def set_variant_context(self, context):
- if self.has_variants:
- context.no_cache = True
-
- # load variants
- # also used in set_attribute_context
- context.variants = frappe.get_all("Item",
- filters={"variant_of": self.name, "show_variant_in_website": 1},
- order_by="name asc")
-
- variant = frappe.form_dict.variant
- if not variant and context.variants:
- # the case when the item is opened for the first time from its list
- variant = context.variants[0]
-
- if variant:
- context.variant = frappe.get_doc("Item", variant)
-
- for fieldname in ("website_image", "website_image_alt", "web_long_description", "description",
- "website_specifications"):
- if context.variant.get(fieldname):
- value = context.variant.get(fieldname)
- if isinstance(value, list):
- value = [d.as_dict() for d in value]
-
- context[fieldname] = value
-
- if self.slideshow:
- if context.variant and context.variant.slideshow:
- context.update(get_slideshow(context.variant))
- else:
- context.update(get_slideshow(self))
-
- def set_attribute_context(self, context):
- if not self.has_variants:
- return
-
- attribute_values_available = {}
- context.attribute_values = {}
- context.selected_attributes = {}
-
- # load attributes
- for v in context.variants:
- v.attributes = frappe.get_all("Item Variant Attribute",
- fields=["attribute", "attribute_value"],
- filters={"parent": v.name})
- # make a map for easier access in templates
- v.attribute_map = frappe._dict({})
- for attr in v.attributes:
- v.attribute_map[attr.attribute] = attr.attribute_value
-
- for attr in v.attributes:
- values = attribute_values_available.setdefault(attr.attribute, [])
- if attr.attribute_value not in values:
- values.append(attr.attribute_value)
-
- if v.name == context.variant.name:
- context.selected_attributes[attr.attribute] = attr.attribute_value
-
- # filter attributes, order based on attribute table
- for attr in self.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)
-
- context.variant_info = json.dumps(context.variants)
-
- def set_disabled_attributes(self, context):
- """Disable selection options of attribute combinations that do not result in a variant"""
- if not self.attributes or not self.has_variants:
- return
-
- context.disabled_attributes = {}
- attributes = [attr.attribute for attr in self.attributes]
-
- def find_variant(combination):
- for variant in context.variants:
- if len(variant.attributes) < len(attributes):
- continue
-
- if "combination" not in variant:
- ref_combination = []
-
- for attr in variant.attributes:
- idx = attributes.index(attr.attribute)
- ref_combination.insert(idx, attr.attribute_value)
-
- variant["combination"] = ref_combination
-
- if not (set(combination) - set(variant["combination"])):
- # check if the combination is a subset of a variant combination
- # eg. [Blue, 0.5] is a possible combination if exists [Blue, Large, 0.5]
- return True
-
- for i, attr in enumerate(self.attributes):
- if i == 0:
- continue
-
- combination_source = []
-
- # loop through previous attributes
- for prev_attr in self.attributes[:i]:
- combination_source.append([context.selected_attributes.get(prev_attr.attribute)])
-
- combination_source.append(context.attribute_values[attr.attribute])
-
- for combination in itertools.product(*combination_source):
- if not find_variant(combination):
- context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
-
- 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.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.shopping_cart.product_info import get_product_info_for_website
- context.shopping_cart = get_product_info_for_website(self.name, skip_quotation_creation=True)
-
def add_default_uom_in_conversion_factor_table(self):
uom_conv_list = [d.uom for d in self.get("uoms")]
if self.stock_uom not in uom_conv_list:
@@ -505,10 +250,6 @@
[self.remove(d) for d in to_remove]
- def update_show_in_website(self):
- if self.disabled:
- self.show_in_website = False
-
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):
@@ -678,7 +419,7 @@
if merge:
self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name)
- if self.route:
+ if self.published_in_website:
invalidate_cache_for_item(self)
clear_cache(self.route)
@@ -777,25 +518,6 @@
where item_code = %s and docstatus < 2
""", (self.description, self.name))
- def update_template_item(self):
- """Set Show in Website for Template Item if True for its Variant"""
- if not self.variant_of:
- return
-
- if self.show_in_website:
- self.show_variant_in_website = 1
- self.show_in_website = 0
-
- if self.show_variant_in_website:
- # show template
- template_item = frappe.get_doc("Item", self.variant_of)
-
- if not template_item.show_in_website:
- template_item.show_in_website = 1
- template_item.flags.dont_update_variants = True
- template_item.flags.ignore_permissions = True
- template_item.save()
-
def validate_item_defaults(self):
companies = {row.company for row in self.item_defaults}
@@ -1065,7 +787,6 @@
'item_code': item,
'item_name': item,
'description': item,
- 'show_in_website': 1,
'is_sales_item': 1,
'is_purchase_item': 1,
'is_stock_item': 1,
diff --git a/erpnext/templates/generators/item/item_image.html b/erpnext/templates/generators/item/item_image.html
index 39a30d0..e9b0916 100644
--- a/erpnext/templates/generators/item/item_image.html
+++ b/erpnext/templates/generators/item/item_image.html
@@ -23,7 +23,7 @@
})
</script>
{% else %}
- {{ product_image(website_image or image or 'no-image.jpg', alt=website_image_alt or item_name) }}
+ {{ product_image(doc.website_image or doc.image or 'no-image.jpg', alt=doc.website_image_alt or doc.item_name) }}
{% endif %}
<!-- Simple image preview -->
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 4482bc1..979298f 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -4,7 +4,7 @@
{% set select_address = True %}
{% endif %}
-{% set show_coupon_code = frappe.db.get_single_value('Shopping Cart Settings', 'show_apply_coupon_code_in_website') %}
+{% set show_coupon_code = frappe.db.get_single_value('E Commerce Settings', 'show_apply_coupon_code_in_website') %}
{% if show_coupon_code == 1%}
<div class="mb-3">
<div class="row no-gutters">
diff --git a/erpnext/templates/includes/products_as_list.html b/erpnext/templates/includes/products_as_list.html
index 9bf9fd9..976d614 100644
--- a/erpnext/templates/includes/products_as_list.html
+++ b/erpnext/templates/includes/products_as_list.html
@@ -1,4 +1,4 @@
-{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %}
+{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body, product_image_square %}
<a class="product-link product-list-link" href="{{ route|abs_url }}">
<div class='row'>
diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py
index d4e81ab..59df433 100644
--- a/erpnext/templates/pages/order.py
+++ b/erpnext/templates/pages/order.py
@@ -6,10 +6,7 @@
import frappe
from frappe import _
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
- show_attachments,
-)
-
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import show_attachments
def get_context(context):
context.no_cache = 1
@@ -26,7 +23,7 @@
context.payment_ref = frappe.db.get_value("Payment Request",
{"reference_name": frappe.form_dict.name}, "name")
- context.enabled_checkout = frappe.get_doc("Shopping Cart Settings").enable_checkout
+ 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), "value")
if default_print_format:
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index 1c641b5..37e07f4 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -129,7 +129,7 @@
Object.assign(field_filters, { item_group });
}
return new Promise((resolve, reject) => {
- frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args)
+ frappe.call('erpnext.www.all-products.index.get_products_html_for_website', args)
.then(r => {
if (r.exc) reject(r.exc);
else resolve(r.message);
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py
index 67d51ca..688c029 100644
--- a/erpnext/www/all-products/index.py
+++ b/erpnext/www/all-products/index.py
@@ -1,9 +1,7 @@
import frappe
-
-from erpnext.portal.product_configurator.utils import (get_products_for_website, get_e_commerce_settings,
- get_field_filter_data, get_attribute_filter_data)
-from erpnext.shopping_cart.filters import ProductFiltersBuilder
+from frappe.utils import cint
from erpnext.shopping_cart.product_query import ProductQuery
+from erpnext.shopping_cart.filters import ProductFiltersBuilder
sitemap = 1
@@ -13,7 +11,7 @@
search = frappe.form_dict.search
field_filters = frappe.parse_json(frappe.form_dict.field_filters)
attribute_filters = frappe.parse_json(frappe.form_dict.attribute_filters)
- start = frappe.parse_json(frappe.form_dict.start)
+ start = cint(frappe.parse_json(frappe.form_dict.start))
else:
search = field_filters = attribute_filters = None
start = 0
@@ -24,15 +22,34 @@
# Add homepage as parent
context.parents = [{"name": frappe._("Home"), "route":"/"}]
- e_commerce_settings = get_e_commerce_settings()
filter_engine = ProductFiltersBuilder()
context.field_filters = filter_engine.get_field_filters()
context.attribute_filters = filter_engine.get_attribute_filters()
- context.e_commerce_settings = e_commerce_settings
+ context.e_commerce_settings = engine.settings
context.body_class = "product-page"
- context.page_length = e_commerce_settings.products_per_page or 20
+ context.page_length = engine.settings.products_per_page or 20
context.no_cache = 1
- print(context)
+
+@frappe.whitelist(allow_guest=True)
+def get_products_html_for_website(field_filters=None, attribute_filters=None):
+ """Get Products on filter change."""
+ field_filters = frappe.parse_json(field_filters)
+ attribute_filters = frappe.parse_json(attribute_filters)
+
+ engine = ProductQuery()
+ items = engine.query(attribute_filters, field_filters, search_term=None, start=0)
+
+ item_html = []
+ for item in items:
+ item_html.append(frappe.render_template('erpnext/www/all-products/item_row.html', {
+ 'item': item
+ }))
+ html = ''.join(item_html)
+
+ if not items:
+ html = frappe.render_template('erpnext/www/all-products/not_found.html', {})
+
+ return html