Attributes in the variant should be in the same order as in the Template

- Moved Item Variant code from item.py to controllers/item_variant.py
- Use Template Item's order of Attribute for Variant Item
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
new file mode 100644
index 0000000..4edf52b
--- /dev/null
+++ b/erpnext/controllers/item_variant.py
@@ -0,0 +1,168 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import cstr, flt
+import json
+
+class ItemVariantExistsError(frappe.ValidationError): pass
+class InvalidItemAttributeValueError(frappe.ValidationError): pass
+class ItemTemplateCannotHaveStock(frappe.ValidationError): pass
+
+@frappe.whitelist()
+def get_variant(item, args):
+	"""Validates Attributes and their Values, then looks for an exactly matching Item Variant
+
+		:param item: Template Item
+		:param args: A dictionary with "Attribute" as key and "Attribute Value" as value
+	"""
+	if isinstance(args, basestring):
+		args = json.loads(args)
+
+	if not args:
+		frappe.throw(_("Please specify at least one attribute in the Attributes table"))
+
+	validate_item_variant_attributes(item, args)
+
+	return find_variant(item, args)
+
+def validate_item_variant_attributes(item, args):
+	attribute_values = {}
+	for t in frappe.get_all("Item Attribute Value", fields=["parent", "attribute_value"],
+		filters={"parent": ["in", args.keys()]}):
+		(attribute_values.setdefault(t.parent, [])).append(t.attribute_value)
+
+	numeric_attributes = frappe._dict((t.attribute, t) for t in \
+		frappe.db.sql("""select attribute, from_range, to_range, increment from `tabItem Variant Attribute`
+		where parent = %s and numeric_values=1""", (item), as_dict=1))
+
+	for attribute, value in args.items():
+		if attribute in numeric_attributes:
+			numeric_attribute = numeric_attributes[attribute]
+
+			from_range = numeric_attribute.from_range
+			to_range = numeric_attribute.to_range
+			increment = numeric_attribute.increment
+
+			if increment == 0:
+				# defensive validation to prevent ZeroDivisionError
+				frappe.throw(_("Increment for Attribute {0} cannot be 0").format(attribute))
+
+			is_in_range = from_range <= flt(value) <= to_range
+			precision = len(cstr(increment).split(".")[-1].rstrip("0"))
+			#avoid precision error by rounding the remainder
+			remainder = flt((flt(value) - from_range) % increment, precision)
+
+			is_incremental = remainder==0 or remainder==0 or remainder==increment
+
+			if not (is_in_range and is_incremental):
+				frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3}")\
+					.format(attribute, from_range, to_range, increment), InvalidItemAttributeValueError)
+
+		elif value not in attribute_values[attribute]:
+			frappe.throw(_("Value {0} for Attribute {1} does not exist in the list of valid Item Attribute Values").format(
+				value, attribute))
+
+def find_variant(item, args):
+	conditions = ["""(iv_attribute.attribute="{0}" and iv_attribute.attribute_value="{1}")"""\
+		.format(frappe.db.escape(key), frappe.db.escape(cstr(value))) for key, value in args.items()]
+
+	conditions = " or ".join(conditions)
+
+	# use approximate match and shortlist possible variant matches
+	# it is approximate because we are matching using OR condition
+	# and it need not be exact match at this stage
+	# this uses a simpler query instead of using multiple exists conditions
+	possible_variants = frappe.db.sql_list("""select name from `tabItem` item
+		where variant_of=%s and exists (
+			select name from `tabItem Variant Attribute` iv_attribute
+				where iv_attribute.parent=item.name
+				and ({conditions})
+		)""".format(conditions=conditions), item)
+
+	for variant in possible_variants:
+		variant = frappe.get_doc("Item", variant)
+
+		if len(args.keys()) == len(variant.get("attributes")):
+			# has the same number of attributes and values
+			# assuming no duplication as per the validation in Item
+			match_count = 0
+
+			for attribute, value in args.items():
+				for row in variant.attributes:
+					if row.attribute==attribute and row.attribute_value== cstr(value):
+						# this row matches
+						match_count += 1
+						break
+
+			if match_count == len(args.keys()):
+				return variant.name
+
+@frappe.whitelist()
+def create_variant(item, args):
+	if isinstance(args, basestring):
+		args = json.loads(args)
+
+	template = frappe.get_doc("Item", item)
+	variant = frappe.new_doc("Item")
+	variant_attributes = []
+
+	for d in template.attributes:
+		variant_attributes.append({
+			"attribute": d.attribute,
+			"attribute_value": args.get(d.attribute)
+		})
+
+	variant.set("attributes", variant_attributes)
+	copy_attributes_to_variant(template, variant)
+	make_variant_item_code(template, variant)
+
+	return variant
+
+def copy_attributes_to_variant(item, variant):
+	from frappe.model import no_value_fields
+	for field in item.meta.fields:
+		if field.fieldtype not in no_value_fields and (not field.no_copy)\
+			and field.fieldname not in ("item_code", "item_name"):
+			if variant.get(field.fieldname) != item.get(field.fieldname):
+				variant.set(field.fieldname, item.get(field.fieldname))
+	variant.variant_of = item.name
+	variant.has_variants = 0
+	variant.show_in_website = 0
+	if variant.attributes:
+		variant.description += "\n"
+		for d in variant.attributes:
+			variant.description += "<p>" + d.attribute + ": " + cstr(d.attribute_value) + "</p>"
+
+def make_variant_item_code(template, variant):
+	"""Uses template's item code and abbreviations to make variant's item code"""
+	if variant.item_code:
+		return
+
+	abbreviations = []
+	for attr in variant.attributes:
+		item_attribute = frappe.db.sql("""select i.numeric_values, v.abbr
+			from `tabItem Attribute` i left join `tabItem Attribute Value` v
+				on (i.name=v.parent)
+			where i.name=%(attribute)s and v.attribute_value=%(attribute_value)s""", {
+				"attribute": attr.attribute,
+				"attribute_value": attr.attribute_value
+			}, as_dict=True)
+
+		if not item_attribute:
+			# somehow an invalid item attribute got used
+			return
+
+		if item_attribute[0].numeric_values:
+			# don't generate item code if one of the attributes is numeric
+			return
+
+		abbreviations.append(item_attribute[0].abbr)
+
+	if abbreviations:
+		variant.item_code = "{0}-{1}".format(template.item_code, "-".join(abbreviations))
+
+	if variant.item_code:
+		variant.item_name = variant.item_code