Merge branch 'develop' into multi-barcode
diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py
index 63db16c..e37dbbf 100644
--- a/erpnext/accounts/doctype/sales_invoice/pos.py
+++ b/erpnext/accounts/doctype/sales_invoice/pos.py
@@ -2,54 +2,64 @@
 # License: GNU General Public License v3. See license.txt
 
 from __future__ import unicode_literals
-import frappe, json
-from frappe import _
-from frappe.utils import nowdate
-from erpnext.setup.utils import get_exchange_rate
-from frappe.core.doctype.communication.email import make
-from erpnext.stock.get_item_details import get_pos_profile
+
+import json
+
+import frappe
 from erpnext.accounts.party import get_party_account_currency
 from erpnext.controllers.accounts_controller import get_taxes_and_charges
+from erpnext.setup.utils import get_exchange_rate
+from erpnext.stock.get_item_details import get_pos_profile
+from frappe import _
+from frappe.core.doctype.communication.email import make
+from frappe.utils import nowdate
+
 
 @frappe.whitelist()
 def get_pos_data():
-	doc = frappe.new_doc('Sales Invoice')
-	doc.is_pos = 1;
-	pos_profile = get_pos_profile(doc.company) or {}
-	if not pos_profile:
-		frappe.throw(_("POS Profile is required to use Point-of-Sale"))
-	if not doc.company: doc.company = pos_profile.get('company')
-	doc.update_stock = pos_profile.get('update_stock')
+    doc = frappe.new_doc('Sales Invoice')
+    doc.is_pos = 1
+    pos_profile = get_pos_profile(doc.company) or {}
+    if not pos_profile:
+        frappe.throw(_("POS Profile is required to use Point-of-Sale"))
 
-	if pos_profile.get('name'):
-		pos_profile = frappe.get_doc('POS Profile', pos_profile.get('name'))
-		pos_profile.validate()
+    if not doc.company:
+        doc.company = pos_profile.get('company')
 
-	company_data = get_company_data(doc.company)
-	update_pos_profile_data(doc, pos_profile, company_data)
-	update_multi_mode_option(doc, pos_profile)
-	default_print_format = pos_profile.get('print_format') or "Point of Sale"
-	print_template = frappe.db.get_value('Print Format', default_print_format, 'html')
-	customers = get_customers_list(pos_profile)
+    doc.update_stock = pos_profile.get('update_stock')
 
-	return {
-		'doc': doc,
-		'default_customer': pos_profile.get('customer'),
-		'items': get_items_list(pos_profile),
-		'item_groups': get_item_groups(pos_profile),
-		'customers': customers,
-		'address': get_customers_address(customers),
-		'contacts': get_contacts(customers),
-		'serial_no_data': get_serial_no_data(pos_profile, doc.company),
-		'batch_no_data': get_batch_no_data(),
-		'tax_data': get_item_tax_data(),
-		'price_list_data': get_price_list_data(doc.selling_price_list),
-		'bin_data': get_bin_data(pos_profile),
-		'pricing_rules': get_pricing_rule_data(doc),
-		'print_template': print_template,
-		'pos_profile': pos_profile,
-		'meta': get_meta()
-	}
+    if pos_profile.get('name'):
+        pos_profile = frappe.get_doc('POS Profile', pos_profile.get('name'))
+        pos_profile.validate()
+
+    company_data = get_company_data(doc.company)
+    update_pos_profile_data(doc, pos_profile, company_data)
+    update_multi_mode_option(doc, pos_profile)
+    default_print_format = pos_profile.get('print_format') or "Point of Sale"
+    print_template = frappe.db.get_value('Print Format', default_print_format, 'html')
+    items_list = get_items_list(pos_profile)
+    customers = get_customers_list(pos_profile)
+
+    return {
+        'doc': doc,
+        'default_customer': pos_profile.get('customer'),
+        'items': items_list,
+        'item_groups': get_item_groups(pos_profile),
+        'customers': customers,
+        'address': get_customers_address(customers),
+        'contacts': get_contacts(customers),
+        'serial_no_data': get_serial_no_data(pos_profile, doc.company),
+        'batch_no_data': get_batch_no_data(),
+        'barcode_data': get_barcode_data(items_list),
+        'tax_data': get_item_tax_data(),
+        'price_list_data': get_price_list_data(doc.selling_price_list),
+        'bin_data': get_bin_data(pos_profile),
+        'pricing_rules': get_pricing_rule_data(doc),
+        'print_template': print_template,
+        'pos_profile': pos_profile,
+        'meta': get_meta()
+    }
+
 
 def get_meta():
 	doctype_meta = {
@@ -57,14 +67,16 @@
 		'invoice': frappe.get_meta('Sales Invoice')
 	}
 
-	for row in frappe.get_all('DocField', fields = ['fieldname', 'options'],
-		filters = {'parent': 'Sales Invoice', 'fieldtype': 'Table'}):
+	for row in frappe.get_all('DocField', fields=['fieldname', 'options'],
+		filters={'parent': 'Sales Invoice', 'fieldtype': 'Table'}):
 		doctype_meta[row.fieldname] = frappe.get_meta(row.options)
 
 	return doctype_meta
 
+
 def get_company_data(company):
-	return frappe.get_all('Company', fields = ["*"], filters= {'name': company})[0]
+	return frappe.get_all('Company', fields=["*"], filters={'name': company})[0]
+
 
 def update_pos_profile_data(doc, pos_profile, company_data):
 	doc.campaign = pos_profile.get('campaign')
@@ -93,15 +105,18 @@
 	doc.apply_discount_on = pos_profile.get('apply_discount_on') or 'Grand Total'
 	doc.customer_group = pos_profile.get('customer_group') or get_root('Customer Group')
 	doc.territory = pos_profile.get('territory') or get_root('Territory')
-	doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get('tc_name'), 'terms') or doc.terms or ''
+	doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get(
+	    'tc_name'), 'terms') or doc.terms or ''
 	doc.offline_pos_name = ''
 
+
 def get_root(table):
 	root = frappe.db.sql(""" select name from `tab%(table)s` having
-		min(lft)"""%{'table': table}, as_dict=1)
+		min(lft)""" % {'table': table}, as_dict=1)
 
 	return root[0].name
 
+
 def update_multi_mode_option(doc, pos_profile):
 	from frappe.model import default_fields
 
@@ -123,15 +138,18 @@
 
 		doc.append('payments', payment_mode)
 
+
 def get_mode_of_payment(doc):
 	return frappe.db.sql(""" select mpa.default_account, mpa.parent, mp.type as type from `tabMode of Payment Account` mpa,
 		 `tabMode of Payment` mp where mpa.parent = mp.name and mpa.company = %(company)s""", {'company': doc.company}, as_dict=1)
 
+
 def update_tax_table(doc):
 	taxes = get_taxes_and_charges('Sales Taxes and Charges Template', doc.taxes_and_charges)
 	for tax in taxes:
 		doc.append('taxes', tax)
 
+
 def get_items_list(pos_profile):
 	cond = "1=1"
 	item_groups = []
@@ -139,19 +157,20 @@
 		# Get items based on the item groups defined in the POS profile
 		for d in pos_profile.get('item_groups'):
 			item_groups.extend([d.name for d in get_child_nodes('Item Group', d.item_group)])
-		cond = "item_group in (%s)"%(', '.join(['%s']*len(item_groups)))
+		cond = "item_group in (%s)" % (', '.join(['%s'] * len(item_groups)))
 
-	return frappe.db.sql(""" 
+	return frappe.db.sql("""
 		select
 			name, item_code, item_name, description, item_group, expense_account, has_batch_no,
-			has_serial_no, expense_account, selling_cost_center, stock_uom, image, 
-			default_warehouse, is_stock_item, barcode, brand
+			has_serial_no, expense_account, selling_cost_center, stock_uom, image,
+			default_warehouse, is_stock_item, brand
 		from
 			tabItem
 		where
 			disabled = 0 and has_variants = 0 and is_sales_item = 1 and {cond}
 		""".format(cond=cond), tuple(item_groups), as_dict=1)
 
+
 def get_item_groups(pos_profile):
 	item_group_dict = {}
 	item_groups = frappe.db.sql("""Select name,
@@ -161,6 +180,7 @@
 		item_group_dict[data.name] = [data.lft, data.rgt]
 	return item_group_dict
 
+
 def get_customers_list(pos_profile={}):
 	cond = "1=1"
 	customer_groups = []
@@ -168,12 +188,13 @@
 		# Get customers based on the customer groups defined in the POS profile
 		for d in pos_profile.get('customer_groups'):
 			customer_groups.extend([d.name for d in get_child_nodes('Customer Group', d.customer_group)])
-		cond = "customer_group in (%s)"%(', '.join(['%s']*len(customer_groups)))
+		cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups)))
 
 	return frappe.db.sql(""" select name, customer_name, customer_group,
 		territory, customer_pos_id from tabCustomer where disabled = 0
 		and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {}
 
+
 def get_customers_address(customers):
 	customer_address = {}
 	if isinstance(customers, basestring):
@@ -192,26 +213,29 @@
 
 	return customer_address
 
+
 def get_contacts(customers):
 	customer_contact = {}
 	if isinstance(customers, basestring):
 		customers = [frappe._dict({'name': customers})]
 
 	for data in customers:
-		contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact` 
+		contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact`
 			where is_primary_contact =1 and name in
 			(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
 			and parenttype = 'Contact')""", data.name, as_dict=1)
-		if contact: 
+		if contact:
 			customer_contact[data.name] = contact[0]
 
 	return customer_contact
 
+
 def get_child_nodes(group_type, root):
 	lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
 	return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
 			lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
 
+
 def get_serial_no_data(pos_profile, company):
 	# get itemwise serial no data
 	# example {'Nokia Lumia 1020': {'SN0001': 'Pune'}}
@@ -232,6 +256,7 @@
 
 	return itemwise_serial_no
 
+
 def get_batch_no_data():
 	# get itemwise batch no data
 	# exmaple: {'LED-GRE': [Batch001, Batch002]}
@@ -248,6 +273,26 @@
 
 	return itemwise_batch
 
+
+def get_barcode_data(items_list):
+    # get itemwise batch no data
+    # exmaple: {'LED-GRE': [Batch001, Batch002]}
+    # where LED-GRE is item code, SN0001 is serial no and Pune is warehouse
+
+    itemwise_barcode = {}
+    for item in items_list:
+        barcodes = frappe.db.sql("""
+        select barcode from `tabItem Barcode` where parent = '{0}'
+        """.format(item.item_code), as_dict=1)
+
+        for barcode in barcodes:
+            if item.item_code not in itemwise_barcode:
+                itemwise_barcode.setdefault(item.item_code, [])
+            itemwise_barcode[item.item_code].append(barcode)
+
+    return itemwise_barcode
+
+
 def get_item_tax_data():
 	# get default tax of an item
 	# example: {'Consulting Services': {'Excise 12 - TS': '12.000'}}
@@ -425,7 +470,7 @@
 
 def make_address(args, customer):
 	if not args.get('address_line1'): return
-	
+
 	name = args.get('name')
 
 	if not name:
diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js
index 558dd8d..b2449fd 100644
--- a/erpnext/accounts/page/pos/pos.js
+++ b/erpnext/accounts/page/pos/pos.js
@@ -296,6 +296,7 @@
 		this.customers = r.message.customers;
 		this.serial_no_data = r.message.serial_no_data;
 		this.batch_no_data = r.message.batch_no_data;
+		this.barcode_data = r.message.barcode_data;
 		this.tax_data = r.message.tax_data;
 		this.contacts = r.message.contacts;
 		this.address = r.message.address || {};
@@ -410,7 +411,7 @@
 		});
 
 		this.serach_item.make_input();
-		
+
 		this.serach_item.$input.on("keypress", function (event) {
 
 			clearTimeout(me.last_search_timeout);
@@ -418,7 +419,7 @@
 				if((me.serach_item.$input.val() != "") || (event.which == 13)) {
 					me.items = me.get_items();
 					me.make_item_list();
-				}				
+				}
 			}, 400);
 		});
 
@@ -1105,9 +1106,9 @@
 						search_status = false;
 						me.item_serial_no[item.item_code] = [me.serach_item.$input.val(), me.serial_no_data[item.item_code][me.serach_item.$input.val()]]
 						return true
-					} else if (item.barcode == me.serach_item.$input.val()) {
+					} else if (in_list(me.barcode_data[item.item_code], me.serach_item.$input.val())) {
 						search_status = false;
-						return item.barcode == me.serach_item.$input.val();
+						return true;
 					} else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) ||
 						reg.test(item.item_name.toLowerCase()) || reg.test(item.item_group.toLowerCase())) {
 						return true
@@ -1512,8 +1513,8 @@
 				me.print_document(html)
 			})
 		}
-		
-		if (this.frm.doc.docstatus == 1) {	
+
+		if (this.frm.doc.docstatus == 1) {
 			this.page.add_menu_item(__("Email"), function () {
 				me.email_prompt()
 			})
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index e176472..1401d37 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -12,7 +12,7 @@
 from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
 from erpnext.exceptions import InvalidCurrency
 
-force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
+force_item_fields = ("item_group", "barcodes", "brand", "stock_uom")
 
 class AccountsController(TransactionBase):
 	def __init__(self, *args, **kwargs):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index dd95026..35b4f53 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -165,8 +165,8 @@
 			and (tabItem.`{key}` LIKE %(txt)s
 				or tabItem.item_group LIKE %(txt)s
 				or tabItem.item_name LIKE %(txt)s
-				or tabItem.barcode LIKE %(txt)s
 				or tabItem.description LIKE %(txt)s)
+                or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s)
 			{fcond} {mcond}
 		order by
 			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 5c2c112..2c3cce3 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -485,4 +485,5 @@
 erpnext.patches.v10_0.update_asset_calculate_depreciation
 erpnext.patches.v10_0.add_guardian_role_for_parent_portal
 erpnext.patches.v10_0.set_numeric_ranges_in_template_if_blank
-erpnext.patches.v10_0.update_reserved_qty_for_purchase_order
\ No newline at end of file
+erpnext.patches.v10_0.update_reserved_qty_for_purchase_order
+erpnext.patches.v10_0.item_barcode_childtable_migrate
diff --git a/erpnext/patches/v10_0/item_barcode_childtable_migrate.py b/erpnext/patches/v10_0/item_barcode_childtable_migrate.py
new file mode 100644
index 0000000..d36012d
--- /dev/null
+++ b/erpnext/patches/v10_0/item_barcode_childtable_migrate.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2017, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+    items_barcode = frappe.db.sql("SELECT name, barcode FROM tabItem WHERE barcode IS NOT NULL", as_dict=1)
+
+    for item in items_barcode:
+        doc = frappe.get_doc("Item", item.name)
+        doc.append("barcodes", {"barcode": item.get("barcode")})
+        doc.save()
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index d98a017..8801696 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -28,7 +28,7 @@
 				batch_no, item_code = batch_no_data
 
 		if not serial_no and not batch_no:
-			barcode_data = frappe.db.get_value('Item', {'barcode': search_value}, ['name', 'barcode'])
+			barcode_data = frappe.db.get_value('Item Barcode', {'barcode': search_value}, ['parent', 'barcode'])
 			if barcode_data:
 				item_code, barcode = barcode_data
 
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index eb00e5c..0bac8cf 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -178,35 +178,6 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "barcode", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Barcode", 
-   "length": 0, 
-   "no_copy": 1, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
    "description": "", 
    "fieldname": "item_group", 
    "fieldtype": "Link", 
@@ -705,6 +676,67 @@
    "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "sb_barcodes", 
+   "fieldtype": "Section Break", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Barcodes", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "barcodes", 
+   "fieldtype": "Table", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Barcodes", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Item Barcode", 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
    "collapsible": 1, 
    "collapsible_depends_on": "is_stock_item", 
    "columns": 0, 
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index df90ad9..6836793 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -2,744 +2,777 @@
 # License: GNU General Public License v3. See license.txt
 
 from __future__ import unicode_literals
-import frappe
-import erpnext
-import json
-import itertools
-from frappe import msgprint, _
-from frappe.utils import (cstr, flt, cint, getdate, now_datetime, formatdate,
-	strip, get_timestamp, random_string)
-from frappe.utils.html_utils import clean_html
-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
-from erpnext.controllers.item_variant import (get_variant, copy_attributes_to_variant,
-	make_variant_item_code, validate_item_variant_attributes, ItemVariantExistsError)
 
-class DuplicateReorderRows(frappe.ValidationError): pass
-class StockExistsForTemplate(frappe.ValidationError): pass
+import itertools
+import json
+
+import erpnext
+import frappe
+from erpnext.controllers.item_variant import (ItemVariantExistsError,
+                                              copy_attributes_to_variant,
+                                              get_variant,
+                                              make_variant_item_code,
+                                              validate_item_variant_attributes)
+from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups,
+                                                         invalidate_cache_for)
+from frappe import _, msgprint
+from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
+                          now_datetime, random_string, strip)
+from frappe.utils.html_utils import clean_html
+from frappe.website.doctype.website_slideshow.website_slideshow import \
+    get_slideshow
+from frappe.website.render import clear_cache
+from frappe.website.website_generator import WebsiteGenerator
+
+
+class DuplicateReorderRows(frappe.ValidationError):
+    pass
+
+
+class StockExistsForTemplate(frappe.ValidationError):
+    pass
+
 
 class Item(WebsiteGenerator):
-	website = frappe._dict(
-		page_title_field = "item_name",
-		condition_field = "show_in_website",
-		template = "templates/generators/item.html",
-		no_cache = 1
-	)
+    website = frappe._dict(
+        page_title_field="item_name",
+        condition_field="show_in_website",
+        template="templates/generators/item.html",
+        no_cache=1
+    )
 
-	def onload(self):
-		super(Item, self).onload()
+    def onload(self):
+        super(Item, self).onload()
 
-		self.set_onload('stock_exists', self.stock_ledger_created())
-		if self.is_fixed_asset:
-			asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1)
-			self.set_onload("asset_exists", True if asset else False)
+        self.set_onload('stock_exists', self.stock_ledger_created())
+        if self.is_fixed_asset:
+            asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1)
+            self.set_onload("asset_exists", True if asset else False)
 
-	def autoname(self):
-		if frappe.db.get_default("item_naming_by")=="Naming Series":
-			if self.variant_of:
-				if not self.item_code:
-					template_item_name = frappe.db.get_value("Item", self.variant_of, "item_name")
-					self.item_code = make_variant_item_code(self.variant_of, template_item_name, self)
-			else:
-				from frappe.model.naming import make_autoname
-				self.item_code = make_autoname(self.naming_series+'.#####')
-		elif not self.item_code:
-			msgprint(_("Item Code is mandatory because Item is not automatically numbered"), raise_exception=1)
+    def autoname(self):
+        if frappe.db.get_default("item_naming_by") == "Naming Series":
+            if self.variant_of:
+                if not self.item_code:
+                    template_item_name = frappe.db.get_value("Item", self.variant_of, "item_name")
+                    self.item_code = make_variant_item_code(self.variant_of, template_item_name, self)
+            else:
+                from frappe.model.naming import make_autoname
+                self.item_code = make_autoname(self.naming_series + '.#####')
+        elif not self.item_code:
+            msgprint(_("Item Code is mandatory because Item is not automatically numbered"), raise_exception=1)
 
-		self.item_code = strip(self.item_code)
-		self.name = self.item_code
+        self.item_code = strip(self.item_code)
+        self.name = self.item_code
 
-	def before_insert(self):
-		if not self.description:
-			self.description = self.item_name
+    def before_insert(self):
+        if not self.description:
+            self.description = self.item_name
 
-		if self.is_sales_item and not self.get('is_item_from_hub'):
-			self.publish_in_hub = 1
+        if self.is_sales_item and not self.get('is_item_from_hub'):
+            self.publish_in_hub = 1
 
-	def after_insert(self):
-		'''set opening stock and item price'''
-		if self.standard_rate:
-			self.add_price()
+    def after_insert(self):
+        '''set opening stock and item price'''
+        if self.standard_rate:
+            self.add_price()
 
-		if self.opening_stock:
-			self.set_opening_stock()
+        if self.opening_stock:
+            self.set_opening_stock()
 
-	def validate(self):
-		self.get_doc_before_save()
+    def validate(self):
+        self.get_doc_before_save()
 
-		super(Item, self).validate()
+        super(Item, self).validate()
 
-		if not self.item_name:
-			self.item_name = self.item_code
+        if not self.item_name:
+            self.item_name = self.item_code
 
-		if not self.description:
-			self.description = self.item_name
+        if not self.description:
+            self.description = self.item_name
 
-		self.validate_uom()
-		self.validate_description()
-		self.add_default_uom_in_conversion_factor_table()
-		self.validate_conversion_factor()
-		self.validate_item_type()
-		self.check_for_active_boms()
-		self.fill_customer_code()
-		self.check_item_tax()
-		self.validate_barcode()
-		self.validate_warehouse_for_reorder()
-		self.update_bom_item_desc()
-		self.synced_with_hub = 0
+        self.validate_uom()
+        self.validate_description()
+        self.add_default_uom_in_conversion_factor_table()
+        self.validate_conversion_factor()
+        self.validate_item_type()
+        self.check_for_active_boms()
+        self.fill_customer_code()
+        self.check_item_tax()
+        self.validate_barcode()
+        self.validate_warehouse_for_reorder()
+        self.update_bom_item_desc()
+        self.synced_with_hub = 0
 
-		self.validate_has_variants()
-		self.validate_stock_exists_for_template_item()
-		self.validate_attributes()
-		self.validate_variant_attributes()
-		self.validate_website_image()
-		self.make_thumbnail()
-		self.validate_fixed_asset()
-		self.validate_retain_sample()
+        self.validate_has_variants()
+        self.validate_stock_exists_for_template_item()
+        self.validate_attributes()
+        self.validate_variant_attributes()
+        self.validate_website_image()
+        self.make_thumbnail()
+        self.validate_fixed_asset()
+        self.validate_retain_sample()
 
-		if not self.get("__islocal"):
-			self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
-			self.old_website_item_groups = frappe.db.sql_list("""select item_group
+        if not self.get("__islocal"):
+            self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
+            self.old_website_item_groups = frappe.db.sql_list("""select item_group
 				from `tabWebsite Item Group`
 				where parentfield='website_item_groups' and parenttype='Item' and parent=%s""", self.name)
 
-	def on_update(self):
-		invalidate_cache_for_item(self)
-		self.validate_name_with_item_group()
-		self.update_variants()
-		self.update_item_price()
-		self.update_template_item()
+    def on_update(self):
+        invalidate_cache_for_item(self)
+        self.validate_name_with_item_group()
+        self.update_variants()
+        self.update_item_price()
+        self.update_template_item()
 
-	def validate_description(self):
-		'''Clean HTML description if set'''
-		if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')):
-			self.description = clean_html(self.description)
+    def validate_description(self):
+        '''Clean HTML description if set'''
+        if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')):
+            self.description = clean_html(self.description)
 
-	def add_price(self, price_list=None):
-		'''Add a new price'''
-		if not price_list:
-			price_list = (frappe.db.get_single_value('Selling Settings', 'selling_price_list')
-				or frappe.db.get_value('Price List', _('Standard Selling')))
-		if price_list:
-			item_price = frappe.get_doc({
-				"doctype": "Item Price",
-				"price_list": price_list,
-				"item_code": self.name,
-				"currency": erpnext.get_default_currency(),
-				"price_list_rate": self.standard_rate
-			})
-			item_price.insert()
+    def add_price(self, price_list=None):
+        '''Add a new price'''
+        if not price_list:
+            price_list = (frappe.db.get_single_value('Selling Settings', 'selling_price_list')
+                          or frappe.db.get_value('Price List', _('Standard Selling')))
+        if price_list:
+            item_price = frappe.get_doc({
+                "doctype": "Item Price",
+                "price_list": price_list,
+                "item_code": self.name,
+                "currency": erpnext.get_default_currency(),
+                "price_list_rate": self.standard_rate
+            })
+            item_price.insert()
 
-	def set_opening_stock(self):
-		'''set opening stock'''
-		if not self.is_stock_item or self.has_serial_no or self.has_batch_no:
-			return
+    def set_opening_stock(self):
+        '''set opening stock'''
+        if not self.is_stock_item or self.has_serial_no or self.has_batch_no:
+            return
 
-		if not self.valuation_rate and self.standard_rate:
-			self.valuation_rate = self.standard_rate
+        if not self.valuation_rate and self.standard_rate:
+            self.valuation_rate = self.standard_rate
 
-		if not self.valuation_rate:
-			frappe.throw(_("Valuation Rate is mandatory if Opening Stock entered"))
+        if not self.valuation_rate:
+            frappe.throw(_("Valuation Rate is mandatory if Opening Stock entered"))
 
-		from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+        from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 
-		# default warehouse, or Stores
-		default_warehouse = (self.default_warehouse
-			or frappe.db.get_single_value('Stock Settings', 'default_warehouse')
-			or frappe.db.get_value('Warehouse', {'warehouse_name': _('Stores')}))
+        # default warehouse, or Stores
+        default_warehouse = (self.default_warehouse
+                             or frappe.db.get_single_value('Stock Settings', 'default_warehouse')
+                             or frappe.db.get_value('Warehouse', {'warehouse_name': _('Stores')}))
 
-		if default_warehouse:
-			stock_entry = make_stock_entry(item_code=self.name, target=default_warehouse,
-				qty=self.opening_stock, rate=self.valuation_rate)
+        if default_warehouse:
+            stock_entry = make_stock_entry(item_code=self.name, target=default_warehouse,
+                                           qty=self.opening_stock, rate=self.valuation_rate)
 
-			stock_entry.add_comment("Comment", _("Opening Stock"))
+            stock_entry.add_comment("Comment", _("Opening Stock"))
 
-	def make_route(self):
-		if not self.route:
-			return cstr(frappe.db.get_value('Item Group', self.item_group,
-				'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
+    def make_route(self):
+        if not self.route:
+            return cstr(frappe.db.get_value('Item Group', self.item_group,
+                                            'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
 
-	def validate_website_image(self):
-		"""Validate if the website image is a public file"""
-		auto_set_website_image = False
-		if not self.website_image and self.image:
-			auto_set_website_image = True
-			self.website_image = self.image
+    def validate_website_image(self):
+        """Validate if the website image is a public file"""
+        auto_set_website_image = False
+        if not self.website_image and self.image:
+            auto_set_website_image = True
+            self.website_image = self.image
 
-		if not self.website_image:
-			return
+        if not self.website_image:
+            return
 
-		# find if website image url exists as public
-		file_doc = frappe.get_all("File", filters={
-			"file_url": self.website_image
-		}, fields=["name", "is_private"], order_by="is_private asc", limit_page_length=1)
+        # find if website image url exists as public
+        file_doc = frappe.get_all("File", filters={
+            "file_url": self.website_image
+        }, fields=["name", "is_private"], order_by="is_private asc", limit_page_length=1)
 
+        if file_doc:
+            file_doc = file_doc[0]
 
-		if file_doc:
-			file_doc = file_doc[0]
+        if not file_doc:
+            if not auto_set_website_image:
+                frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found")
+                                .format(self.website_image, self.name))
 
-		if not file_doc:
-			if not auto_set_website_image:
-				frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found")
-					.format(self.website_image, self.name))
+            self.website_image = None
 
-			self.website_image = None
+        elif file_doc.is_private:
+            if not auto_set_website_image:
+                frappe.msgprint(_("Website Image should be a public file or website URL"))
 
-		elif file_doc.is_private:
-			if not auto_set_website_image:
-				frappe.msgprint(_("Website Image should be a public file or website URL"))
+            self.website_image = None
 
-			self.website_image = None
+    def make_thumbnail(self):
+        """Make a thumbnail of `website_image`"""
+        import requests.exceptions
 
-	def make_thumbnail(self):
-		"""Make a thumbnail of `website_image`"""
-		import requests.exceptions
+        if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
+            self.thumbnail = None
 
-		if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
-			self.thumbnail = None
+        if self.website_image and not self.thumbnail:
+            file_doc = None
 
-		if self.website_image and not self.thumbnail:
-			file_doc = None
+            try:
+                file_doc = frappe.get_doc("File", {
+                    "file_url": self.website_image,
+                    "attached_to_doctype": "Item",
+                    "attached_to_name": self.name
+                })
+            except frappe.DoesNotExistError:
+                pass
+                # cleanup
+                frappe.local.message_log.pop()
 
-			try:
-				file_doc = frappe.get_doc("File", {
-					"file_url": self.website_image,
-					"attached_to_doctype": "Item",
-					"attached_to_name": self.name
-				})
-			except frappe.DoesNotExistError:
-				pass
-				# cleanup
-				frappe.local.message_log.pop()
+            except requests.exceptions.HTTPError:
+                frappe.msgprint(_("Warning: Invalid attachment {0}").format(self.website_image))
+                self.website_image = None
 
-			except requests.exceptions.HTTPError:
-				frappe.msgprint(_("Warning: Invalid attachment {0}").format(self.website_image))
-				self.website_image = None
+            except requests.exceptions.SSLError:
+                frappe.msgprint(
+                    _("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
+                self.website_image = None
 
-			except requests.exceptions.SSLError:
-				frappe.msgprint(_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
-				self.website_image = None
+            # for CSV import
+            if self.website_image and not file_doc:
+                try:
+                    file_doc = frappe.get_doc({
+                        "doctype": "File",
+                        "file_url": self.website_image,
+                        "attached_to_doctype": "Item",
+                        "attached_to_name": self.name
+                    }).insert()
 
-			# for CSV import
-			if self.website_image and not file_doc:
-				try:
-					file_doc = frappe.get_doc({
-						"doctype": "File",
-						"file_url": self.website_image,
-						"attached_to_doctype": "Item",
-						"attached_to_name": self.name
-					}).insert()
+                except IOError:
+                    self.website_image = None
 
-				except IOError:
-					self.website_image = None
+            if file_doc:
+                if not file_doc.thumbnail_url:
+                    file_doc.make_thumbnail()
 
-			if file_doc:
-				if not file_doc.thumbnail_url:
-					file_doc.make_thumbnail()
+                self.thumbnail = file_doc.thumbnail_url
 
-				self.thumbnail = file_doc.thumbnail_url
+    def validate_fixed_asset(self):
+        if self.is_fixed_asset:
+            if self.is_stock_item:
+                frappe.throw(_("Fixed Asset Item must be a non-stock item."))
 
-	def validate_fixed_asset(self):
-		if self.is_fixed_asset:
-			if self.is_stock_item:
-				frappe.throw(_("Fixed Asset Item must be a non-stock item."))
+            if not self.asset_category:
+                frappe.throw(_("Asset Category is mandatory for Fixed Asset item"))
 
-			if not self.asset_category:
-				frappe.throw(_("Asset Category is mandatory for Fixed Asset item"))
+            if self.stock_ledger_created():
+                frappe.throw(_("Cannot be a fixed asset item as Stock Ledger is created."))
 
-			if self.stock_ledger_created():
-				frappe.throw(_("Cannot be a fixed asset item as Stock Ledger is created."))
+        if not self.is_fixed_asset:
+            asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1)
+            if asset:
+                frappe.throw(_('"Is Fixed Asset" cannot be unchecked, as Asset record exists against the item'))
 
-		if not self.is_fixed_asset:
-			asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1)
-			if asset:
-				frappe.throw(_('"Is Fixed Asset" cannot be unchecked, as Asset record exists against the item'))
+    def validate_retain_sample(self):
+        if self.retain_sample and not frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse'):
+            frappe.throw(_("Please select Sample Retention Warehouse in Stock Settings first"))
+        if self.retain_sample and not self.has_batch_no:
+            frappe.throw(_(" {0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(
+                self.item_code))
 
-	def validate_retain_sample(self):
-		if self.retain_sample and not frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse'):
-			frappe.throw(_("Please select Sample Retention Warehouse in Stock Settings first"));
-		if self.retain_sample and not self.has_batch_no:
-			frappe.throw(_(" {0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(self.item_code))
+    def get_context(self, context):
+        context.show_search = True
+        context.search_link = '/product_search'
 
-	def get_context(self, context):
-		context.show_search=True
-		context.search_link = '/product_search'
+        context.parents = get_parent_item_groups(self.item_group)
 
-		context.parents = get_parent_item_groups(self.item_group)
+        self.set_variant_context(context)
+        self.set_attribute_context(context)
+        self.set_disabled_attributes(context)
 
-		self.set_variant_context(context)
-		self.set_attribute_context(context)
-		self.set_disabled_attributes(context)
+        return context
 
-		return context
+    def set_variant_context(self, context):
+        if self.has_variants:
+            context.no_cache = True
 
-	def set_variant_context(self, context):
-		if self.has_variants:
-			context.no_cache = True
+            # load variants
+            # also used in set_attribute_context
+            context.variants = frappe.get_all("Item",
+                                              filters={"variant_of": self.name, "show_variant_in_website": 1},
+                                              order_by="name asc")
 
-			# load variants
-			# also used in set_attribute_context
-			context.variants = frappe.get_all("Item",
-				filters={"variant_of": self.name, "show_variant_in_website": 1},
-				order_by="name asc")
+            variant = frappe.form_dict.variant
+            if not variant and context.variants:
+                # the case when the item is opened for the first time from its list
+                variant = context.variants[0]
 
-			variant = frappe.form_dict.variant
-			if not variant and context.variants:
-				# the case when the item is opened for the first time from its list
-				variant = context.variants[0]
+            if variant:
+                context.variant = frappe.get_doc("Item", variant)
 
-			if variant:
-				context.variant = frappe.get_doc("Item", variant)
+                for fieldname in ("website_image", "web_long_description", "description",
+                                  "website_specifications"):
+                    if context.variant.get(fieldname):
+                        value = context.variant.get(fieldname)
+                        if isinstance(value, list):
+                            value = [d.as_dict() for d in value]
 
-				for fieldname in ("website_image", "web_long_description", "description",
-					"website_specifications"):
-					if context.variant.get(fieldname):
-						value = context.variant.get(fieldname)
-						if isinstance(value, list):
-							value = [d.as_dict() for d in value]
+                        context[fieldname] = value
 
-						context[fieldname] = value
-
-		if self.slideshow:
-			if context.variant and context.variant.slideshow:
-				context.update(get_slideshow(context.variant))
-			else:
-				context.update(get_slideshow(self))
-
-	def set_attribute_context(self, context):
-		if self.has_variants:
-			attribute_values_available = {}
-			context.attribute_values = {}
-			context.selected_attributes = {}
+        if self.slideshow:
+            if context.variant and context.variant.slideshow:
+                context.update(get_slideshow(context.variant))
+            else:
+                context.update(get_slideshow(self))
 
-			# load attributes
-			for v in context.variants:
-				v.attributes = frappe.get_all("Item Variant Attribute",
-					fields=["attribute", "attribute_value"], filters={"parent": v.name})
+    def set_attribute_context(self, context):
+        if self.has_variants:
+            attribute_values_available = {}
+            context.attribute_values = {}
+            context.selected_attributes = {}
 
-				for attr in v.attributes:
-					values = attribute_values_available.setdefault(attr.attribute, [])
-					if attr.attribute_value not in values:
-						values.append(attr.attribute_value)
+            # load attributes
+            for v in context.variants:
+                v.attributes = frappe.get_all("Item Variant Attribute",
+                                              fields=["attribute", "attribute_value"], filters={"parent": v.name})
 
-					if v.name==context.variant.name:
-						context.selected_attributes[attr.attribute] = attr.attribute_value
+                for attr in v.attributes:
+                    values = attribute_values_available.setdefault(attr.attribute, [])
+                    if attr.attribute_value not in values:
+                        values.append(attr.attribute_value)
 
-			# filter attributes, order based on attribute table
-			for attr in self.attributes:
-				values = context.attribute_values.setdefault(attr.attribute, [])
+                    if v.name == context.variant.name:
+                        context.selected_attributes[attr.attribute] = attr.attribute_value
 
-				if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")):
-					for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt):
-						values.append(val)
+            # filter attributes, order based on attribute table
+            for attr in self.attributes:
+                values = context.attribute_values.setdefault(attr.attribute, [])
 
-				else:
-					# get list of values defined (for sequence)
-					for attr_value in frappe.db.get_all("Item Attribute Value",
-						fields=["attribute_value"], filters={"parent": attr.attribute}, order_by="idx asc"):
+                if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")):
+                    for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt):
+                        values.append(val)
 
-						if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
-							values.append(attr_value.attribute_value)
+                else:
+                    # get list of values defined (for sequence)
+                    for attr_value in frappe.db.get_all("Item Attribute Value",
+                                                        fields=["attribute_value"], filters={"parent": attr.attribute}, order_by="idx asc"):
 
-			context.variant_info = json.dumps(context.variants)
+                        if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
+                            values.append(attr_value.attribute_value)
 
-	def set_disabled_attributes(self, context):
-		"""Disable selection options of attribute combinations that do not result in a variant"""
-		if not self.attributes or not self.has_variants:
-			return
+            context.variant_info = json.dumps(context.variants)
 
-		context.disabled_attributes = {}
-		attributes = [attr.attribute for attr in self.attributes]
+    def set_disabled_attributes(self, context):
+        """Disable selection options of attribute combinations that do not result in a variant"""
+        if not self.attributes or not self.has_variants:
+            return
 
-		def find_variant(combination):
-			for variant in context.variants:
-				if len(variant.attributes) < len(attributes):
-					continue
+        context.disabled_attributes = {}
+        attributes = [attr.attribute for attr in self.attributes]
 
-				if "combination" not in variant:
-					ref_combination = []
+        def find_variant(combination):
+            for variant in context.variants:
+                if len(variant.attributes) < len(attributes):
+                    continue
 
-					for attr in variant.attributes:
-						idx = attributes.index(attr.attribute)
-						ref_combination.insert(idx, attr.attribute_value)
+                if "combination" not in variant:
+                    ref_combination = []
 
-					variant["combination"] = ref_combination
+                    for attr in variant.attributes:
+                        idx = attributes.index(attr.attribute)
+                        ref_combination.insert(idx, attr.attribute_value)
 
-				if not (set(combination) - set(variant["combination"])):
-					# check if the combination is a subset of a variant combination
-					# eg. [Blue, 0.5] is a possible combination if exists [Blue, Large, 0.5]
-					return True
+                    variant["combination"] = ref_combination
 
-		for i, attr in enumerate(self.attributes):
-			if i==0:
-				continue
+                if not (set(combination) - set(variant["combination"])):
+                    # check if the combination is a subset of a variant combination
+                    # eg. [Blue, 0.5] is a possible combination if exists [Blue, Large, 0.5]
+                    return True
 
-			combination_source = []
+        for i, attr in enumerate(self.attributes):
+            if i == 0:
+                continue
 
-			# loop through previous attributes
-			for prev_attr in self.attributes[:i]:
-				combination_source.append([context.selected_attributes.get(prev_attr.attribute)])
+            combination_source = []
 
-			combination_source.append(context.attribute_values[attr.attribute])
+            # loop through previous attributes
+            for prev_attr in self.attributes[:i]:
+                combination_source.append([context.selected_attributes.get(prev_attr.attribute)])
 
-			for combination in itertools.product(*combination_source):
-				if not find_variant(combination):
-					context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
+            combination_source.append(context.attribute_values[attr.attribute])
 
-	def add_default_uom_in_conversion_factor_table(self):
-		uom_conv_list = [d.uom for d in self.get("uoms")]
-		if self.stock_uom not in uom_conv_list:
-			ch = self.append('uoms', {})
-			ch.uom = self.stock_uom
-			ch.conversion_factor = 1
+            for combination in itertools.product(*combination_source):
+                if not find_variant(combination):
+                    context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
 
-		to_remove = []
-		for d in self.get("uoms"):
-			if d.conversion_factor == 1 and d.uom != self.stock_uom:
-				to_remove.append(d)
+    def add_default_uom_in_conversion_factor_table(self):
+        uom_conv_list = [d.uom for d in self.get("uoms")]
+        if self.stock_uom not in uom_conv_list:
+            ch = self.append('uoms', {})
+            ch.uom = self.stock_uom
+            ch.conversion_factor = 1
 
-		[self.remove(d) for d in to_remove]
+        to_remove = []
+        for d in self.get("uoms"):
+            if d.conversion_factor == 1 and d.uom != self.stock_uom:
+                to_remove.append(d)
 
-	def update_template_tables(self):
-		template = frappe.get_doc("Item", self.variant_of)
+        [self.remove(d) for d in to_remove]
 
-		# add item taxes from template
-		for d in template.get("taxes"):
-			self.append("taxes", {"tax_type": d.tax_type, "tax_rate": d.tax_rate})
+    def update_template_tables(self):
+        template = frappe.get_doc("Item", self.variant_of)
 
-		# copy re-order table if empty
-		if not self.get("reorder_levels"):
-			for d in template.get("reorder_levels"):
-				n = {}
-				for k in ("warehouse", "warehouse_reorder_level",
-					"warehouse_reorder_qty", "material_request_type"):
-					n[k] = d.get(k)
-				self.append("reorder_levels", n)
+        # add item taxes from template
+        for d in template.get("taxes"):
+            self.append("taxes", {"tax_type": d.tax_type, "tax_rate": d.tax_rate})
 
-	def validate_conversion_factor(self):
-		check_list = []
-		for d in self.get('uoms'):
-			if cstr(d.uom) in check_list:
-				frappe.throw(_("Unit of Measure {0} has been entered more than once in Conversion Factor Table").format(d.uom))
-			else:
-				check_list.append(cstr(d.uom))
+        # copy re-order table if empty
+        if not self.get("reorder_levels"):
+            for d in template.get("reorder_levels"):
+                n = {}
+                for k in ("warehouse", "warehouse_reorder_level",
+                          "warehouse_reorder_qty", "material_request_type"):
+                    n[k] = d.get(k)
+                self.append("reorder_levels", n)
 
-			if d.uom and cstr(d.uom) == cstr(self.stock_uom) and flt(d.conversion_factor) != 1:
-				frappe.throw(_("Conversion factor for default Unit of Measure must be 1 in row {0}").format(d.idx))
+    def validate_conversion_factor(self):
+        check_list = []
+        for d in self.get('uoms'):
+            if cstr(d.uom) in check_list:
+                frappe.throw(
+                    _("Unit of Measure {0} has been entered more than once in Conversion Factor Table").format(d.uom))
+            else:
+                check_list.append(cstr(d.uom))
 
-	def validate_item_type(self):
-		if self.has_serial_no == 1 and self.is_stock_item == 0:
-			msgprint(_("'Has Serial No' can not be 'Yes' for non-stock item"), raise_exception=1)
+            if d.uom and cstr(d.uom) == cstr(self.stock_uom) and flt(d.conversion_factor) != 1:
+                frappe.throw(
+                    _("Conversion factor for default Unit of Measure must be 1 in row {0}").format(d.idx))
 
-		if self.has_serial_no == 0 and self.serial_no_series:
-			self.serial_no_series = None
+    def validate_item_type(self):
+        if self.has_serial_no == 1 and self.is_stock_item == 0:
+            msgprint(_("'Has Serial No' can not be 'Yes' for non-stock item"), raise_exception=1)
 
+        if self.has_serial_no == 0 and self.serial_no_series:
+            self.serial_no_series = None
 
-	def check_for_active_boms(self):
-		if self.default_bom:
-			bom_item = frappe.db.get_value("BOM", self.default_bom, "item")
-			if bom_item not in (self.name, self.variant_of):
-				frappe.throw(_("Default BOM ({0}) must be active for this item or its template").format(bom_item))
+    def check_for_active_boms(self):
+        if self.default_bom:
+            bom_item = frappe.db.get_value("BOM", self.default_bom, "item")
+            if bom_item not in (self.name, self.variant_of):
+                frappe.throw(
+                    _("Default BOM ({0}) must be active for this item or its template").format(bom_item))
 
-	def fill_customer_code(self):
-		""" Append all the customer codes and insert into "customer_code" field of item table """
-		cust_code=[]
-		for d in self.get('customer_items'):
-			cust_code.append(d.ref_code)
-		self.customer_code=','.join(cust_code)
+    def fill_customer_code(self):
+        """ Append all the customer codes and insert into "customer_code" field of item table """
+        cust_code = []
+        for d in self.get('customer_items'):
+            cust_code.append(d.ref_code)
+        self.customer_code = ','.join(cust_code)
 
-	def check_item_tax(self):
-		"""Check whether Tax Rate is not entered twice for same Tax Type"""
-		check_list=[]
-		for d in self.get('taxes'):
-			if d.tax_type:
-				account_type = frappe.db.get_value("Account", d.tax_type, "account_type")
+    def check_item_tax(self):
+        """Check whether Tax Rate is not entered twice for same Tax Type"""
+        check_list = []
+        for d in self.get('taxes'):
+            if d.tax_type:
+                account_type = frappe.db.get_value("Account", d.tax_type, "account_type")
 
-				if account_type not in ['Tax', 'Chargeable', 'Income Account', 'Expense Account']:
-					frappe.throw(_("Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable").format(d.idx))
-				else:
-					if d.tax_type in check_list:
-						frappe.throw(_("{0} entered twice in Item Tax").format(d.tax_type))
-					else:
-						check_list.append(d.tax_type)
+                if account_type not in ['Tax', 'Chargeable', 'Income Account', 'Expense Account']:
+                    frappe.throw(
+                        _("Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable").format(d.idx))
+                else:
+                    if d.tax_type in check_list:
+                        frappe.throw(_("{0} entered twice in Item Tax").format(d.tax_type))
+                    else:
+                        check_list.append(d.tax_type)
 
-	def validate_barcode(self):
-		if self.barcode:
-			duplicate = frappe.db.sql("""select name from tabItem where barcode = %s
-				and name != %s""", (self.barcode, self.name))
-			if duplicate:
-				frappe.throw(_("Barcode {0} already used in Item {1}").format(self.barcode, duplicate[0][0]))
+    def validate_barcode(self):
+        from stdnum import ean
+        if len(self.barcodes) > 0:
+            for item_barcode in self.barcodes:
+                if item_barcode.barcode:
+                    duplicate = frappe.db.sql("""select parent from `tabItem Barcode` where barcode = %s and parent != %s""", (item_barcode.barcode, self.name))
+                    if duplicate:
+                        frappe.throw(_("Barcode {0} already used in Item {1}").format(
+                            item_barcode.barcode, duplicate[0][0]))
 
+                    if item_barcode.barcode_type:
+                        if not ean.is_valid(item_barcode.barcode):
+                            frappe.throw(_("Barcode {0} is not a valid {1} code").format(
+                                item_barcode.barcode, item_barcode.barcode_type))
 
-	def validate_warehouse_for_reorder(self):
-		'''Validate Reorder level table for duplicate and conditional mandatory'''
-		warehouse = []
-		for d in self.get("reorder_levels"):
-			if not d.warehouse_group:
-				d.warehouse_group = d.warehouse
-			if d.get("warehouse") and d.get("warehouse") not in warehouse:
-				warehouse += [d.get("warehouse")]
-			else:
-				frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
-					.format(d.idx, d.warehouse), DuplicateReorderRows)
+    def validate_warehouse_for_reorder(self):
+        '''Validate Reorder level table for duplicate and conditional mandatory'''
+        warehouse = []
+        for d in self.get("reorder_levels"):
+            if not d.warehouse_group:
+                d.warehouse_group = d.warehouse
+            if d.get("warehouse") and d.get("warehouse") not in warehouse:
+                warehouse += [d.get("warehouse")]
+            else:
+                frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
+                             .format(d.idx, d.warehouse), DuplicateReorderRows)
 
-			if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
-				frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
+            if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
+                frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
 
-	def stock_ledger_created(self):
-		if not hasattr(self, '_stock_ledger_created'):
-			self._stock_ledger_created = len(frappe.db.sql("""select name from `tabStock Ledger Entry`
+    def stock_ledger_created(self):
+        if not hasattr(self, '_stock_ledger_created'):
+            self._stock_ledger_created = len(frappe.db.sql("""select name from `tabStock Ledger Entry`
 				where item_code = %s limit 1""", self.name))
-		return self._stock_ledger_created
+        return self._stock_ledger_created
 
-	def validate_name_with_item_group(self):
-		# causes problem with tree build
-		if frappe.db.exists("Item Group", self.name):
-			frappe.throw(_("An Item Group exists with same name, please change the item name or rename the item group"))
+    def validate_name_with_item_group(self):
+        # causes problem with tree build
+        if frappe.db.exists("Item Group", self.name):
+            frappe.throw(
+                _("An Item Group exists with same name, please change the item name or rename the item group"))
 
-	def update_item_price(self):
-		frappe.db.sql("""update `tabItem Price` set item_name=%s,
+    def update_item_price(self):
+        frappe.db.sql("""update `tabItem Price` set item_name=%s,
 			item_description=%s, modified=NOW() where item_code=%s""",
-			(self.item_name, self.description, self.name))
+                      (self.item_name, self.description, self.name))
 
-	def on_trash(self):
-		super(Item, self).on_trash()
-		frappe.db.sql("""delete from tabBin where item_code=%s""", self.item_code)
-		frappe.db.sql("delete from `tabItem Price` where item_code=%s", self.name)
-		for variant_of in frappe.get_all("Item", filters={"variant_of": self.name}):
-			frappe.delete_doc("Item", variant_of.name)
+    def on_trash(self):
+        super(Item, self).on_trash()
+        frappe.db.sql("""delete from tabBin where item_code=%s""", self.item_code)
+        frappe.db.sql("delete from `tabItem Price` where item_code=%s", self.name)
+        for variant_of in frappe.get_all("Item", filters={"variant_of": self.name}):
+            frappe.delete_doc("Item", variant_of.name)
 
-	def before_rename(self, old_name, new_name, merge=False):
-		if self.item_name==old_name:
-			frappe.db.set_value("Item", old_name, "item_name", new_name)
+    def before_rename(self, old_name, new_name, merge=False):
+        if self.item_name == old_name:
+            frappe.db.set_value("Item", old_name, "item_name", new_name)
 
-		if merge:
-			# Validate properties before merging
-			if not frappe.db.exists("Item", new_name):
-				frappe.throw(_("Item {0} does not exist").format(new_name))
+        if merge:
+            # Validate properties before merging
+            if not frappe.db.exists("Item", new_name):
+                frappe.throw(_("Item {0} does not exist").format(new_name))
 
-			field_list = ["stock_uom", "is_stock_item", "has_serial_no", "has_batch_no"]
-			new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)]
-			if new_properties != [cstr(self.get(fld)) for fld in field_list]:
-				frappe.throw(_("To merge, following properties must be same for both items")
-					+ ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
+            field_list = ["stock_uom", "is_stock_item", "has_serial_no", "has_batch_no"]
+            new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)]
+            if new_properties != [cstr(self.get(fld)) for fld in field_list]:
+                frappe.throw(_("To merge, following properties must be same for both items")
+                             + ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
 
-	def after_rename(self, old_name, new_name, merge):
-		if self.route:
-			invalidate_cache_for_item(self)
-			clear_cache(self.route)
+    def after_rename(self, old_name, new_name, merge):
+        if self.route:
+            invalidate_cache_for_item(self)
+            clear_cache(self.route)
 
-		frappe.db.set_value("Item", new_name, "item_code", new_name)
+        frappe.db.set_value("Item", new_name, "item_code", new_name)
 
-		if merge:
-			self.set_last_purchase_rate(new_name)
-			self.recalculate_bin_qty(new_name)
+        if merge:
+            self.set_last_purchase_rate(new_name)
+            self.recalculate_bin_qty(new_name)
 
-		for dt in ("Sales Taxes and Charges", "Purchase Taxes and Charges"):
-			for d in frappe.db.sql("""select name, item_wise_tax_detail from `tab{0}`
+        for dt in ("Sales Taxes and Charges", "Purchase Taxes and Charges"):
+            for d in frappe.db.sql("""select name, item_wise_tax_detail from `tab{0}`
 					where ifnull(item_wise_tax_detail, '') != ''""".format(dt), as_dict=1):
 
-				item_wise_tax_detail = json.loads(d.item_wise_tax_detail)
-				if old_name in item_wise_tax_detail:
-					item_wise_tax_detail[new_name] = item_wise_tax_detail[old_name]
-					item_wise_tax_detail.pop(old_name)
+                item_wise_tax_detail = json.loads(d.item_wise_tax_detail)
+                if old_name in item_wise_tax_detail:
+                    item_wise_tax_detail[new_name] = item_wise_tax_detail[old_name]
+                    item_wise_tax_detail.pop(old_name)
 
-					frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
-						json.dumps(item_wise_tax_detail), update_modified=False)
+                    frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
+                                        json.dumps(item_wise_tax_detail), update_modified=False)
 
-	def set_last_purchase_rate(self, new_name):
-		last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0)
-		frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate)
+    def set_last_purchase_rate(self, new_name):
+        last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0)
+        frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate)
 
-	def recalculate_bin_qty(self, new_name):
-		from erpnext.stock.stock_balance import repost_stock
-		frappe.db.auto_commit_on_many_writes = 1
-		existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
-		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
+    def recalculate_bin_qty(self, new_name):
+        from erpnext.stock.stock_balance import repost_stock
+        frappe.db.auto_commit_on_many_writes = 1
+        existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
+        frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
 
-		repost_stock_for_warehouses = frappe.db.sql_list("""select distinct warehouse
+        repost_stock_for_warehouses = frappe.db.sql_list("""select distinct warehouse
 			from tabBin where item_code=%s""", new_name)
 
-		# Delete all existing bins to avoid duplicate bins for the same item and warehouse
-		frappe.db.sql("delete from `tabBin` where item_code=%s", new_name)
+        # Delete all existing bins to avoid duplicate bins for the same item and warehouse
+        frappe.db.sql("delete from `tabBin` where item_code=%s", new_name)
 
-		for warehouse in repost_stock_for_warehouses:
-			repost_stock(new_name, warehouse)
+        for warehouse in repost_stock_for_warehouses:
+            repost_stock(new_name, warehouse)
 
-		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
-		frappe.db.auto_commit_on_many_writes = 0
+        frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
+        frappe.db.auto_commit_on_many_writes = 0
 
-	def copy_specification_from_item_group(self):
-		self.set("website_specifications", [])
-		if self.item_group:
-			for label, desc in frappe.db.get_values("Item Website Specification",
-				{"parent": self.item_group}, ["label", "description"]):
-					row = self.append("website_specifications")
-					row.label = label
-					row.description = desc
+    def copy_specification_from_item_group(self):
+        self.set("website_specifications", [])
+        if self.item_group:
+            for label, desc in frappe.db.get_values("Item Website Specification",
+                                                    {"parent": self.item_group}, ["label", "description"]):
+                row = self.append("website_specifications")
+                row.label = label
+                row.description = desc
 
-	def update_bom_item_desc(self):
-		if self.is_new(): return
+    def update_bom_item_desc(self):
+        if self.is_new():
+            return
 
-		if self.db_get('description') != self.description:
-			frappe.db.sql("""
+        if self.db_get('description') != self.description:
+            frappe.db.sql("""
 				update `tabBOM`
 				set description = %s
 				where item = %s and docstatus < 2
 			""", (self.description, self.name))
 
-			frappe.db.sql("""
+            frappe.db.sql("""
 				update `tabBOM Item`
 				set description = %s
 				where item_code = %s and docstatus < 2
 			""", (self.description, self.name))
 
-			frappe.db.sql("""
+            frappe.db.sql("""
 				update `tabBOM Explosion Item`
 				set description = %s
 				where item_code = %s and docstatus < 2
 			""", (self.description, self.name))
 
-	def update_template_item(self):
-		"""Set Show in Website for Template Item if True for its Variant"""
-		if self.variant_of and self.show_in_website:
-			self.show_variant_in_website = 1
-			self.show_in_website = 0
+    def update_template_item(self):
+        """Set Show in Website for Template Item if True for its Variant"""
+        if self.variant_of and self.show_in_website:
+            self.show_variant_in_website = 1
+            self.show_in_website = 0
 
-		if self.show_variant_in_website:
-			# show template
-			template_item = frappe.get_doc("Item", self.variant_of)
+        if self.show_variant_in_website:
+            # show template
+            template_item = frappe.get_doc("Item", self.variant_of)
 
-			if not template_item.show_in_website:
-				template_item.show_in_website = 1
-				template_item.flags.dont_update_variants = True
-				template_item.flags.ignore_permissions = True
-				template_item.save()
+            if not template_item.show_in_website:
+                template_item.show_in_website = 1
+                template_item.flags.dont_update_variants = True
+                template_item.flags.ignore_permissions = True
+                template_item.save()
 
-	def update_variants(self):
-		if self.flags.dont_update_variants or \
-			frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
-			return
-		if self.has_variants:
-			updated = []
-			variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name })
-			for d in variants:
-				variant = frappe.get_doc("Item", d)
-				copy_attributes_to_variant(self, variant)
-				variant.save()
-				updated.append(d.item_code)
-			if updated:
-				frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
+    def update_variants(self):
+        if self.flags.dont_update_variants or \
+                frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
+            return
+        if self.has_variants:
+            updated = []
+            variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name})
+            for d in variants:
+                variant = frappe.get_doc("Item", d)
+                copy_attributes_to_variant(self, variant)
+                variant.save()
+                updated.append(d.item_code)
+            if updated:
+                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_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_stock_exists_for_template_item(self):
-		if self.stock_ledger_created() and self._doc_before_save:
-			if (self._doc_before_save.has_variants != self.has_variants
-				or self._doc_before_save.variant_of != self.variant_of):
-				frappe.throw(_("Cannot change Variant properties after stock transction. You will have to make a new Item to do this.").format(self.name),
-					StockExistsForTemplate)
+    def validate_stock_exists_for_template_item(self):
+        if self.stock_ledger_created() and self._doc_before_save:
+            if (self._doc_before_save.has_variants != self.has_variants
+                    or self._doc_before_save.variant_of != self.variant_of):
+                frappe.throw(_("Cannot change Variant properties after stock transction. You will have to make a new Item to do this.").format(self.name),
+                             StockExistsForTemplate)
 
-			if self.has_variants or self.variant_of:
-				if not self.is_child_table_same('attributes'):
-					frappe.throw(_('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item'))
+            if self.has_variants or self.variant_of:
+                if not self.is_child_table_same('attributes'):
+                    frappe.throw(
+                        _('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item'))
 
-	def validate_uom(self):
-		if not self.get("__islocal"):
-			check_stock_uom_with_bin(self.name, self.stock_uom)
-		if self.has_variants:
-			for d in frappe.db.get_all("Item", filters= {"variant_of": self.name}):
-				check_stock_uom_with_bin(d.name, self.stock_uom)
-		if self.variant_of:
-			template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
-			if template_uom != self.stock_uom:
-				frappe.throw(_("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'")
-					.format(self.stock_uom, template_uom))
+    def validate_uom(self):
+        if not self.get("__islocal"):
+            check_stock_uom_with_bin(self.name, self.stock_uom)
+        if self.has_variants:
+            for d in frappe.db.get_all("Item", filters={"variant_of": self.name}):
+                check_stock_uom_with_bin(d.name, self.stock_uom)
+        if self.variant_of:
+            template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
+            if template_uom != self.stock_uom:
+                frappe.throw(_("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'")
+                             .format(self.stock_uom, template_uom))
 
-	def validate_attributes(self):
-		if (self.has_variants or self.variant_of) and self.variant_based_on=='Item Attribute':
-			attributes = []
-			if not self.attributes:
-				frappe.throw(_("Attribute table is mandatory"))
-			for d in self.attributes:
-				if d.attribute in attributes:
-					frappe.throw(_("Attribute {0} selected multiple times in Attributes Table".format(d.attribute)))
-				else:
-					attributes.append(d.attribute)
+    def validate_attributes(self):
+        if (self.has_variants or self.variant_of) and self.variant_based_on == 'Item Attribute':
+            attributes = []
+            if not self.attributes:
+                frappe.throw(_("Attribute table is mandatory"))
+            for d in self.attributes:
+                if d.attribute in attributes:
+                    frappe.throw(
+                        _("Attribute {0} selected multiple times in Attributes Table".format(d.attribute)))
+                else:
+                    attributes.append(d.attribute)
 
-	def validate_variant_attributes(self):
-		if self.variant_of and self.variant_based_on=='Item Attribute':
-			args = {}
-			for d in self.attributes:
-				if not d.attribute_value:
-					frappe.throw(_("Please specify Attribute Value for attribute {0}").format(d.attribute))
-				args[d.attribute] = d.attribute_value
+    def validate_variant_attributes(self):
+        if self.variant_of and self.variant_based_on == 'Item Attribute':
+            args = {}
+            for d in self.attributes:
+                if not d.attribute_value:
+                    frappe.throw(_("Please specify Attribute Value for attribute {0}").format(d.attribute))
+                args[d.attribute] = d.attribute_value
 
-			variant = get_variant(self.variant_of, args, self.name)
-			if variant:
-				frappe.throw(_("Item variant {0} exists with same attributes")
-					.format(variant), ItemVariantExistsError)
+            variant = get_variant(self.variant_of, args, self.name)
+            if variant:
+                frappe.throw(_("Item variant {0} exists with same attributes")
+                             .format(variant), ItemVariantExistsError)
 
-			validate_item_variant_attributes(self, args)
+            validate_item_variant_attributes(self, args)
+
 
 def get_timeline_data(doctype, name):
-	'''returns timeline data based on stock ledger entry'''
-	out = {}
-	items = dict(frappe.db.sql('''select posting_date, count(*)
+    '''returns timeline data based on stock ledger entry'''
+    out = {}
+    items = dict(frappe.db.sql('''select posting_date, count(*)
 		from `tabStock Ledger Entry` where item_code=%s
 			and posting_date > date_sub(curdate(), interval 1 year)
 			group by posting_date''', name))
 
-	for date, count in items.iteritems():
-		timestamp = get_timestamp(date)
-		out.update({ timestamp: count })
+    for date, count in items.iteritems():
+        timestamp = get_timestamp(date)
+        out.update({timestamp: count})
 
-	return out
+    return out
+
 
 def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1):
-	if (not end_of_life) or (disabled is None):
-		end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"])
+    if (not end_of_life) or (disabled is None):
+        end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"])
 
-	if end_of_life and end_of_life!="0000-00-00" and getdate(end_of_life) <= now_datetime().date():
-		msg = _("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life))
-		_msgprint(msg, verbose)
+    if end_of_life and end_of_life != "0000-00-00" and getdate(end_of_life) <= now_datetime().date():
+        msg = _("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life))
+        _msgprint(msg, verbose)
 
-	if disabled:
-		_msgprint(_("Item {0} is disabled").format(item_code), verbose)
+    if disabled:
+        _msgprint(_("Item {0} is disabled").format(item_code), verbose)
+
 
 def validate_is_stock_item(item_code, is_stock_item=None, verbose=1):
-	if not is_stock_item:
-		is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item")
+    if not is_stock_item:
+        is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item")
 
-	if is_stock_item != 1:
-		msg = _("Item {0} is not a stock Item").format(item_code)
+    if is_stock_item != 1:
+        msg = _("Item {0} is not a stock Item").format(item_code)
 
-		_msgprint(msg, verbose)
+        _msgprint(msg, verbose)
+
 
 def validate_cancelled_item(item_code, docstatus=None, verbose=1):
-	if docstatus is None:
-		docstatus = frappe.db.get_value("Item", item_code, "docstatus")
+    if docstatus is None:
+        docstatus = frappe.db.get_value("Item", item_code, "docstatus")
 
-	if docstatus == 2:
-		msg = _("Item {0} is cancelled").format(item_code)
-		_msgprint(msg, verbose)
+    if docstatus == 2:
+        msg = _("Item {0} is cancelled").format(item_code)
+        _msgprint(msg, verbose)
+
 
 def _msgprint(msg, verbose):
-	if verbose:
-		msgprint(msg, raise_exception=True)
-	else:
-		raise frappe.ValidationError(msg)
+    if verbose:
+        msgprint(msg, raise_exception=True)
+    else:
+        raise frappe.ValidationError(msg)
 
 
 def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
-	"""returns last purchase details in stock uom"""
-	# get last purchase order item details
-	last_purchase_order = frappe.db.sql("""\
+    """returns last purchase details in stock uom"""
+    # get last purchase order item details
+    last_purchase_order = frappe.db.sql("""\
 		select po.name, po.transaction_date, po.conversion_rate,
 			po_item.conversion_factor, po_item.base_price_list_rate,
 			po_item.discount_percentage, po_item.base_rate
@@ -749,8 +782,8 @@
 		order by po.transaction_date desc, po.name desc
 		limit 1""", (item_code, cstr(doc_name)), as_dict=1)
 
-	# get last purchase receipt item details
-	last_purchase_receipt = frappe.db.sql("""\
+    # get last purchase receipt item details
+    last_purchase_receipt = frappe.db.sql("""\
 		select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate,
 			pr_item.conversion_factor, pr_item.base_price_list_rate, pr_item.discount_percentage,
 			pr_item.base_rate
@@ -760,76 +793,79 @@
 		order by pr.posting_date desc, pr.posting_time desc, pr.name desc
 		limit 1""", (item_code, cstr(doc_name)), as_dict=1)
 
-	purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date \
-		or "1900-01-01")
-	purchase_receipt_date = getdate(last_purchase_receipt and \
-		last_purchase_receipt[0].posting_date or "1900-01-01")
+    purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
+                                  or "1900-01-01")
+    purchase_receipt_date = getdate(last_purchase_receipt and
+                                    last_purchase_receipt[0].posting_date or "1900-01-01")
 
-	if (purchase_order_date > purchase_receipt_date) or \
-			(last_purchase_order and not last_purchase_receipt):
-		# use purchase order
-		last_purchase = last_purchase_order[0]
-		purchase_date = purchase_order_date
+    if (purchase_order_date > purchase_receipt_date) or \
+            (last_purchase_order and not last_purchase_receipt):
+        # use purchase order
+        last_purchase = last_purchase_order[0]
+        purchase_date = purchase_order_date
 
-	elif (purchase_receipt_date > purchase_order_date) or \
-			(last_purchase_receipt and not last_purchase_order):
-		# use purchase receipt
-		last_purchase = last_purchase_receipt[0]
-		purchase_date = purchase_receipt_date
+    elif (purchase_receipt_date > purchase_order_date) or \
+            (last_purchase_receipt and not last_purchase_order):
+        # use purchase receipt
+        last_purchase = last_purchase_receipt[0]
+        purchase_date = purchase_receipt_date
 
-	else:
-		return frappe._dict()
+    else:
+        return frappe._dict()
 
-	conversion_factor = flt(last_purchase.conversion_factor)
-	out = frappe._dict({
-		"base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor,
-		"base_rate": flt(last_purchase.base_rate) / conversion_factor,
-		"discount_percentage": flt(last_purchase.discount_percentage),
-		"purchase_date": purchase_date
-	})
+    conversion_factor = flt(last_purchase.conversion_factor)
+    out = frappe._dict({
+        "base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor,
+        "base_rate": flt(last_purchase.base_rate) / conversion_factor,
+        "discount_percentage": flt(last_purchase.discount_percentage),
+        "purchase_date": purchase_date
+    })
 
-	conversion_rate = flt(conversion_rate) or 1.0
-	out.update({
-		"price_list_rate": out.base_price_list_rate / conversion_rate,
-		"rate": out.base_rate / conversion_rate,
-		"base_rate": out.base_rate
-	})
+    conversion_rate = flt(conversion_rate) or 1.0
+    out.update({
+        "price_list_rate": out.base_price_list_rate / conversion_rate,
+        "rate": out.base_rate / conversion_rate,
+        "base_rate": out.base_rate
+    })
 
-	return out
+    return out
+
 
 def invalidate_cache_for_item(doc):
-	invalidate_cache_for(doc, doc.item_group)
+    invalidate_cache_for(doc, doc.item_group)
 
-	website_item_groups = list(set((doc.get("old_website_item_groups") or [])
-		+ [d.item_group for d in doc.get({"doctype":"Website Item Group"}) if d.item_group]))
+    website_item_groups = list(set((doc.get("old_website_item_groups") or [])
+                                   + [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
 
-	for item_group in website_item_groups:
-		invalidate_cache_for(doc, item_group)
+    for item_group in website_item_groups:
+        invalidate_cache_for(doc, item_group)
 
-	if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group:
-		invalidate_cache_for(doc, doc.old_item_group)
+    if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group:
+        invalidate_cache_for(doc, doc.old_item_group)
+
 
 def check_stock_uom_with_bin(item, stock_uom):
-	if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
-		return
+    if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
+        return
 
-	matched=True
-	ref_uom = frappe.db.get_value("Stock Ledger Entry",
-		{"item_code": item}, "stock_uom")
+    matched = True
+    ref_uom = frappe.db.get_value("Stock Ledger Entry",
+                                  {"item_code": item}, "stock_uom")
 
-	if ref_uom:
-		if cstr(ref_uom) != cstr(stock_uom):
-			matched = False
-	else:
-		bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
-		for bin in bin_list:
-			if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \
-				or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
-					matched = False
-					break
+    if ref_uom:
+        if cstr(ref_uom) != cstr(stock_uom):
+            matched = False
+    else:
+        bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
+        for bin in bin_list:
+            if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0
+                    or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
+                matched = False
+                break
 
-		if matched and bin_list:
-			frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item))
+        if matched and bin_list:
+            frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item))
 
-	if not matched:
-		frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item))
+    if not matched:
+        frappe.throw(
+            _("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item))
diff --git a/erpnext/stock/doctype/item_barcode/__init__.py b/erpnext/stock/doctype/item_barcode/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/doctype/item_barcode/__init__.py
diff --git a/erpnext/stock/doctype/item_barcode/item_barcode.json b/erpnext/stock/doctype/item_barcode/item_barcode.json
new file mode 100644
index 0000000..c8a3a89
--- /dev/null
+++ b/erpnext/stock/doctype/item_barcode/item_barcode.json
@@ -0,0 +1,103 @@
+{
+ "allow_copy": 0, 
+ "allow_guest_to_view": 0, 
+ "allow_import": 0, 
+ "allow_rename": 0, 
+ "autoname": "field:barcode", 
+ "beta": 0, 
+ "creation": "2017-12-09 18:54:50.562438", 
+ "custom": 0, 
+ "docstatus": 0, 
+ "doctype": "DocType", 
+ "document_type": "", 
+ "editable_grid": 1, 
+ "engine": "InnoDB", 
+ "fields": [
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "barcode", 
+   "fieldtype": "Data", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 1, 
+   "in_list_view": 1, 
+   "in_standard_filter": 0, 
+   "label": "Barcode", 
+   "length": 0, 
+   "no_copy": 1, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 1, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 1
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "barcode_type", 
+   "fieldtype": "Select", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 1, 
+   "in_standard_filter": 0, 
+   "label": "Barcode Type", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "\nEAN\nUPC-A", 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }
+ ], 
+ "has_web_view": 0, 
+ "hide_heading": 0, 
+ "hide_toolbar": 0, 
+ "idx": 0, 
+ "image_view": 0, 
+ "in_create": 0, 
+ "is_submittable": 0, 
+ "issingle": 0, 
+ "istable": 1, 
+ "max_attachments": 0, 
+ "modified": "2017-12-10 20:55:23.814039", 
+ "modified_by": "Administrator", 
+ "module": "Stock", 
+ "name": "Item Barcode", 
+ "name_case": "", 
+ "owner": "Administrator", 
+ "permissions": [], 
+ "quick_entry": 1, 
+ "read_only": 0, 
+ "read_only_onload": 0, 
+ "show_name_in_global_search": 0, 
+ "sort_field": "modified", 
+ "sort_order": "DESC", 
+ "track_changes": 1, 
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item_barcode/item_barcode.py b/erpnext/stock/doctype/item_barcode/item_barcode.py
new file mode 100644
index 0000000..e85f93b
--- /dev/null
+++ b/erpnext/stock/doctype/item_barcode/item_barcode.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+
+from frappe.model.document import Document
+
+
+class ItemBarcode(Document):
+    pass
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 89ece33..23a18f4 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -25,7 +25,7 @@
 			frappe.msgprint (_("`Freeze Stocks Older Than` should be smaller than %d days.") %stock_frozen_limit)
 
 		# show/hide barcode field
-		frappe.make_property_setter({'fieldname': 'barcode', 'property': 'hidden',
+		frappe.make_property_setter({'fieldname': 'barcodes', 'property': 'hidden',
 			'value': 0 if self.show_barcode_field else 1})
 
 		self.cant_change_valuation_method()
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index a7638b4..2f7d3d6 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -120,7 +120,7 @@
 @frappe.whitelist()
 def get_item_code(barcode=None, serial_no=None):
 	if barcode:
-		item_code = frappe.db.get_value("Item", {"barcode": barcode})
+		item_code = frappe.db.get_value("Item Barcode", {"barcode": barcode}, fieldname=["parent"])
 		if not item_code:
 			frappe.throw(_("No Item with Barcode {0}").format(barcode))
 	elif serial_no:
@@ -273,7 +273,7 @@
 			if not out[d[1]] or (company and args.company != company):
 				out[d[1]] = frappe.db.get_value("Company", args.company, d[2]) if d[2] else None
 
-	for fieldname in ("item_name", "item_group", "barcode", "brand", "stock_uom"):
+	for fieldname in ("item_name", "item_group", "barcodes", "brand", "stock_uom"):
 		out[fieldname] = item.get(fieldname)
 
 	return out
diff --git a/requirements.txt b/requirements.txt
index a6cfaf2..7b5d8da 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,4 @@
 unidecode
 pygithub
 googlemaps
+python-stdnum