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
This commit is contained in:
parent
939b0dd67d
commit
eef9cf152f
@ -293,7 +293,7 @@ class PaymentRequest(Document):
|
||||
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 @@ def get_gateway_details(args):
|
||||
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})
|
||||
|
@ -102,7 +102,7 @@ class TaxRule(Document):
|
||||
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
|
||||
|
@ -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 @@ frappe.ui.form.on('E Commerce Settings', {
|
||||
'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', '');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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 @@ class ECommerceSettings(Document):
|
||||
|
||||
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 @@ class ECommerceSettings(Document):
|
||||
# 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."""
|
||||
|
@ -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"]
|
||||
|
@ -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",
|
||||
|
@ -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 @@ class WebsiteItem(WebsiteGenerator):
|
||||
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 @@ class WebsiteItem(WebsiteGenerator):
|
||||
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 @@ class WebsiteItem(WebsiteGenerator):
|
||||
|
||||
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 @@ def make_website_item(doc):
|
||||
|
||||
if frappe.db.exists("Website Item", {"item_code": doc.get("item_code")}):
|
||||
message = _("Website Item already exists against {0}").format(frappe.bold(doc.get("item_code")))
|
||||
frappe.throw(message, title=_("Already Published"), indicator="blue")
|
||||
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)})
|
||||
|
||||
|
@ -240,8 +240,8 @@ doc_events = {
|
||||
"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"
|
||||
|
@ -5,166 +5,6 @@ from erpnext.portal.product_configurator.item_variants_cache import ItemVariants
|
||||
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 @@ def get_items_with_selected_attributes(item_code, selected_attributes):
|
||||
|
||||
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):
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
@ -180,7 +180,7 @@ $.extend(shopping_cart, {
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -274,7 +274,7 @@ def _make_customer(source_name, ignore_permissions=False):
|
||||
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:
|
||||
|
@ -120,9 +120,8 @@ class ItemGroup(NestedSet, WebsiteGenerator):
|
||||
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 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non
|
||||
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)
|
||||
|
||||
|
@ -33,7 +33,7 @@ def create_fiscal_year_and_company(args):
|
||||
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}),
|
||||
|
@ -534,7 +534,7 @@ def create_bank_account(args):
|
||||
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,
|
||||
|
@ -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.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.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 @@ class WebsitePriceListMissingError(frappe.ValidationError):
|
||||
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 @@ def get_cart_quotation(doc=None):
|
||||
"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 @@ def get_billing_addresses(party=None):
|
||||
@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 @@ def guess_territory():
|
||||
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 @@ def _get_cart_quotation(party=None):
|
||||
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 @@ def apply_cart_settings(party=None, quotation=None):
|
||||
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 @@ def get_party(user=None):
|
||||
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 = ''
|
||||
|
||||
|
@ -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', '');
|
||||
}
|
||||
}
|
||||
});
|
@ -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
|
||||
}
|
@ -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
|
@ -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"]
|
@ -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):
|
||||
|
@ -10,26 +10,22 @@ class ProductQuery:
|
||||
"""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 @@ class ProductQuery:
|
||||
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"
|
||||
)
|
||||
|
||||
# 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)
|
||||
result = self.query_items(self.conditions, self.or_conditions,
|
||||
self.substitutions, start=start)
|
||||
|
||||
# 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 @@ class ProductQuery:
|
||||
|
||||
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 @@ class ProductQuery:
|
||||
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 @@ class ProductQuery:
|
||||
|
||||
# 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)
|
||||
|
@ -167,7 +167,7 @@ class TestShoppingCart(unittest.TestCase):
|
||||
|
||||
# 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 @@ class TestShoppingCart(unittest.TestCase):
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -21,7 +21,6 @@ from frappe.utils import (
|
||||
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 @@ class Item(WebsiteGenerator):
|
||||
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 @@ class Item(WebsiteGenerator):
|
||||
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 @@ class Item(WebsiteGenerator):
|
||||
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 @@ class Item(WebsiteGenerator):
|
||||
|
||||
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 @@ class Item(WebsiteGenerator):
|
||||
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 @@ class Item(WebsiteGenerator):
|
||||
|
||||
[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 @@ class Item(WebsiteGenerator):
|
||||
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 @@ class Item(WebsiteGenerator):
|
||||
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 @@ class Item(WebsiteGenerator):
|
||||
'item_code': item,
|
||||
'item_name': item,
|
||||
'description': item,
|
||||
'show_in_website': 1,
|
||||
'is_sales_item': 1,
|
||||
'is_purchase_item': 1,
|
||||
'is_stock_item': 1,
|
||||
|
@ -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 -->
|
||||
|
@ -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">
|
||||
|
@ -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'>
|
||||
|
@ -6,10 +6,7 @@ from __future__ import unicode_literals
|
||||
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 @@ def get_context(context):
|
||||
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:
|
||||
|
@ -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);
|
||||
|
@ -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 @@ def get_context(context):
|
||||
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 @@ def get_context(context):
|
||||
# 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
|
||||
|
Loading…
Reference in New Issue
Block a user