From c8cc8b7115033c9e8bd4ce4c919aebd26381ba89 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 20 May 2015 15:49:55 +0530 Subject: [PATCH] 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 5da3d35443a..9db24f143a0 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 8b10319f86e..6ff8ac38675 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 aa463eee775..77bcb4d668f 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 00000000000..e69de29bb2d 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 00000000000..f579897765d --- /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 00000000000..b00266a5cd6 --- /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 00000000000..4169cae243f --- /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 00000000000..e69de29bb2d 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 00000000000..5ab3d73239e --- /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 00000000000..9c35732bf2f --- /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 00000000000..e69de29bb2d 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 00000000000..8e1c8621e21 --- /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 00000000000..4aa4ed81a47 --- /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