diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 0e1d1268327..5890b5d1e9c 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -60,7 +60,7 @@ def validate_item_variant_attributes(item, args): if not (is_in_range and is_incremental): frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3}")\ .format(attribute, from_range, to_range, increment), InvalidItemAttributeValueError) - + elif value not in attribute_values.get(attribute, []): frappe.throw(_("Value {0} for Attribute {1} does not exist in the list of valid Item Attribute Values").format( value, attribute)) @@ -125,12 +125,11 @@ def copy_attributes_to_variant(item, variant): from frappe.model import no_value_fields for field in item.meta.fields: if field.fieldtype not in no_value_fields and (not field.no_copy)\ - and field.fieldname not in ("item_code", "item_name"): + and field.fieldname not in ("item_code", "item_name", "show_in_website"): if variant.get(field.fieldname) != item.get(field.fieldname): variant.set(field.fieldname, item.get(field.fieldname)) variant.variant_of = item.name variant.has_variants = 0 - variant.show_in_website = 0 if variant.attributes: variant.description += "\n" for d in variant.attributes: diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 6045e773844..6c299d81e83 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -224,5 +224,8 @@ erpnext.patches.v6_4.email_digest_update execute:frappe.delete_doc_if_exists("DocType", "Applicable Territory") execute:frappe.delete_doc_if_exists("DocType", "Shopping Cart Price List") execute:frappe.delete_doc_if_exists("DocType", "Shopping Cart Taxes and Charges Master") + erpnext.patches.v6_4.set_user_in_contact erpnext.patches.v6_4.make_image_thumbnail + +erpnext.patches.v6_5.show_in_website_for_template_item diff --git a/erpnext/patches/v6_5/__init__.py b/erpnext/patches/v6_5/__init__.py new file mode 100644 index 00000000000..baffc488252 --- /dev/null +++ b/erpnext/patches/v6_5/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/erpnext/patches/v6_5/show_in_website_for_template_item.py b/erpnext/patches/v6_5/show_in_website_for_template_item.py new file mode 100644 index 00000000000..48040ee949a --- /dev/null +++ b/erpnext/patches/v6_5/show_in_website_for_template_item.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals +import frappe +import frappe.website.render + +def execute(): + for item_code in frappe.db.sql_list("""select distinct variant_of from `tabItem` + where variant_of is not null and variant_of !='' and show_in_website=1"""): + + item = frappe.get_doc("Item", item_code) + item.db_set("show_in_website", 1, update_modified=False) + + item.get_route() + item.db_set("page_name", item.page_name, update_modified=False) + + frappe.website.render.clear_cache() diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 22f920faa69..68bfd950c53 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -292,6 +292,7 @@ def get_customer(user=None): "customer_group": get_shopping_cart_settings().default_customer_group, "territory": get_root_of("Territory") }) + customer.ignore_mandatory = True customer.insert(ignore_permissions=True) contact = frappe.new_doc("Contact") @@ -300,6 +301,7 @@ def get_customer(user=None): "first_name": fullname, "email_id": user }) + contact.ignore_mandatory = True contact.insert(ignore_permissions=True) return customer diff --git a/erpnext/shopping_cart/product.py b/erpnext/shopping_cart/product.py index d7795d2f976..6a6cb693496 100644 --- a/erpnext/shopping_cart/product.py +++ b/erpnext/shopping_cart/product.py @@ -14,25 +14,11 @@ def get_product_info(item_code): if not is_cart_enabled(): return {} - cart_quotation = _get_cart_quotation() - - price_list = cart_quotation.selling_price_list - - warehouse = frappe.db.get_value("Item", item_code, "website_warehouse") - if warehouse: - in_stock = frappe.db.sql("""select actual_qty from tabBin where - item_code=%s and warehouse=%s""", (item_code, warehouse)) - if in_stock: - in_stock = in_stock[0][0] > 0 and 1 or 0 - else: - in_stock = -1 - - price = price_list and frappe.db.sql("""select price_list_rate, currency from - `tabItem Price` where item_code=%s and price_list=%s""", - (item_code, price_list), as_dict=1) or [] - - price = price and price[0] or None qty = 0 + cart_quotation = _get_cart_quotation() + template_item_code = frappe.db.get_value("Item", item_code, "variant_of") + in_stock = get_qty_in_stock(item_code, template_item_code) + price = get_price(item_code, template_item_code, cart_quotation.selling_price_list) if price: price["formatted_price"] = fmt_money(price["price_list_rate"], currency=price["currency"]) @@ -52,3 +38,31 @@ def get_product_info(item_code): "uom": frappe.db.get_value("Item", item_code, "stock_uom"), "qty": qty } + +def get_qty_in_stock(item_code, template_item_code): + warehouse = frappe.db.get_value("Item", item_code, "website_warehouse") + if not warehouse and template_item_code and template_item_code != item_code: + warehouse = frappe.db.get_value("Item", template_item_code, "website_warehouse") + + if warehouse: + in_stock = frappe.db.sql("""select actual_qty from tabBin where + item_code=%s and warehouse=%s""", (item_code, warehouse)) + if in_stock: + in_stock = in_stock[0][0] > 0 and 1 or 0 + + else: + in_stock = -1 + + return in_stock + +def get_price(item_code, template_item_code, price_list): + if price_list: + price = frappe.get_all("Item Price", fields=["price_list_rate", "currency"], + filters={"price_list": price_list, "item_code": item_code}) + + if not price: + price = frappe.get_all("Item Price", fields=["price_list_rate", "currency"], + filters={"price_list": price_list, "item_code": template_item_code}) + + if price: + return price[0] diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 65a4b91d36a..99f64171cc1 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe import json +import urllib from frappe import msgprint, _ from frappe.utils import cstr, flt, cint, getdate, now_datetime, formatdate from frappe.website.website_generator import WebsiteGenerator @@ -79,6 +80,7 @@ class Item(WebsiteGenerator): self.validate_name_with_item_group() self.update_item_price() self.update_variants() + self.update_template_item() def make_thumbnail(self): """Make a thumbnail of `website_image`""" @@ -113,18 +115,59 @@ class Item(WebsiteGenerator): self.thumbnail = file_doc.thumbnail_url def get_context(self, context): + if self.variant_of: + # redirect to template page! + template_item = frappe.get_doc("Item", self.variant_of) + frappe.flags.redirect_location = template_item.get_route() + "?variant=" + urllib.quote(self.name) + raise frappe.Redirect + context.parent_groups = get_parent_item_groups(self.item_group) + \ [{"name": self.name}] - if self.slideshow: - context.update(get_slideshow(self)) + self.set_variant_context(context) + + self.set_attribute_context(context) + + context.parents = self.get_parents(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_in_website": 1}, order_by="name asc") + + variant = frappe.form_dict.variant + if not variant: + # the case when the item is opened for the first time from its list + variant = context.variants[0] + + context.variant = frappe.get_doc("Item", variant) + + for fieldname in ("website_image", "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 self.has_variants: attribute_values_available = {} context.attribute_values = {} - - # load variants - context.variants = frappe.get_all("Item", - filters={"variant_of": self.name, "show_in_website": 1}) + context.selected_attributes = {} # load attributes for v in context.variants: @@ -136,6 +179,9 @@ class Item(WebsiteGenerator): 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, []) @@ -149,10 +195,6 @@ class Item(WebsiteGenerator): context.variant_info = json.dumps(context.variants) - context.parents = self.get_parents(context) - - return context - def check_warehouse_is_set_for_stock_item(self): if self.is_stock_item==1 and not self.default_warehouse and frappe.get_all("Warehouse"): frappe.msgprint(_("Default Warehouse is mandatory for stock Item."), @@ -360,6 +402,16 @@ class Item(WebsiteGenerator): frappe.db.sql("""update `tabBOM Explosion Item` set description = %s 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 self.variant_of and self.show_in_website: + 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.ignore_permissions = True + template_item.save() + def update_variants(self): if self.has_variants: updated = [] diff --git a/erpnext/templates/generators/item.html b/erpnext/templates/generators/item.html index f24b1a678b1..ccb992b804e 100644 --- a/erpnext/templates/generators/item.html +++ b/erpnext/templates/generators/item.html @@ -38,8 +38,11 @@ @@ -71,13 +74,13 @@ - {% if doc.get({"doctype":"Item Website Specification"}) -%} -
| {{ d.label }} | {{ d.description }} | diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js index 7468ffeff91..2345de46e39 100644 --- a/erpnext/templates/includes/product_page.js +++ b/erpnext/templates/includes/product_page.js @@ -9,7 +9,7 @@ frappe.ready(function() { type: "POST", method: "erpnext.shopping_cart.product.get_product_info", args: { - item_code: "{{ name }}" + item_code: get_item_code() }, callback: function(r) { $(".item-cart").toggleClass("hide", !!!r.message.price); @@ -63,6 +63,15 @@ frappe.ready(function() { }, }); }); + + $("[itemscope] .item-view-attribute select").on("change", function() { + var item_code = encodeURIComponent(get_item_code()); + if (window.location.search.indexOf(item_code)!==-1) { + return; + } + + frappe.load_via_ajax(window.location.pathname + "?variant=" + item_code); + }); }); var toggle_update_cart = function(qty) {