item variants, creation, deleation and update logic added.
logic added to copy changes in template to variants
This commit is contained in:
parent
333ccd212b
commit
8fb123b20e
@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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:
|
||||
|
@ -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');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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>"
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user