item variants, creation, deleation and update logic added.

logic added to copy changes in template to variants
This commit is contained in:
Neil Trini Lasrado 2015-05-22 17:00:58 +05:30
parent 333ccd212b
commit 8fb123b20e
8 changed files with 204 additions and 125 deletions

View File

@ -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");
}
});

View File

@ -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",

View File

@ -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 += "<p>" + attr[0] + ": " + attr[1] + "</p>"
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:

View File

@ -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');
}
})
}
});

View File

@ -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",

View File

@ -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
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 += "<p>" + d.attribute + ": " + d.attribute_value + "</p>"

View File

@ -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

View File

@ -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",