From c8cc8b7115033c9e8bd4ce4c919aebd26381ba89 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 20 May 2015 15:49:55 +0530 Subject: [PATCH 01/14] manage variants new doctype created --- erpnext/stock/doctype/item/item.js | 39 +------ erpnext/stock/doctype/item/item.json | 12 +-- erpnext/stock/doctype/item/item.py | 50 ++------- .../stock/doctype/manage_variants/__init__.py | 0 .../manage_variants/manage_variants.js | 44 ++++++++ .../manage_variants/manage_variants.json | 101 ++++++++++++++++++ .../manage_variants/manage_variants.py | 77 +++++++++++++ .../doctype/variant_attribute/__init__.py | 0 .../variant_attribute/variant_attribute.json | 78 ++++++++++++++ .../variant_attribute/variant_attribute.py | 10 ++ .../stock/doctype/variant_item/__init__.py | 0 .../doctype/variant_item/variant_item.json | 90 ++++++++++++++++ .../doctype/variant_item/variant_item.py | 10 ++ 13 files changed, 422 insertions(+), 89 deletions(-) create mode 100644 erpnext/stock/doctype/manage_variants/__init__.py create mode 100644 erpnext/stock/doctype/manage_variants/manage_variants.js create mode 100644 erpnext/stock/doctype/manage_variants/manage_variants.json create mode 100644 erpnext/stock/doctype/manage_variants/manage_variants.py create mode 100644 erpnext/stock/doctype/variant_attribute/__init__.py create mode 100644 erpnext/stock/doctype/variant_attribute/variant_attribute.json create mode 100644 erpnext/stock/doctype/variant_attribute/variant_attribute.py create mode 100644 erpnext/stock/doctype/variant_item/__init__.py create mode 100644 erpnext/stock/doctype/variant_item/variant_item.json create mode 100644 erpnext/stock/doctype/variant_item/variant_item.py diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 5da3d35443..9db24f143a 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -5,40 +5,6 @@ frappe.provide("erpnext.item"); frappe.ui.form.on("Item", { onload: function(frm) { - var df = frappe.meta.get_docfield("Item Variant", "item_attribute_value"); - df.on_make = function(field) { - $(field.input_area).addClass("ui-front"); - field.$input.autocomplete({ - minLength: 0, - minChars: 0, - source: function(request, response) { - frappe.call({ - method:"frappe.client.get_list", - args:{ - doctype:"Item Attribute Value", - filters: [ - ["parent","=", field.doc.item_attribute], - ["attribute_value", "like", request.term + "%"] - ], - fields: ["attribute_value"] - }, - callback: function(r) { - response($.map(r.message, function(d) { return d.attribute_value; })); - } - }); - }, - select: function(event, ui) { - field.$input.val(ui.item.value); - field.$input.trigger("change"); - }, - focus: function( event, ui ) { - if(ui.item.action) { - return false; - } - }, - }); - } - erpnext.item.setup_queries(frm); }, @@ -64,6 +30,10 @@ frappe.ui.form.on("Item", { frm.add_custom_button(__("Show Variants"), function() { frappe.set_route("List", "Item", {"variant_of": frm.doc.name}); }, "icon-list", "btn-default"); + frm.add_custom_button(__("Manage Variants"), function() { + frappe.route_options = {"item": frm.doc.name }; + new_doc("Manage Variants"); + }); } if (frm.doc.variant_of) { frm.set_intro(__("This Item is a Variant of {0} (Template). Attributes will be copied over from the template unless 'No Copy' is set", [frm.doc.variant_of]), true); @@ -114,6 +84,7 @@ frappe.ui.form.on("Item", { method: "copy_specification_from_item_group" }); }, + is_stock_item: function(frm) { erpnext.item.toggle_reqd(frm); } diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 8b10319f86..6ff8ac3867 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -185,16 +185,6 @@ "precision": "", "read_only": 0 }, - { - "depends_on": "has_variants", - "description": "A new variant (Item) will be created for each attribute value combination", - "fieldname": "variants", - "fieldtype": "Table", - "label": "Variants", - "options": "Item Variant", - "permlevel": 0, - "precision": "" - }, { "fieldname": "inventory", "fieldtype": "Section Break", @@ -877,7 +867,7 @@ } ], "icon": "icon-tag", - "idx": 1, + "idx": 1, "max_attachments": 1, "modified": "2015-06-26 17:20:18.204558", "modified_by": "Administrator", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index aa463eee77..77bcb4d668 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -12,8 +12,6 @@ from frappe.website.doctype.website_slideshow.website_slideshow import get_slide import copy class WarehouseNotSet(frappe.ValidationError): pass -class DuplicateVariant(frappe.ValidationError): pass -class ItemTemplateCannotHaveStock(frappe.ValidationError): pass class Item(WebsiteGenerator): website = frappe._dict( @@ -63,7 +61,6 @@ class Item(WebsiteGenerator): self.cant_change() self.validate_reorder_level() self.validate_warehouse_for_reorder() - self.validate_variants() self.update_item_desc() self.synced_with_hub = 0 @@ -77,7 +74,6 @@ class Item(WebsiteGenerator): invalidate_cache_for_item(self) self.validate_name_with_item_group() self.update_item_price() - self.sync_variants() def get_context(self, context): context["parent_groups"] = get_parent_item_groups(self.item_group) + \ @@ -133,43 +129,6 @@ class Item(WebsiteGenerator): if not matched: frappe.throw(_("Default Unit of Measure can not be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module.")) - def validate_variants(self): - self.validate_variants_are_unique() - self.validate_stock_for_template_must_be_zero() - - def validate_stock_for_template_must_be_zero(self): - if self.has_variants: - stock_in = frappe.db.sql_list("""select warehouse from tabBin - where item_code=%s and ifnull(actual_qty, 0) > 0""", self.name) - if stock_in: - frappe.throw(_("Item Template cannot have stock and varaiants. Please remove stock from warehouses {0}").format(", ".join(stock_in)), - ItemTemplateCannotHaveStock) - - def validate_variants_are_unique(self): - if not self.has_variants: - self.variants = [] - return - - if self.variants: - if self.variant_of: - frappe.throw(_("Item cannot be a variant of a variant")) - - variants, attributes = [], {} - for d in self.variants: - key = (d.item_attribute, d.item_attribute_value) - if key in variants: - frappe.throw(_("{0} {1} is entered more than once in Item Variants table") - .format(d.item_attribute, d.item_attribute_value), DuplicateVariant) - variants.append(key) - - attributes.setdefault(d.item_attribute, [t.attribute_value for t in frappe.db.get_all("Item Attribute Value", - fields=["attribute_value"], filters={"parent": d.item_attribute })]) - - if d.item_attribute_value not in attributes.get(d.item_attribute): - frappe.throw(_("Attribute value {0} does not exist in Item Attribute Master.").format(d.item_attribute_value)) - else: - frappe.throw(_("Please enter atleast one attribute row in Item Variants table")) - def sync_variants(self): variant_item_codes = self.get_variant_item_codes() @@ -460,9 +419,11 @@ class Item(WebsiteGenerator): def update_item_desc(self): if frappe.db.get_value('BOM',self.name, 'description') != self.description: frappe.db.sql("""update `tabBOM` set description = %s where item = %s and docstatus < 2""",(self.description, self.name)) - frappe.db.sql("""update `tabBOM Item` set description = %s where item_code = %s and docstatus < 2""",(self.description, self.name)) - frappe.db.sql("""update `tabBOM Explosion Item` set description = %s where item_code = %s and docstatus < 2""",(self.description, self.name)) - + frappe.db.sql("""update `tabBOM Item` set description = %s where + item_code = %s and docstatus < 2""",(self.description, self.name)) + frappe.db.sql("""update `tabBOM Explosion Item` set description = %s where + item_code = %s and docstatus < 2""",(self.description, self.name)) + def validate_end_of_life(item_code, end_of_life=None, verbose=1): if not end_of_life: end_of_life = frappe.db.get_value("Item", item_code, "end_of_life") @@ -567,3 +528,4 @@ def invalidate_cache_for_item(doc): if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group: invalidate_cache_for(doc, doc.old_item_group) + diff --git a/erpnext/stock/doctype/manage_variants/__init__.py b/erpnext/stock/doctype/manage_variants/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js new file mode 100644 index 0000000000..f579897765 --- /dev/null +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -0,0 +1,44 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.ui.form.on("Manage Variants", { + onload: function(frm) { + var df = frappe.meta.get_docfield("Variant Attribute", "attribute_value"); + df.on_make = function(field) { + field.$input.autocomplete({ + minLength: 0, + minChars: 0, + source: function(request, response) { + frappe.call({ + method:"frappe.client.get_list", + args:{ + doctype:"Variant Attribute", + filters: [ + ["parent","=", field.doc.attribute], + ["attribute_value", "like", request.term + "%"] + ], + fields: ["attribute_value"] + }, + callback: function(r) { + response($.map(r.message, function(d) { return d.attribute_value; })); + } + }); + }, + select: function(event, ui) { + field.$input.val(ui.item.value); + field.$input.trigger("change"); + }, + focus: function( event, ui ) { + if(ui.manage_variants.action) { + return false; + } + }, + }); + } + }, + + refresh: function(frm) { + frm.disable_save(); + } + +}); diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json new file mode 100644 index 0000000000..b00266a5cd --- /dev/null +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -0,0 +1,101 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "creation": "2015-05-19 05:39:59.345901", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "fields": [ + { + "fieldname": "item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "permlevel": 0, + "precision": "" + }, + { + "allow_on_submit": 0, + "fieldname": "attributes", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Attributes", + "no_copy": 0, + "options": "Variant Attribute", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + }, + { + "fieldname": "generate_combinations", + "fieldtype": "Button", + "label": "Generate Combinations", + "options": "generate_combinations", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "variants", + "fieldtype": "Table", + "label": "Variants", + "options": "Variant Item", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "create_variants", + "fieldtype": "Button", + "label": "Create Variants", + "permlevel": 0, + "precision": "" + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "in_create": 1, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "modified": "2015-05-20 18:00:48.331950", + "modified_by": "Administrator", + "module": "Stock", + "name": "Manage Variants", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "Material Master Manager", + "share": 1, + "write": 1 + } + ], + "read_only": 1, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py new file mode 100644 index 0000000000..4169cae243 --- /dev/null +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors 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 +import copy + +class DuplicateAttribute(frappe.ValidationError): pass +class ItemTemplateCannotHaveStock(frappe.ValidationError): pass + +class ManageVariants(Document): + + def generate_combinations(self): + self.validate_attributes() + self.validate_template_item() + self.validate_stock_for_template_must_be_zero() + self.validate_attributes_are_unique() + self.get_variant_item_codes() + + def validate_attributes(self): + if not self.attributes: + frappe.throw("Enter atleast one Attribute & its Value in Attribute table.") + + def validate_template_item(self): + template_item = frappe.get_doc("Item", self.item) + if not template_item.has_variants: + frappe.throw(_("Selected Item cannot have Variants.")) + + if template_item.variant_of: + frappe.throw(_("Item cannot be a variant of a variant")) + + def validate_stock_for_template_must_be_zero(self): + stock_in = frappe.db.sql_list("""select warehouse from tabBin + where item_code=%s and ifnull(actual_qty, 0) > 0""", self.item) + if stock_in: + frappe.throw(_("Item Template cannot have stock and varaiants. Please remove \ + stock from warehouses {0}").format(", ".join(stock_in)), ItemTemplateCannotHaveStock) + + def validate_attributes_are_unique(self): + attributes = [] + for d in self.attributes: + key = (d.attribute, d.attribute_value) + if key in attributes: + frappe.throw(_("{0} {1} is entered more than once in Attributes table") + .format(d.attribute, d.attribute_value), DuplicateAttribute) + attributes.append(key) + + def get_variant_item_codes(self): + """Get all possible suffixes for variants""" + variant_dict = {} + variant_item_codes = [] + + for d in self.attributes: + variant_dict.setdefault(d.attribute, []).append(d.attribute_value) + + all_attributes = [d.name for d in frappe.get_all("Item Attribute", order_by = "priority asc")] + + # sort attributes by their priority + attributes = filter(None, map(lambda d: d if d in variant_dict else None, all_attributes)) + + def add_attribute_suffixes(item_code, my_attributes, attributes): + attr = frappe.get_doc("Item Attribute", attributes[0]) + for value in attr.item_attribute_values: + if value.attribute_value in variant_dict[attr.name]: + _my_attributes = copy.deepcopy(my_attributes) + _my_attributes.append([attr.name, value.attribute_value]) + if len(attributes) > 1: + add_attribute_suffixes(item_code + "-" + value.abbr, _my_attributes, attributes[1:]) + else: + variant_item_codes.append(item_code + "-" + value.abbr) + + add_attribute_suffixes(self.item, [], attributes) + + print variant_item_codes \ No newline at end of file diff --git a/erpnext/stock/doctype/variant_attribute/__init__.py b/erpnext/stock/doctype/variant_attribute/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/variant_attribute/variant_attribute.json b/erpnext/stock/doctype/variant_attribute/variant_attribute.json new file mode 100644 index 0000000000..5ab3d73239 --- /dev/null +++ b/erpnext/stock/doctype/variant_attribute/variant_attribute.json @@ -0,0 +1,78 @@ +{ + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "", + "creation": "2015-05-19 05:12:30.344797", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Other", + "fields": [ + { + "allow_on_submit": 0, + "fieldname": "attribute", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Attribute", + "no_copy": 0, + "options": "Item Attribute", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "allow_on_submit": 0, + "fieldname": "attribute_value", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Attribute Value", + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "", + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "modified": "2015-05-20 06:16:16.803578", + "modified_by": "Administrator", + "module": "Stock", + "name": "Variant Attribute", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/stock/doctype/variant_attribute/variant_attribute.py b/erpnext/stock/doctype/variant_attribute/variant_attribute.py new file mode 100644 index 0000000000..9c35732bf2 --- /dev/null +++ b/erpnext/stock/doctype/variant_attribute/variant_attribute.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class VariantAttribute(Document): + pass diff --git a/erpnext/stock/doctype/variant_item/__init__.py b/erpnext/stock/doctype/variant_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/variant_item/variant_item.json b/erpnext/stock/doctype/variant_item/variant_item.json new file mode 100644 index 0000000000..8e1c8621e2 --- /dev/null +++ b/erpnext/stock/doctype/variant_item/variant_item.json @@ -0,0 +1,90 @@ +{ + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "", + "creation": "2015-05-19 05:55:31.155672", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Other", + "fields": [ + { + "allow_on_submit": 0, + "fieldname": "varient", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Variant", + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "fieldname": "item_code", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Item Code", + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "", + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "modified": "2015-05-20 18:20:10.555404", + "modified_by": "Administrator", + "module": "Stock", + "name": "Variant Item", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/stock/doctype/variant_item/variant_item.py b/erpnext/stock/doctype/variant_item/variant_item.py new file mode 100644 index 0000000000..4aa4ed81a4 --- /dev/null +++ b/erpnext/stock/doctype/variant_item/variant_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class VariantItem(Document): + pass From 333ccd212b4b92e06f19caa5204874e2907d455d Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 21 May 2015 16:40:27 +0530 Subject: [PATCH 02/14] variants combination generation logic added --- .../manage_variants/manage_variants.json | 3 +- .../manage_variants/manage_variants.py | 38 +++++++++++------ .../doctype/variant_item/variant_item.json | 42 +------------------ 3 files changed, 30 insertions(+), 53 deletions(-) diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json index b00266a5cd..f16910b7f9 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.json +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -62,6 +62,7 @@ "fieldname": "create_variants", "fieldtype": "Button", "label": "Create Variants", + "options": "create_variants", "permlevel": 0, "precision": "" } @@ -73,7 +74,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-05-20 18:00:48.331950", + "modified": "2015-05-21 16:21:33.707125", "modified_by": "Administrator", "module": "Stock", "name": "Manage Variants", diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 4169cae243..a583619b62 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -17,13 +17,14 @@ class ManageVariants(Document): self.validate_attributes() self.validate_template_item() self.validate_stock_for_template_must_be_zero() + self.validate_attribute_values() self.validate_attributes_are_unique() self.get_variant_item_codes() - + def validate_attributes(self): if not self.attributes: frappe.throw("Enter atleast one Attribute & its Value in Attribute table.") - + def validate_template_item(self): template_item = frappe.get_doc("Item", self.item) if not template_item.has_variants: @@ -31,7 +32,7 @@ class ManageVariants(Document): if template_item.variant_of: frappe.throw(_("Item cannot be a variant of a variant")) - + def validate_stock_for_template_must_be_zero(self): stock_in = frappe.db.sql_list("""select warehouse from tabBin where item_code=%s and ifnull(actual_qty, 0) > 0""", self.item) @@ -39,15 +40,24 @@ class ManageVariants(Document): frappe.throw(_("Item Template cannot have stock and varaiants. Please remove \ stock from warehouses {0}").format(", ".join(stock_in)), ItemTemplateCannotHaveStock) + def validate_attribute_values(self): + attributes = {} + for d in self.attributes: + attributes.setdefault(d.attribute, + [t.attribute_value for t in + frappe.db.get_all("Item Attribute Value", fields=["attribute_value"], filters={"parent": d.attribute })]) + if d.attribute_value not in attributes.get(d.attribute): + frappe.throw(_("Attribute value {0} does not exist in Item Attribute Master.").format(d.attribute_value)) + def validate_attributes_are_unique(self): - attributes = [] - for d in self.attributes: - key = (d.attribute, d.attribute_value) - if key in attributes: - frappe.throw(_("{0} {1} is entered more than once in Attributes table") - .format(d.attribute, d.attribute_value), DuplicateAttribute) - attributes.append(key) - + attributes = [] + for d in self.attributes: + key = (d.attribute, d.attribute_value) + if key in attributes: + frappe.throw(_("{0} {1} is entered more than once in Attributes table") + .format(d.attribute, d.attribute_value), DuplicateAttribute) + attributes.append(key) + def get_variant_item_codes(self): """Get all possible suffixes for variants""" variant_dict = {} @@ -74,4 +84,8 @@ class ManageVariants(Document): add_attribute_suffixes(self.item, [], attributes) - print variant_item_codes \ No newline at end of file + for v in variant_item_codes: + self.append('variants', {"variant": v}) + + def create_variants(self): + pass \ No newline at end of file diff --git a/erpnext/stock/doctype/variant_item/variant_item.json b/erpnext/stock/doctype/variant_item/variant_item.json index 8e1c8621e2..b4a4c0db59 100644 --- a/erpnext/stock/doctype/variant_item/variant_item.json +++ b/erpnext/stock/doctype/variant_item/variant_item.json @@ -11,7 +11,7 @@ "fields": [ { "allow_on_submit": 0, - "fieldname": "varient", + "fieldname": "variant", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, @@ -23,44 +23,6 @@ "permlevel": 0, "precision": "", "print_hide": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0 - }, - { - "allow_on_submit": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0 - }, - { - "allow_on_submit": 0, - "fieldname": "item_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Item Code", - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, "read_only": 0, "report_hide": 0, "reqd": 1, @@ -76,7 +38,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-05-20 18:20:10.555404", + "modified": "2015-05-21 16:18:16.605271", "modified_by": "Administrator", "module": "Stock", "name": "Variant Item", From 8fb123b20e25f54c01816172ec4aee2ba9eb931d Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Fri, 22 May 2015 17:00:58 +0530 Subject: [PATCH 03/14] item variants, creation, deleation and update logic added. logic added to copy changes in template to variants --- erpnext/stock/doctype/item/item.js | 9 +- erpnext/stock/doctype/item/item.json | 41 +++++- erpnext/stock/doctype/item/item.py | 123 +++--------------- .../manage_variants/manage_variants.js | 13 +- .../manage_variants/manage_variants.json | 5 +- .../manage_variants/manage_variants.py | 110 ++++++++++++++-- .../stock_ledger_entry/stock_ledger_entry.py | 2 +- .../doctype/variant_item/variant_item.json | 26 +++- 8 files changed, 204 insertions(+), 125 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 9db24f143a..d2f1d7527c 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -30,10 +30,6 @@ frappe.ui.form.on("Item", { frm.add_custom_button(__("Show Variants"), function() { frappe.set_route("List", "Item", {"variant_of": frm.doc.name}); }, "icon-list", "btn-default"); - frm.add_custom_button(__("Manage Variants"), function() { - frappe.route_options = {"item": frm.doc.name }; - new_doc("Manage Variants"); - }); } if (frm.doc.variant_of) { frm.set_intro(__("This Item is a Variant of {0} (Template). Attributes will be copied over from the template unless 'No Copy' is set", [frm.doc.variant_of]), true); @@ -87,6 +83,11 @@ frappe.ui.form.on("Item", { is_stock_item: function(frm) { erpnext.item.toggle_reqd(frm); + }, + + manage_variants: function(frm) { + frappe.route_options = {"item": frm.doc.name }; + frappe.set_route("List", "Manage Variants"); } }); diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 6ff8ac3867..9e7fb3dabe 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -12,7 +12,7 @@ { "fieldname": "name_and_description_section", "fieldtype": "Section Break", - "label": "Name and Description", + "label": "", "no_copy": 0, "oldfieldtype": "Section Break", "options": "icon-flag", @@ -167,16 +167,17 @@ "search_index": 0 }, { - "depends_on": "eval:!!!doc.variant_of", + "depends_on": "eval:!doc.variant_of", "fieldname": "variants_section", "fieldtype": "Section Break", - "label": "Variants", + "label": "Variant", "permlevel": 0, "precision": "" }, { "default": "0", - "description": "Automatically set. If this item has variants, then it cannot be selected in sales orders etc.", + "depends_on": "", + "description": "If this item has variants, then it cannot be selected in sales orders etc.", "fieldname": "has_variants", "fieldtype": "Check", "label": "Has Variants", @@ -185,6 +186,38 @@ "precision": "", "read_only": 0 }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "depends_on": "has_variants", + "fieldname": "manage_variants", + "fieldtype": "Button", + "label": "Manage Variants", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "section_break_20", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "depends_on": "variant_of", + "fieldname": "attributes", + "fieldtype": "Table", + "hidden": 0, + "label": "Attributes", + "no_copy": 1, + "options": "Variant Attribute", + "permlevel": 0, + "precision": "", + "read_only": 0 + }, { "fieldname": "inventory", "fieldtype": "Section Break", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 77bcb4d668..c907993d65 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -9,7 +9,7 @@ from frappe.website.website_generator import WebsiteGenerator from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for, get_parent_item_groups from frappe.website.render import clear_cache from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow -import copy +from erpnext.stock.doctype.manage_variants.manage_variants import update_variant class WarehouseNotSet(frappe.ValidationError): pass @@ -46,9 +46,6 @@ class Item(WebsiteGenerator): if self.image and not self.website_image: self.website_image = self.image - if self.variant_of: - self.copy_attributes_to_variant(frappe.get_doc("Item", self.variant_of), self) - self.check_warehouse_is_set_for_stock_item() self.check_stock_uom_with_bin() self.add_default_uom_in_conversion_factor_table() @@ -63,6 +60,7 @@ class Item(WebsiteGenerator): self.validate_warehouse_for_reorder() self.update_item_desc() self.synced_with_hub = 0 + self.validate_has_variants() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -74,6 +72,7 @@ class Item(WebsiteGenerator): invalidate_cache_for_item(self) self.validate_name_with_item_group() self.update_item_price() + self.update_variants() def get_context(self, context): context["parent_groups"] = get_parent_item_groups(self.item_group) + \ @@ -129,105 +128,6 @@ class Item(WebsiteGenerator): if not matched: frappe.throw(_("Default Unit of Measure can not be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module.")) - def sync_variants(self): - variant_item_codes = self.get_variant_item_codes() - - # delete missing variants - existing_variants = [d.name for d in frappe.get_all("Item", - filters={"variant_of":self.name})] - - updated, deleted = [], [] - for existing_variant in existing_variants: - if existing_variant not in variant_item_codes: - frappe.delete_doc("Item", existing_variant) - deleted.append(existing_variant) - else: - self.update_variant(existing_variant) - updated.append(existing_variant) - - inserted = [] - for item_code in variant_item_codes: - if item_code not in existing_variants: - self.make_variant(item_code) - inserted.append(item_code) - - if inserted: - frappe.msgprint(_("Item Variants {0} created").format(", ".join(inserted))) - - if updated: - frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) - - if deleted: - frappe.msgprint(_("Item Variants {0} deleted").format(", ".join(deleted))) - - def get_variant_item_codes(self): - """Get all possible suffixes for variants""" - if not self.variants: - return [] - - self.variant_attributes = {} - variant_dict = {} - variant_item_codes = [] - - for d in self.variants: - variant_dict.setdefault(d.item_attribute, []).append(d.item_attribute_value) - - all_attributes = [d.name for d in frappe.get_all("Item Attribute", order_by = "priority asc")] - - # sort attributes by their priority - attributes = filter(None, map(lambda d: d if d in variant_dict else None, all_attributes)) - - def add_attribute_suffixes(item_code, my_attributes, attributes): - attr = frappe.get_doc("Item Attribute", attributes[0]) - for value in attr.item_attribute_values: - if value.attribute_value in variant_dict[attr.name]: - _my_attributes = copy.deepcopy(my_attributes) - _my_attributes.append([attr.name, value.attribute_value]) - if len(attributes) > 1: - add_attribute_suffixes(item_code + "-" + value.abbr, _my_attributes, attributes[1:]) - else: - variant_item_codes.append(item_code + "-" + value.abbr) - self.variant_attributes[item_code + "-" + value.abbr] = _my_attributes - - add_attribute_suffixes(self.name, [], attributes) - - return variant_item_codes - - def make_variant(self, item_code): - item = frappe.new_doc("Item") - item.item_code = item_code - self.copy_attributes_to_variant(self, item, insert=True) - item.insert() - - def update_variant(self, item_code): - item = frappe.get_doc("Item", item_code) - item.item_code = item_code - self.copy_attributes_to_variant(self, item) - item.save() - - def copy_attributes_to_variant(self, template, variant, insert=False): - from frappe.model import no_value_fields - for field in self.meta.fields: - if field.fieldtype not in no_value_fields and (insert or not field.no_copy)\ - and field.fieldname not in ("item_code", "item_name"): - if variant.get(field.fieldname) != template.get(field.fieldname): - variant.set(field.fieldname, template.get(field.fieldname)) - variant.__dirty = True - - variant.description += "\n" - - if not getattr(template, "variant_attributes", None): - template.get_variant_item_codes() - - for attr in template.variant_attributes[variant.item_code]: - variant.description += "

" + attr[0] + ": " + attr[1] + "

" - - variant.item_name = self.item_name + variant.item_code[len(self.name):] - - variant.variant_of = template.name - variant.has_variants = 0 - variant.show_in_website = 0 - def update_template_tables(self): template = frappe.get_doc("Item", self.variant_of) @@ -320,7 +220,8 @@ class Item(WebsiteGenerator): vals.has_batch_no != self.has_batch_no or cstr(vals.valuation_method) != cstr(self.valuation_method)): if self.check_if_sle_exists() == "exists": - frappe.throw(_("As there are existing stock transactions for this item, you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'")) + frappe.throw(_("As there are existing stock transactions for this item, \ + you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'")) def validate_reorder_level(self): if cint(self.apply_warehouse_wise_reorder_level): @@ -423,6 +324,20 @@ class Item(WebsiteGenerator): item_code = %s and docstatus < 2""",(self.description, self.name)) frappe.db.sql("""update `tabBOM Explosion Item` set description = %s where item_code = %s and docstatus < 2""",(self.description, self.name)) + + def update_variants(self): + if self.has_variants: + updated = [] + variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name }) + for d in variants: + update_variant(self.item_code, d) + updated.append(d.item_code) + frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) + + def validate_has_variants(self): + if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"): + if frappe.db.exists("Item", {"variant_of": self.name}): + frappe.throw("Item has variants.") def validate_end_of_life(item_code, end_of_life=None, verbose=1): if not end_of_life: diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js index f579897765..992e2a9df1 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.js +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -12,7 +12,7 @@ frappe.ui.form.on("Manage Variants", { frappe.call({ method:"frappe.client.get_list", args:{ - doctype:"Variant Attribute", + doctype:"Item Attribute Value", filters: [ ["parent","=", field.doc.attribute], ["attribute_value", "like", request.term + "%"] @@ -39,6 +39,17 @@ frappe.ui.form.on("Manage Variants", { refresh: function(frm) { frm.disable_save(); + }, + + item:function(frm) { + return frappe.call({ + method: "get_item_details", + doc:frm.doc, + callback: function(r) { + refresh_field('attributes'); + refresh_field('variants'); + } + }) } }); diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json index f16910b7f9..bcad045c07 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.json +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -14,7 +14,8 @@ "label": "Item", "options": "Item", "permlevel": 0, - "precision": "" + "precision": "", + "reqd": 1 }, { "allow_on_submit": 0, @@ -74,7 +75,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-05-21 16:21:33.707125", + "modified": "2015-05-27 04:43:52.051367", "modified_by": "Administrator", "module": "Stock", "name": "Manage Variants", diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index a583619b62..8b96ff9dd2 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -7,12 +7,17 @@ import frappe from frappe import _ from frappe.model.document import Document import copy +import json class DuplicateAttribute(frappe.ValidationError): pass class ItemTemplateCannotHaveStock(frappe.ValidationError): pass class ManageVariants(Document): - + + def get_item_details(self): + self.get_attributes() + self.get_variants() + def generate_combinations(self): self.validate_attributes() self.validate_template_item() @@ -20,6 +25,31 @@ class ManageVariants(Document): self.validate_attribute_values() self.validate_attributes_are_unique() self.get_variant_item_codes() + + def create_variants(self): + self.sync_variants() + + def get_attributes(self): + attributes = {} + self.set('attributes', []) + for d in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` as attribute, + `tabItem` as item where attribute.parent= item.name and item.variant_of = %s""", self.item, as_dict=1): + attributes.setdefault(d.attribute, []).append(d.attribute_value) + for d in attributes: + attribute_values = set(attributes[d]) + for value in attribute_values: + self.append('attributes',{"attribute": d, "attribute_value": value}) + + def get_variants(self): + self.set('variants', []) + variants = [d.name for d in frappe.get_all("Item", + filters={"variant_of":self.item})] + for d in variants: + variant_attributes, attributes = "", [] + for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d): + variant_attributes += attribute[1] + " " + attributes.append([attribute[0], attribute[1]]) + self.append('variants',{"variant": d, "variant_attributes": variant_attributes, "attributes": json.dumps(attributes)}) def validate_attributes(self): if not self.attributes: @@ -61,7 +91,7 @@ class ManageVariants(Document): def get_variant_item_codes(self): """Get all possible suffixes for variants""" variant_dict = {} - variant_item_codes = [] + self.set('variants', []) for d in self.attributes: variant_dict.setdefault(d.attribute, []).append(d.attribute_value) @@ -80,12 +110,76 @@ class ManageVariants(Document): if len(attributes) > 1: add_attribute_suffixes(item_code + "-" + value.abbr, _my_attributes, attributes[1:]) else: - variant_item_codes.append(item_code + "-" + value.abbr) + variant_attributes = "" + for d in _my_attributes: + variant_attributes += d[1] + " " + self.append('variants', {"variant": item_code + "-" + value.abbr, + "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes}) add_attribute_suffixes(self.item, [], attributes) - for v in variant_item_codes: - self.append('variants', {"variant": v}) - - def create_variants(self): - pass \ No newline at end of file + def sync_variants(self): + variant_item_codes = [] + for v in self.variants: + variant_item_codes.append(v.variant) + + existing_variants = [d.name for d in frappe.get_all("Item", + filters={"variant_of":self.item})] + + inserted, updated, deleted = [], [], [] + for existing_variant in existing_variants: + if existing_variant not in variant_item_codes: + frappe.delete_doc("Item", existing_variant) + deleted.append(existing_variant) + + for item_code in variant_item_codes: + if item_code not in existing_variants: + make_variant(self.item, item_code, self.variants) + inserted.append(item_code) + else: + update_variant(self.item, existing_variant, self.variants) + updated.append(existing_variant) + + if inserted: + frappe.msgprint(_("Item Variants {0} created").format(", ".join(inserted))) + + if updated: + frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) + + if deleted: + frappe.msgprint(_("Item Variants {0} deleted").format(", ".join(deleted))) + +def make_variant(item, variant_code, variant_attribute): + variant = frappe.new_doc("Item") + variant.item_code = variant_code + template = frappe.get_doc("Item", item) + copy_attributes_to_variant(template, variant, variant_attribute, insert=True) + variant.insert() + +def update_variant(item, variant_code, variant_attribute=None): + variant = frappe.get_doc("Item", variant_code) + template = frappe.get_doc("Item", item) + copy_attributes_to_variant(template, variant, variant_attribute, insert=True) + variant.save() + +def copy_attributes_to_variant(template, variant, variant_attribute=None, insert=False): + from frappe.model import no_value_fields + for field in template.meta.fields: + if field.fieldtype not in no_value_fields and (insert or not field.no_copy)\ + and field.fieldname not in ("item_code", "item_name"): + if variant.get(field.fieldname) != template.get(field.fieldname): + variant.set(field.fieldname, template.get(field.fieldname)) + variant.item_name = template.item_name + variant.item_code[len(template.name):] + variant.variant_of = template.name + variant.has_variants = 0 + variant.show_in_website = 0 + if variant_attribute: + for d in variant_attribute: + if d.variant == variant.item_code: + variant.attributes= [] + for a in json.loads(d.attributes): + variant.append('attributes', {"attribute": a[0], "attribute_value": a[1]}) + if variant.attributes: + variant.description += "\n" + for d in variant.attributes: + variant.description += "

" + d.attribute + ": " + d.attribute_value + "

" \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index de6b3a6950..74fd4d1140 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -8,7 +8,7 @@ from frappe import _ from frappe.utils import flt, getdate, add_days, formatdate from frappe.model.document import Document from datetime import date -from erpnext.stock.doctype.item.item import ItemTemplateCannotHaveStock +from erpnext.stock.doctype.manage_variants.manage_variants import ItemTemplateCannotHaveStock class StockFreezeError(frappe.ValidationError): pass diff --git a/erpnext/stock/doctype/variant_item/variant_item.json b/erpnext/stock/doctype/variant_item/variant_item.json index b4a4c0db59..9f10482720 100644 --- a/erpnext/stock/doctype/variant_item/variant_item.json +++ b/erpnext/stock/doctype/variant_item/variant_item.json @@ -28,6 +28,30 @@ "reqd": 1, "search_index": 0, "set_only_once": 0 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "variant_attributes", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Variant Attributes", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "fieldname": "attributes", + "fieldtype": "Text", + "hidden": 1, + "label": "attributes", + "permlevel": 0, + "precision": "", + "read_only": 1 } ], "hide_heading": 0, @@ -38,7 +62,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-05-21 16:18:16.605271", + "modified": "2015-05-28 04:58:20.495616", "modified_by": "Administrator", "module": "Stock", "name": "Variant Item", From bd9745ba729235416b5da8616a447ba0da123328 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Fri, 29 May 2015 17:15:55 +0530 Subject: [PATCH 04/14] Rename Item Variant from Manage Variants feature Added. --- .../manage_variants/manage_variants.js | 9 ++- .../manage_variants/manage_variants.json | 10 +-- .../manage_variants/manage_variants.py | 61 ++++++++++++++----- .../doctype/variant_item/variant_item.json | 4 +- 4 files changed, 56 insertions(+), 28 deletions(-) diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js index 992e2a9df1..5388cc2a92 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.js +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -39,8 +39,14 @@ frappe.ui.form.on("Manage Variants", { refresh: function(frm) { frm.disable_save(); + frm.page.set_primary_action(__("Create Variants"), function() { + frappe.call({ + method: "create_variants", + doc:frm.doc + }) + }); }, - + item:function(frm) { return frappe.call({ method: "get_item_details", @@ -51,5 +57,4 @@ frappe.ui.form.on("Manage Variants", { } }) } - }); diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json index bcad045c07..1ad938d05b 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.json +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -58,14 +58,6 @@ "options": "Variant Item", "permlevel": 0, "precision": "" - }, - { - "fieldname": "create_variants", - "fieldtype": "Button", - "label": "Create Variants", - "options": "create_variants", - "permlevel": 0, - "precision": "" } ], "hide_heading": 0, @@ -75,7 +67,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-05-27 04:43:52.051367", + "modified": "2015-05-28 06:18:03.238411", "modified_by": "Administrator", "module": "Stock", "name": "Manage Variants", diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 8b96ff9dd2..834b9ef9d9 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -15,8 +15,10 @@ class ItemTemplateCannotHaveStock(frappe.ValidationError): pass class ManageVariants(Document): def get_item_details(self): - self.get_attributes() - self.get_variants() + self.clear_tables() + if self.item: + self.get_attributes() + self.get_variants() def generate_combinations(self): self.validate_attributes() @@ -29,6 +31,10 @@ class ManageVariants(Document): def create_variants(self): self.sync_variants() + def clear_tables(self): + self.set('attributes', []) + self.set('variants', []) + def get_attributes(self): attributes = {} self.set('attributes', []) @@ -41,15 +47,14 @@ class ManageVariants(Document): self.append('attributes',{"attribute": d, "attribute_value": value}) def get_variants(self): - self.set('variants', []) variants = [d.name for d in frappe.get_all("Item", filters={"variant_of":self.item})] for d in variants: variant_attributes, attributes = "", [] for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d): - variant_attributes += attribute[1] + " " + variant_attributes += attribute[1] + " | " attributes.append([attribute[0], attribute[1]]) - self.append('variants',{"variant": d, "variant_attributes": variant_attributes, "attributes": json.dumps(attributes)}) + self.append('variants',{"variant": d, "variant_attributes": variant_attributes[: -2], "attributes": json.dumps(attributes)}) def validate_attributes(self): if not self.attributes: @@ -112,33 +117,50 @@ class ManageVariants(Document): else: variant_attributes = "" for d in _my_attributes: - variant_attributes += d[1] + " " + variant_attributes += d[1] + " | " self.append('variants', {"variant": item_code + "-" + value.abbr, - "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes}) - + "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes[: -2]}) add_attribute_suffixes(self.item, [], attributes) def sync_variants(self): variant_item_codes = [] + item_variants_attributes = {} + inserted, updated, renamed_old, renamed_new, deleted = [], [], [], [], [] + for v in self.variants: variant_item_codes.append(v.variant) existing_variants = [d.name for d in frappe.get_all("Item", filters={"variant_of":self.item})] + + for d in existing_variants: + attributes = [] + for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d): + attributes.append([attribute[0], attribute[1]]) + item_variants_attributes.setdefault(d, []).append(attributes) - inserted, updated, deleted = [], [], [] for existing_variant in existing_variants: if existing_variant not in variant_item_codes: - frappe.delete_doc("Item", existing_variant) - deleted.append(existing_variant) + att = item_variants_attributes[existing_variant][0] + for variant in self.variants: + if sorted(json.loads(variant.attributes) ,key=lambda x: x[0]) == \ + sorted(att ,key=lambda x: x[0]): + rename_variant(existing_variant, variant.variant) + renamed_old.append(existing_variant) + renamed_new.append(variant.variant) + + if existing_variant not in renamed_old: + delete_variant(existing_variant) + deleted.append(existing_variant) for item_code in variant_item_codes: if item_code not in existing_variants: - make_variant(self.item, item_code, self.variants) - inserted.append(item_code) + if item_code not in renamed_new: + make_variant(self.item, item_code, self.variants) + inserted.append(item_code) else: - update_variant(self.item, existing_variant, self.variants) - updated.append(existing_variant) + update_variant(self.item, item_code, self.variants) + updated.append(item_code) if inserted: frappe.msgprint(_("Item Variants {0} created").format(", ".join(inserted))) @@ -146,6 +168,9 @@ class ManageVariants(Document): if updated: frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) + if renamed_old: + frappe.msgprint(_("Item Variants {0} renamed").format(", ".join(renamed_old))) + if deleted: frappe.msgprint(_("Item Variants {0} deleted").format(", ".join(deleted))) @@ -162,6 +187,12 @@ def update_variant(item, variant_code, variant_attribute=None): copy_attributes_to_variant(template, variant, variant_attribute, insert=True) variant.save() +def rename_variant(old_variant_code, new_variant_code): + frappe.rename_doc("Item", old_variant_code, new_variant_code) + +def delete_variant(variant_code): + frappe.delete_doc("Item", variant_code) + def copy_attributes_to_variant(template, variant, variant_attribute=None, insert=False): from frappe.model import no_value_fields for field in template.meta.fields: diff --git a/erpnext/stock/doctype/variant_item/variant_item.json b/erpnext/stock/doctype/variant_item/variant_item.json index 9f10482720..13c46ac0f7 100644 --- a/erpnext/stock/doctype/variant_item/variant_item.json +++ b/erpnext/stock/doctype/variant_item/variant_item.json @@ -47,7 +47,7 @@ { "fieldname": "attributes", "fieldtype": "Text", - "hidden": 1, + "hidden": 0, "label": "attributes", "permlevel": 0, "precision": "", @@ -62,7 +62,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-05-28 04:58:20.495616", + "modified": "2015-05-29 02:09:33.622631", "modified_by": "Administrator", "module": "Stock", "name": "Variant Item", From fed431f9085c8bb1725a7a7387f9dde64fe5d94b Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 1 Jun 2015 17:05:37 +0530 Subject: [PATCH 05/14] Code Fixes in Manage Variants --- .../doctype/manage_variants/manage_variants.js | 7 +------ .../doctype/manage_variants/manage_variants.py | 14 +++++++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js index 5388cc2a92..9e6fff78b8 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.js +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -27,12 +27,7 @@ frappe.ui.form.on("Manage Variants", { select: function(event, ui) { field.$input.val(ui.item.value); field.$input.trigger("change"); - }, - focus: function( event, ui ) { - if(ui.manage_variants.action) { - return false; - } - }, + } }); } }, diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 834b9ef9d9..913c2d8136 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -125,7 +125,7 @@ class ManageVariants(Document): def sync_variants(self): variant_item_codes = [] item_variants_attributes = {} - inserted, updated, renamed_old, renamed_new, deleted = [], [], [], [], [] + inserted, updated, old_variant_name, new_variant_name, deleted = [], [], [], [], [] for v in self.variants: variant_item_codes.append(v.variant) @@ -146,16 +146,16 @@ class ManageVariants(Document): if sorted(json.loads(variant.attributes) ,key=lambda x: x[0]) == \ sorted(att ,key=lambda x: x[0]): rename_variant(existing_variant, variant.variant) - renamed_old.append(existing_variant) - renamed_new.append(variant.variant) + old_variant_name.append(existing_variant) + new_variant_name.append(variant.variant) - if existing_variant not in renamed_old: + if existing_variant not in old_variant_name: delete_variant(existing_variant) deleted.append(existing_variant) for item_code in variant_item_codes: if item_code not in existing_variants: - if item_code not in renamed_new: + if item_code not in new_variant_name: make_variant(self.item, item_code, self.variants) inserted.append(item_code) else: @@ -168,8 +168,8 @@ class ManageVariants(Document): if updated: frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) - if renamed_old: - frappe.msgprint(_("Item Variants {0} renamed").format(", ".join(renamed_old))) + if old_variant_name: + frappe.msgprint(_("Item Variants {0} renamed").format(", ".join(old_variant_name))) if deleted: frappe.msgprint(_("Item Variants {0} deleted").format(", ".join(deleted))) From 90c66b19986091db954eaa5a0f7b0baa18bb1161 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 4 Jun 2015 15:42:38 +0530 Subject: [PATCH 06/14] test cases added --- .../projects/doctype/project/test_project.py | 1 + erpnext/stock/doctype/item/item.py | 10 ++++ erpnext/stock/doctype/item/test_item.py | 60 +++---------------- erpnext/stock/doctype/item/test_records.json | 5 -- .../manage_variants/manage_variants.py | 15 +---- .../manage_variants/test_manage_variants.py | 48 +++++++++++++++ .../stock_ledger_entry/stock_ledger_entry.py | 2 +- 7 files changed, 70 insertions(+), 71 deletions(-) create mode 100644 erpnext/stock/doctype/manage_variants/test_manage_variants.py diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index f69ce80824..f1be54492f 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -5,3 +5,4 @@ from __future__ import unicode_literals import frappe test_records = frappe.get_test_records('Project') +test_ignore = ["Sales Order"] diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index c907993d65..22e6b212f5 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -12,6 +12,7 @@ from frappe.website.doctype.website_slideshow.website_slideshow import get_slide from erpnext.stock.doctype.manage_variants.manage_variants import update_variant class WarehouseNotSet(frappe.ValidationError): pass +class ItemTemplateCannotHaveStock(frappe.ValidationError): pass class Item(WebsiteGenerator): website = frappe._dict( @@ -61,6 +62,7 @@ class Item(WebsiteGenerator): self.update_item_desc() self.synced_with_hub = 0 self.validate_has_variants() + self.validate_stock_for_template_must_be_zero() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -338,6 +340,14 @@ class Item(WebsiteGenerator): if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"): if frappe.db.exists("Item", {"variant_of": self.name}): frappe.throw("Item has variants.") + + def validate_stock_for_template_must_be_zero(self): + if self.has_variants: + stock_in = frappe.db.sql_list("""select warehouse from tabBin + where item_code=%s and ifnull(actual_qty, 0) > 0""", self.name) + if stock_in: + frappe.throw(_("Item Template cannot have stock and varaiants. Please remove \ + stock from warehouses {0}").format(", ".join(stock_in)), ItemTemplateCannotHaveStock) def validate_end_of_life(item_code, end_of_life=None, verbose=1): if not end_of_life: diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 02ff714bda..9cf3c077b4 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -6,7 +6,7 @@ import unittest import frappe from frappe.test_runner import make_test_records -from erpnext.stock.doctype.item.item import WarehouseNotSet, DuplicateVariant, ItemTemplateCannotHaveStock +from erpnext.stock.doctype.item.item import WarehouseNotSet, ItemTemplateCannotHaveStock from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry test_ignore = ["BOM"] @@ -20,60 +20,14 @@ class TestItem(unittest.TestCase): item.insert() else: item = frappe.get_doc("Item", item_code) - return item - - def test_duplicate_variant(self): - item = frappe.copy_doc(test_records[11]) - item.append("variants", {"item_attribute": "Test Size", "item_attribute_value": "Small"}) - self.assertRaises(DuplicateVariant, item.insert) - + def test_template_cannot_have_stock(self): - item = self.get_item(10) - - se = make_stock_entry(item_code=item.name, target="Stores - _TC", qty=1, incoming_rate=1) - - item.has_variants = 1 - item.append("variants", {"item_attribute": "Test Size", "item_attribute_value": "Small"}) - - self.assertRaises(ItemTemplateCannotHaveStock, item.save) - - def test_variant_item_codes(self): - item = self.get_item(11) - - variants = ['_Test Variant Item-S', '_Test Variant Item-M', '_Test Variant Item-L'] - self.assertEqual(item.get_variant_item_codes(), variants) - for v in variants: - self.assertTrue(frappe.db.get_value("Item", {"variant_of": item.name, "name": v})) - - item.append("variants", {"item_attribute": "Test Colour", "item_attribute_value": "Red"}) - item.append("variants", {"item_attribute": "Test Colour", "item_attribute_value": "Blue"}) - item.append("variants", {"item_attribute": "Test Colour", "item_attribute_value": "Green"}) - - self.assertEqual(item.get_variant_item_codes(), ['_Test Variant Item-S-R', - '_Test Variant Item-S-G', '_Test Variant Item-S-B', - '_Test Variant Item-M-R', '_Test Variant Item-M-G', - '_Test Variant Item-M-B', '_Test Variant Item-L-R', - '_Test Variant Item-L-G', '_Test Variant Item-L-B']) - - self.assertEqual(item.variant_attributes['_Test Variant Item-L-R'], [['Test Size', 'Large'], ['Test Colour', 'Red']]) - self.assertEqual(item.variant_attributes['_Test Variant Item-S-G'], [['Test Size', 'Small'], ['Test Colour', 'Green']]) - - # check stock entry cannot be made - def test_stock_entry_cannot_be_made_for_template(self): - item = self.get_item(11) - - se = frappe.new_doc("Stock Entry") - se.purpose = "Material Receipt" - se.append("items", { - "item_code": item.name, - "t_warehouse": "Stores - _TC", - "qty": 1, - "incoming_rate": 1 - }) - se.insert() - self.assertRaises(ItemTemplateCannotHaveStock, se.submit) - + item = self.get_item(10) + se = make_stock_entry(item_code=item.name, target="Stores - _TC", qty=1, incoming_rate=1) + item.has_variants = 1 + self.assertRaises(ItemTemplateCannotHaveStock, item.save) + def test_default_warehouse(self): item = frappe.copy_doc(test_records[0]) item.is_stock_item = "Yes" diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json index dc095c6b14..5a02c6bde5 100644 --- a/erpnext/stock/doctype/item/test_records.json +++ b/erpnext/stock/doctype/item/test_records.json @@ -273,11 +273,6 @@ "item_name": "_Test Variant Item", "stock_uom": "_Test UOM", "has_variants": 1, - "variants": [ - {"item_attribute": "Test Size", "item_attribute_value": "Small"}, - {"item_attribute": "Test Size", "item_attribute_value": "Medium"}, - {"item_attribute": "Test Size", "item_attribute_value": "Large"} - ], "apply_warehouse_wise_reorder_level": 1, "reorder_levels": [ { diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 913c2d8136..8b1a13b0a1 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -10,7 +10,6 @@ import copy import json class DuplicateAttribute(frappe.ValidationError): pass -class ItemTemplateCannotHaveStock(frappe.ValidationError): pass class ManageVariants(Document): @@ -23,7 +22,6 @@ class ManageVariants(Document): def generate_combinations(self): self.validate_attributes() self.validate_template_item() - self.validate_stock_for_template_must_be_zero() self.validate_attribute_values() self.validate_attributes_are_unique() self.get_variant_item_codes() @@ -54,7 +52,7 @@ class ManageVariants(Document): for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d): variant_attributes += attribute[1] + " | " attributes.append([attribute[0], attribute[1]]) - self.append('variants',{"variant": d, "variant_attributes": variant_attributes[: -2], "attributes": json.dumps(attributes)}) + self.append('variants',{"variant": d, "variant_attributes": variant_attributes[: -3], "attributes": json.dumps(attributes)}) def validate_attributes(self): if not self.attributes: @@ -64,17 +62,10 @@ class ManageVariants(Document): template_item = frappe.get_doc("Item", self.item) if not template_item.has_variants: frappe.throw(_("Selected Item cannot have Variants.")) - + if template_item.variant_of: frappe.throw(_("Item cannot be a variant of a variant")) - def validate_stock_for_template_must_be_zero(self): - stock_in = frappe.db.sql_list("""select warehouse from tabBin - where item_code=%s and ifnull(actual_qty, 0) > 0""", self.item) - if stock_in: - frappe.throw(_("Item Template cannot have stock and varaiants. Please remove \ - stock from warehouses {0}").format(", ".join(stock_in)), ItemTemplateCannotHaveStock) - def validate_attribute_values(self): attributes = {} for d in self.attributes: @@ -119,7 +110,7 @@ class ManageVariants(Document): for d in _my_attributes: variant_attributes += d[1] + " | " self.append('variants', {"variant": item_code + "-" + value.abbr, - "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes[: -2]}) + "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes[: -3]}) add_attribute_suffixes(self.item, [], attributes) def sync_variants(self): diff --git a/erpnext/stock/doctype/manage_variants/test_manage_variants.py b/erpnext/stock/doctype/manage_variants/test_manage_variants.py new file mode 100644 index 0000000000..e3624a29ad --- /dev/null +++ b/erpnext/stock/doctype/manage_variants/test_manage_variants.py @@ -0,0 +1,48 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import unittest +import frappe + +from erpnext.stock.doctype.manage_variants.manage_variants import DuplicateAttribute + +class TestManageVariants(unittest.TestCase): + def test_variant_item_codes(self): + manage_variant = frappe.new_doc("Manage Variants") + manage_variant.update({ + "item": "_Test Variant Item", + "attributes": [ + { + "attribute": "Test Size", + "attribute_value": "Small" + }, + { + "attribute": "Test Size", + "attribute_value": "Large" + } + ] + }) + manage_variant.generate_combinations() + self.assertEqual(manage_variant.variants[0].variant, "_Test Variant Item-S") + self.assertEqual(manage_variant.variants[1].variant, "_Test Variant Item-L") + + self.assertEqual(manage_variant.variants[0].variant_attributes, "Small") + self.assertEqual(manage_variant.variants[1].variant_attributes, "Large") + + def test_attributes_are_unique(self): + manage_variant = frappe.new_doc("Manage Variants") + manage_variant.update({ + "item": "_Test Variant Item", + "attributes": [ + { + "attribute": "Test Size", + "attribute_value": "Small" + }, + { + "attribute": "Test Size", + "attribute_value": "Small" + } + ] + }) + self.assertRaises(DuplicateAttribute, manage_variant.generate_combinations) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 74fd4d1140..de6b3a6950 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -8,7 +8,7 @@ from frappe import _ from frappe.utils import flt, getdate, add_days, formatdate from frappe.model.document import Document from datetime import date -from erpnext.stock.doctype.manage_variants.manage_variants import ItemTemplateCannotHaveStock +from erpnext.stock.doctype.item.item import ItemTemplateCannotHaveStock class StockFreezeError(frappe.ValidationError): pass From 203cb10ef759ceb9ba4a255ffe036c4beaaebb70 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 4 Jun 2015 17:07:04 +0530 Subject: [PATCH 07/14] fixes in test cases --- erpnext/stock/doctype/manage_variants/test_manage_variants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/manage_variants/test_manage_variants.py b/erpnext/stock/doctype/manage_variants/test_manage_variants.py index e3624a29ad..9952ba9a2b 100644 --- a/erpnext/stock/doctype/manage_variants/test_manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/test_manage_variants.py @@ -29,6 +29,7 @@ class TestManageVariants(unittest.TestCase): self.assertEqual(manage_variant.variants[0].variant_attributes, "Small") self.assertEqual(manage_variant.variants[1].variant_attributes, "Large") + manage_variant.create_variants() def test_attributes_are_unique(self): manage_variant = frappe.new_doc("Manage Variants") From c761fefb780d1b00412cb2db0603ec7855ea6b76 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 4 Jun 2015 17:37:26 +0530 Subject: [PATCH 08/14] more fixes in test records --- .../stock/doctype/stock_entry/test_stock_entry.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index ff0b272fd5..8114befa2f 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -72,6 +72,18 @@ class TestStockEntry(unittest.TestCase): self._test_auto_material_request("_Test Item") def test_auto_material_request_for_variant(self): + manage_variant = frappe.new_doc("Manage Variants") + manage_variant.update({ + "item": "_Test Variant Item", + "attributes": [ + { + "attribute": "Test Size", + "attribute_value": "Small" + } + ] + }) + manage_variant.generate_combinations() + manage_variant.create_variants() self._test_auto_material_request("_Test Variant Item-S") def _test_auto_material_request(self, item_code): From 3f2604eff69c29c4bcb28a409821f4ea3948a797 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 29 Jun 2015 15:06:46 +0530 Subject: [PATCH 09/14] Patch for Item Variants --- erpnext/patches.txt | 2 +- erpnext/patches/v5_0/item_variants.py | 11 +++++++++++ erpnext/stock/doctype/item/item.json | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v5_0/item_variants.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0f32a6dbf6..80965ece06 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -168,4 +168,4 @@ execute:frappe.delete_doc("Page", "users") erpnext.patches.v5_0.update_material_transferred_for_manufacturing_again erpnext.patches.v5_0.index_on_account_and_gl_entry execute:frappe.db.sql("""delete from `tabProject Task`""") - +erpnext.patches.v5_0.item_variants diff --git a/erpnext/patches/v5_0/item_variants.py b/erpnext/patches/v5_0/item_variants.py new file mode 100644 index 0000000000..fd4e4bbe0c --- /dev/null +++ b/erpnext/patches/v5_0/item_variants.py @@ -0,0 +1,11 @@ +import frappe + +def execute(): + frappe.reload_doctype("Item") + for d in frappe.get_list("Item", filters={"has_variants":1}): + manage_variant = frappe.new_doc("Manage Variants") + manage_variant.item = d.name + manage_variant.attributes = frappe.db.sql("select item_attribute as attribute, item_attribute_value as attribute_value \ + from `tabItem Variant` where parent = %s", d.name, as_dict=1) + manage_variant.generate_combinations() + manage_variant.create_variants() \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 9e7fb3dabe..a43b70467d 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -902,7 +902,7 @@ "icon": "icon-tag", "idx": 1, "max_attachments": 1, - "modified": "2015-06-26 17:20:18.204558", + "modified": "2015-06-29 17:20:18.204558", "modified_by": "Administrator", "module": "Stock", "name": "Item", From 724fd824192ce2c7ccbc4dec17efa14d846d6763 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 30 Jun 2015 12:52:39 +0530 Subject: [PATCH 10/14] Fixes in Manage Variants --- erpnext/patches/v5_0/item_variants.py | 3 ++- .../doctype/manage_variants/manage_variants.json | 12 ++++++++++-- .../__init__.py | 0 .../manage_variants_item.json} | 6 +++--- .../manage_variants_item.py} | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-) rename erpnext/stock/doctype/{variant_item => manage_variants_item}/__init__.py (100%) rename erpnext/stock/doctype/{variant_item/variant_item.json => manage_variants_item/manage_variants_item.json} (93%) rename erpnext/stock/doctype/{variant_item/variant_item.py => manage_variants_item/manage_variants_item.py} (88%) diff --git a/erpnext/patches/v5_0/item_variants.py b/erpnext/patches/v5_0/item_variants.py index fd4e4bbe0c..6e4db19432 100644 --- a/erpnext/patches/v5_0/item_variants.py +++ b/erpnext/patches/v5_0/item_variants.py @@ -8,4 +8,5 @@ def execute(): manage_variant.attributes = frappe.db.sql("select item_attribute as attribute, item_attribute_value as attribute_value \ from `tabItem Variant` where parent = %s", d.name, as_dict=1) manage_variant.generate_combinations() - manage_variant.create_variants() \ No newline at end of file + manage_variant.create_variants() + frappe.delete_doc("doctype", "Item Variant") \ No newline at end of file diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json index 1ad938d05b..055fc27442 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.json +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -17,6 +17,13 @@ "precision": "", "reqd": 1 }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "label": "Item Variant Attributes", + "permlevel": 0, + "precision": "" + }, { "allow_on_submit": 0, "fieldname": "attributes", @@ -48,6 +55,7 @@ { "fieldname": "section_break_4", "fieldtype": "Section Break", + "label": "Item Variants", "permlevel": 0, "precision": "" }, @@ -55,7 +63,7 @@ "fieldname": "variants", "fieldtype": "Table", "label": "Variants", - "options": "Variant Item", + "options": "Manage Variants Item", "permlevel": 0, "precision": "" } @@ -67,7 +75,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-05-28 06:18:03.238411", + "modified": "2015-06-30 03:18:13.787883", "modified_by": "Administrator", "module": "Stock", "name": "Manage Variants", diff --git a/erpnext/stock/doctype/variant_item/__init__.py b/erpnext/stock/doctype/manage_variants_item/__init__.py similarity index 100% rename from erpnext/stock/doctype/variant_item/__init__.py rename to erpnext/stock/doctype/manage_variants_item/__init__.py diff --git a/erpnext/stock/doctype/variant_item/variant_item.json b/erpnext/stock/doctype/manage_variants_item/manage_variants_item.json similarity index 93% rename from erpnext/stock/doctype/variant_item/variant_item.json rename to erpnext/stock/doctype/manage_variants_item/manage_variants_item.json index 13c46ac0f7..a8bb61d5e4 100644 --- a/erpnext/stock/doctype/variant_item/variant_item.json +++ b/erpnext/stock/doctype/manage_variants_item/manage_variants_item.json @@ -47,7 +47,7 @@ { "fieldname": "attributes", "fieldtype": "Text", - "hidden": 0, + "hidden": 1, "label": "attributes", "permlevel": 0, "precision": "", @@ -62,10 +62,10 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-05-29 02:09:33.622631", + "modified": "2015-06-30 03:19:07.548196", "modified_by": "Administrator", "module": "Stock", - "name": "Variant Item", + "name": "Manage Variants Item", "name_case": "", "owner": "Administrator", "permissions": [], diff --git a/erpnext/stock/doctype/variant_item/variant_item.py b/erpnext/stock/doctype/manage_variants_item/manage_variants_item.py similarity index 88% rename from erpnext/stock/doctype/variant_item/variant_item.py rename to erpnext/stock/doctype/manage_variants_item/manage_variants_item.py index 4aa4ed81a4..800888a864 100644 --- a/erpnext/stock/doctype/variant_item/variant_item.py +++ b/erpnext/stock/doctype/manage_variants_item/manage_variants_item.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -class VariantItem(Document): +class ManageVariantsItem(Document): pass From 274dd4ada05248c94d06dbe84cb1a8dbc0d4404d Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 30 Jun 2015 13:09:43 +0530 Subject: [PATCH 11/14] Fixed Manage Variants Attribute autocomplete appearance. --- erpnext/stock/doctype/manage_variants/manage_variants.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js index 9e6fff78b8..82a3da7f4d 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.js +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -5,6 +5,7 @@ frappe.ui.form.on("Manage Variants", { onload: function(frm) { var df = frappe.meta.get_docfield("Variant Attribute", "attribute_value"); df.on_make = function(field) { + $(field.input_area).addClass("ui-front"); field.$input.autocomplete({ minLength: 0, minChars: 0, From 0988440fd979c01c5ac6fab774c576af58ea0fbe Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 1 Jul 2015 12:50:52 +0530 Subject: [PATCH 12/14] Fixes for Manage Variants --- erpnext/patches/v5_0/item_variants.py | 12 ++++-- erpnext/stock/doctype/item/item.js | 2 +- erpnext/stock/doctype/item/item.py | 4 +- .../manage_variants/manage_variants.js | 2 +- .../manage_variants/manage_variants.json | 6 +-- .../manage_variants/manage_variants.py | 38 +++++++++---------- 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/erpnext/patches/v5_0/item_variants.py b/erpnext/patches/v5_0/item_variants.py index 6e4db19432..62e9ac9e1f 100644 --- a/erpnext/patches/v5_0/item_variants.py +++ b/erpnext/patches/v5_0/item_variants.py @@ -2,11 +2,15 @@ import frappe def execute(): frappe.reload_doctype("Item") + for dt in ["manage_variants", "manage_variants_item", "variant_attribute"]: + frappe.reload_doc("stock", "doctype", dt) + for d in frappe.get_list("Item", filters={"has_variants":1}): manage_variant = frappe.new_doc("Manage Variants") - manage_variant.item = d.name + manage_variant.item_code = d.name manage_variant.attributes = frappe.db.sql("select item_attribute as attribute, item_attribute_value as attribute_value \ from `tabItem Variant` where parent = %s", d.name, as_dict=1) - manage_variant.generate_combinations() - manage_variant.create_variants() - frappe.delete_doc("doctype", "Item Variant") \ No newline at end of file + if manage_variant.attributes: + manage_variant.generate_combinations() + manage_variant.create_variants() + frappe.delete_doc("DocType", "Item Variant") \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index d2f1d7527c..58b1adb8db 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -86,7 +86,7 @@ frappe.ui.form.on("Item", { }, manage_variants: function(frm) { - frappe.route_options = {"item": frm.doc.name }; + frappe.route_options = {"item_code": frm.doc.name }; frappe.set_route("List", "Manage Variants"); } }); diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 22e6b212f5..a24fc0476a 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -332,14 +332,14 @@ class Item(WebsiteGenerator): updated = [] variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name }) for d in variants: - update_variant(self.item_code, d) + update_variant(self.name, d) updated.append(d.item_code) frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) def validate_has_variants(self): if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"): if frappe.db.exists("Item", {"variant_of": self.name}): - frappe.throw("Item has variants.") + frappe.throw(_("Item has variants.")) def validate_stock_for_template_must_be_zero(self): if self.has_variants: diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js index 82a3da7f4d..ba5c46e8eb 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.js +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -43,7 +43,7 @@ frappe.ui.form.on("Manage Variants", { }); }, - item:function(frm) { + item_code:function(frm) { return frappe.call({ method: "get_item_details", doc:frm.doc, diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json index 055fc27442..7c61620b08 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.json +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -9,9 +9,9 @@ "document_type": "", "fields": [ { - "fieldname": "item", + "fieldname": "item_code", "fieldtype": "Link", - "label": "Item", + "label": "Item Code", "options": "Item", "permlevel": 0, "precision": "", @@ -75,7 +75,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-06-30 03:18:13.787883", + "modified": "2015-06-30 13:40:59.946655", "modified_by": "Administrator", "module": "Stock", "name": "Manage Variants", diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 8b1a13b0a1..0131deb0de 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -15,7 +15,7 @@ class ManageVariants(Document): def get_item_details(self): self.clear_tables() - if self.item: + if self.item_code: self.get_attributes() self.get_variants() @@ -37,7 +37,7 @@ class ManageVariants(Document): attributes = {} self.set('attributes', []) for d in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` as attribute, - `tabItem` as item where attribute.parent= item.name and item.variant_of = %s""", self.item, as_dict=1): + `tabItem` as item where attribute.parent= item.name and item.variant_of = %s""", self.item_code, as_dict=1): attributes.setdefault(d.attribute, []).append(d.attribute_value) for d in attributes: attribute_values = set(attributes[d]) @@ -46,24 +46,25 @@ class ManageVariants(Document): def get_variants(self): variants = [d.name for d in frappe.get_all("Item", - filters={"variant_of":self.item})] + filters={"variant_of":self.item_code})] + data = frappe.db.sql("""select parent, attribute, attribute_value from `tabVariant Attribute`""", as_dict=1) for d in variants: variant_attributes, attributes = "", [] - for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d): - variant_attributes += attribute[1] + " | " - attributes.append([attribute[0], attribute[1]]) + for attribute in data: + if attribute.parent == d: + variant_attributes += attribute.attribute_value + " | " + attributes.append([attribute.attribute, attribute.attribute_value]) self.append('variants',{"variant": d, "variant_attributes": variant_attributes[: -3], "attributes": json.dumps(attributes)}) def validate_attributes(self): if not self.attributes: - frappe.throw("Enter atleast one Attribute & its Value in Attribute table.") + frappe.throw(_("Enter atleast one Attribute & its Value in Attribute table.")) def validate_template_item(self): - template_item = frappe.get_doc("Item", self.item) - if not template_item.has_variants: + if not frappe.db.get_value("Item", self.item_code, "has_variants"): frappe.throw(_("Selected Item cannot have Variants.")) - if template_item.variant_of: + if frappe.db.get_value("Item", self.item_code, "variant_of"): frappe.throw(_("Item cannot be a variant of a variant")) def validate_attribute_values(self): @@ -111,7 +112,7 @@ class ManageVariants(Document): variant_attributes += d[1] + " | " self.append('variants', {"variant": item_code + "-" + value.abbr, "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes[: -3]}) - add_attribute_suffixes(self.item, [], attributes) + add_attribute_suffixes(self.item_code, [], attributes) def sync_variants(self): variant_item_codes = [] @@ -122,7 +123,7 @@ class ManageVariants(Document): variant_item_codes.append(v.variant) existing_variants = [d.name for d in frappe.get_all("Item", - filters={"variant_of":self.item})] + filters={"variant_of":self.item_code})] for d in existing_variants: attributes = [] @@ -147,10 +148,10 @@ class ManageVariants(Document): for item_code in variant_item_codes: if item_code not in existing_variants: if item_code not in new_variant_name: - make_variant(self.item, item_code, self.variants) + make_variant(self.item_code, item_code, self.variants) inserted.append(item_code) else: - update_variant(self.item, item_code, self.variants) + update_variant(self.item_code, item_code, self.variants) updated.append(item_code) if inserted: @@ -168,14 +169,12 @@ class ManageVariants(Document): def make_variant(item, variant_code, variant_attribute): variant = frappe.new_doc("Item") variant.item_code = variant_code - template = frappe.get_doc("Item", item) - copy_attributes_to_variant(template, variant, variant_attribute, insert=True) + copy_attributes_to_variant(item, variant, variant_attribute, insert=True) variant.insert() def update_variant(item, variant_code, variant_attribute=None): variant = frappe.get_doc("Item", variant_code) - template = frappe.get_doc("Item", item) - copy_attributes_to_variant(template, variant, variant_attribute, insert=True) + copy_attributes_to_variant(item, variant, variant_attribute, insert=True) variant.save() def rename_variant(old_variant_code, new_variant_code): @@ -184,7 +183,8 @@ def rename_variant(old_variant_code, new_variant_code): def delete_variant(variant_code): frappe.delete_doc("Item", variant_code) -def copy_attributes_to_variant(template, variant, variant_attribute=None, insert=False): +def copy_attributes_to_variant(item, variant, variant_attribute=None, insert=False): + template = frappe.get_doc("Item", item) from frappe.model import no_value_fields for field in template.meta.fields: if field.fieldtype not in no_value_fields and (insert or not field.no_copy)\ From 64fbe955c7ab255ebbf6652a27f5486bf741da8c Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 1 Jul 2015 14:04:00 +0530 Subject: [PATCH 13/14] Modified Date changed for Item Doctype --- erpnext/stock/doctype/item/item.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index a43b70467d..acf2764e93 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -902,7 +902,7 @@ "icon": "icon-tag", "idx": 1, "max_attachments": 1, - "modified": "2015-06-29 17:20:18.204558", + "modified": "2015-07-01 17:20:18.204558", "modified_by": "Administrator", "module": "Stock", "name": "Item", From fe9cd1d8755ebe263990ec8a6e24cc5ee128bfba Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 1 Jul 2015 14:41:30 +0530 Subject: [PATCH 14/14] More Fixes in Manage Varients --- erpnext/stock/doctype/manage_variants/manage_variants.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 0131deb0de..b6784d3a2e 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -69,10 +69,10 @@ class ManageVariants(Document): def validate_attribute_values(self): attributes = {} + for t in frappe.db.get_all("Item Attribute Value", fields=["parent", "attribute_value"]): + attributes.setdefault(t.parent, []).append(t.attribute_value) + for d in self.attributes: - attributes.setdefault(d.attribute, - [t.attribute_value for t in - frappe.db.get_all("Item Attribute Value", fields=["attribute_value"], filters={"parent": d.attribute })]) if d.attribute_value not in attributes.get(d.attribute): frappe.throw(_("Attribute value {0} does not exist in Item Attribute Master.").format(d.attribute_value))