Merge pull request #4250 from anandpdoshi/disable-item

[enhancement] Ability to disable an Item
diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.py b/erpnext/buying/doctype/purchase_common/purchase_common.py
index 28ea23c..7ec10a6 100644
--- a/erpnext/buying/doctype/purchase_common/purchase_common.py
+++ b/erpnext/buying/doctype/purchase_common/purchase_common.py
@@ -56,11 +56,11 @@
 					d.set(x, f_lst[x])
 
 			item = frappe.db.sql("""select is_stock_item, is_purchase_item,
-				is_sub_contracted_item, end_of_life from `tabItem` where name=%s""",
+				is_sub_contracted_item, end_of_life, disabled from `tabItem` where name=%s""",
 				d.item_code, as_dict=1)[0]
 
 			from erpnext.stock.doctype.item.item import validate_end_of_life
-			validate_end_of_life(d.item_code, item.end_of_life)
+			validate_end_of_life(d.item_code, item.end_of_life, item.disabled)
 
 			# validate stock item
 			if item.is_stock_item==1 and d.qty and not d.warehouse:
@@ -72,6 +72,7 @@
 					frappe.throw(_("{0} must be a Purchased or Sub-Contracted Item in row {1}").format(d.item_code, d.idx))
 
 			items.append(cstr(d.item_code))
+
 		if items and len(items) != len(set(items)) and \
 			not cint(frappe.db.get_single_value("Buying Settings", "allow_multiple_items") or 0):
 			frappe.msgprint(_("Warning: Same item has been entered multiple times."))
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index e2de6c3..ffc682f 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -166,6 +166,7 @@
 		from tabItem
 		where tabItem.docstatus < 2
 			and ifnull(tabItem.has_variants, 0)=0
+			and tabItem.disabled=0
 			and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00')
 			and (tabItem.`{key}` LIKE %(txt)s
 				or tabItem.item_name LIKE %(txt)s
@@ -303,10 +304,10 @@
 	# Hence the first condition is an "OR"
 	if not filters: filters = {}
 
-	condition = ""	
+	condition = ""
 	if filters.get("company"):
 		condition += "and tabAccount.company = %(company)s"
-	
+
 	return frappe.db.sql("""select tabAccount.name from `tabAccount`
 			where (tabAccount.report_type = "Profit and Loss"
 					or tabAccount.account_type in ("Income Account", "Temporary"))
@@ -314,6 +315,6 @@
 				and tabAccount.`{key}` LIKE %(txt)s
 				{condition} {match_condition}"""
 			.format(condition=condition, match_condition=get_match_cond(doctype), key=searchfield), {
-				'txt': "%%%s%%" % frappe.db.escape(txt), 
+				'txt': "%%%s%%" % frappe.db.escape(txt),
 				'company': filters.get("company", "")
-			})
\ No newline at end of file
+			})
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js
index 00eac9a..7c92f2d 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.js
+++ b/erpnext/manufacturing/doctype/production_order/production_order.js
@@ -140,7 +140,7 @@
 			} else msgprint(__("Please enter Production Item first"));
 		});
 	},
-	
+
 	set_default_warehouse: function(frm) {
 		frappe.call({
 			method: "erpnext.manufacturing.doctype.production_order.production_order.get_default_warehouse",
diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py
index e5e0a96..dbd3250 100644
--- a/erpnext/manufacturing/doctype/production_order/production_order.py
+++ b/erpnext/manufacturing/doctype/production_order/production_order.py
@@ -4,7 +4,7 @@
 from __future__ import unicode_literals
 import frappe
 
-from frappe.utils import flt, get_datetime, getdate, date_diff, cint
+from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate
 from frappe import _
 from frappe.model.document import Document
 from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
@@ -159,22 +159,22 @@
 
 	def on_cancel(self):
 		self.validate_cancel()
-		
+
 		frappe.db.set(self,'status', 'Cancelled')
 		self.update_planned_qty()
 		self.delete_time_logs()
-		
+
 	def validate_cancel(self):
 		if self.status == "Stopped":
 			frappe.throw(_("Stopped Production Order cannot be cancelled, Unstop it first to cancel"))
-		
+
 		# Check whether any stock entry exists against this Production Order
 		stock_entry = frappe.db.sql("""select name from `tabStock Entry`
 			where production_order = %s and docstatus = 1""", self.name)
 		if stock_entry:
 			frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(stock_entry[0][0]))
 
-	def update_planned_qty(self):		
+	def update_planned_qty(self):
 		update_bin_qty(self.production_item, self.fg_warehouse, {
 			"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)
 		})
@@ -342,8 +342,8 @@
 @frappe.whitelist()
 def get_item_details(item):
 	res = frappe.db.sql("""select stock_uom, description
-		from `tabItem` where (ifnull(end_of_life, "0000-00-00")="0000-00-00" or end_of_life > now())
-		and name=%s""", item, as_dict=1)
+		from `tabItem` where disabled=0 and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)
+		and name=%s""", (nowdate(), item), as_dict=1)
 	if not res:
 		return {}
 
diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.py b/erpnext/manufacturing/doctype/production_order/test_production_order.py
index eb26d29..cfea4e5 100644
--- a/erpnext/manufacturing/doctype/production_order/test_production_order.py
+++ b/erpnext/manufacturing/doctype/production_order/test_production_order.py
@@ -86,7 +86,7 @@
 		self.assertEqual(prod_order.name, time_log.production_order)
 		self.assertEqual((prod_order.qty - d.completed_qty), time_log.completed_qty)
 		self.assertEqual(time_diff_in_hours(d.planned_end_time, d.planned_start_time),time_log.hours)
-		
+
 		manufacturing_settings = frappe.get_doc({
 			"doctype": "Manufacturing Settings",
 			"allow_production_on_holidays": 0
@@ -136,6 +136,11 @@
 		self.assertRaises(frappe.ValidationError, prod_order.save)
 
 		frappe.db.set_value("Item", "_Test FG Item", "end_of_life", None)
+		frappe.db.set_value("Item", "_Test FG Item", "disabled", 1)
+
+		self.assertRaises(frappe.ValidationError, prod_order.save)
+
+		frappe.db.set_value("Item", "_Test FG Item", "disabled", 0)
 
 		prod_order = make_prod_order_test_record(item="_Test Variant Item", qty=1, do_not_save=True)
 		self.assertRaises(ItemHasVariantError, prod_order.save)
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index bc2d48f..9a4497b 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -72,6 +72,7 @@
 			concat(parent_website_route, "/", page_name) as route
 		from `tabItem`
 		where show_in_website = 1
+			and disabled=0
 			and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
 			and (variant_of = '' or variant_of is null)
 			and (item_group in ({child_groups})
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index b36e34c..bef4e25 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -182,31 +182,6 @@
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
-   "default": "2099-12-31", 
-   "depends_on": "is_stock_item", 
-   "fieldname": "end_of_life", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "End of Life", 
-   "no_copy": 0, 
-   "oldfieldname": "end_of_life", 
-   "oldfieldtype": "Date", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
    "fieldname": "column_break0", 
    "fieldtype": "Column Break", 
    "hidden": 0, 
@@ -253,6 +228,28 @@
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "fieldname": "disabled", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "in_filter": 0, 
+   "in_list_view": 0, 
+   "label": "Disabled", 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "read_only": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
    "fieldname": "image", 
    "fieldtype": "Attach", 
    "hidden": 0, 
@@ -440,6 +437,31 @@
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
+   "default": "2099-12-31", 
+   "depends_on": "is_stock_item", 
+   "fieldname": "end_of_life", 
+   "fieldtype": "Date", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "in_filter": 0, 
+   "in_list_view": 0, 
+   "label": "End of Life", 
+   "no_copy": 0, 
+   "oldfieldname": "end_of_life", 
+   "oldfieldtype": "Date", 
+   "permlevel": 0, 
+   "print_hide": 0, 
+   "read_only": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
    "default": "", 
    "depends_on": "eval:doc.is_stock_item", 
    "fieldname": "has_batch_no", 
@@ -2113,7 +2135,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 1, 
- "modified": "2015-10-20 12:14:43.315827", 
+ "modified": "2015-10-29 02:25:26.256373", 
  "modified_by": "Administrator", 
  "module": "Stock", 
  "name": "Item", 
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index db7b50a..1d81845 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -524,14 +524,17 @@
 				if variant and self.get("__islocal"):
 					frappe.throw(_("Item variant {0} exists with same attributes").format(variant), ItemVariantExistsError)
 
-def validate_end_of_life(item_code, end_of_life=None, verbose=1):
-	if not end_of_life:
-		end_of_life = frappe.db.get_value("Item", item_code, "end_of_life")
+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 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)
+
 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")
diff --git a/erpnext/stock/doctype/item/item_list.js b/erpnext/stock/doctype/item/item_list.js
index 46a22ea..1074bd0 100644
--- a/erpnext/stock/doctype/item/item_list.js
+++ b/erpnext/stock/doctype/item/item_list.js
@@ -1,16 +1,18 @@
 frappe.listview_settings['Item'] = {
 	add_fields: ["item_name", "stock_uom", "item_group", "image", "variant_of",
-		"has_variants", "end_of_life", "is_sales_item"],
+		"has_variants", "end_of_life", "disabled", "is_sales_item"],
 
 	get_indicator: function(doc) {
-		if(doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) {
-			return [__("Expired"), "grey", "end_of_life,<,Today"]
-		} else if(doc.has_variants) {
-			return [__("Template"), "blue", "has_variants,=,Yes"]
-		} else if(doc.variant_of) {
-			return [__("Variant"), "green", "variant_of,=," + doc.variant_of]
+		if (doc.disabled) {
+			return [__("Disabled"), "grey", "disabled,=,Yes"];
+		} else if (doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) {
+			return [__("Expired"), "grey", "end_of_life,<,Today"];
+		} else if (doc.has_variants) {
+			return [__("Template"), "blue", "has_variants,=,Yes"];
+		} else if (doc.variant_of) {
+			return [__("Variant"), "green", "variant_of,=," + doc.variant_of];
 		} else {
-			return [__("Active"), "blue", "end_of_life,>=,Today"]
+			return [__("Active"), "blue", "end_of_life,>=,Today"];
 		}
 	}
 };
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 868053e..79cd8de 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -5,7 +5,7 @@
 import frappe
 import frappe.defaults
 from frappe import _
-from frappe.utils import cstr, cint, flt, comma_or, getdate
+from frappe.utils import cstr, cint, flt, comma_or, getdate, nowdate
 from erpnext.stock.utils import get_incoming_rate
 from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError
 from erpnext.stock.get_item_details import get_available_qty, get_default_cost_center, get_conversion_factor
@@ -359,7 +359,7 @@
 
 	def update_stock_ledger(self):
 		sl_entries = []
-		
+
 		# make sl entries for source warehouse first, then do for target warehouse
 		for d in self.get('items'):
 			if cstr(d.s_warehouse):
@@ -368,7 +368,7 @@
 					"actual_qty": -flt(d.transfer_qty),
 					"incoming_rate": 0
 				}))
-				
+
 		for d in self.get('items'):
 			if cstr(d.t_warehouse):
 				sl_entries.append(self.get_sl_entries(d, {
@@ -438,8 +438,10 @@
 	def get_item_details(self, args=None, for_update=False):
 		item = frappe.db.sql("""select stock_uom, description, image, item_name,
 			expense_account, buying_cost_center, item_group from `tabItem`
-			where name = %s and (ifnull(end_of_life,'0000-00-00')='0000-00-00' or end_of_life > now())""",
-			(args.get('item_code')), as_dict = 1)
+			where name = %s
+				and disabled=0
+				and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)""",
+			(args.get('item_code'), nowdate()), as_dict = 1)
 		if not item:
 			frappe.throw(_("Item {0} is not active or end of life has been reached").format(args.get("item_code")))
 
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index a275de5..cff0041 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -130,7 +130,7 @@
 			item = frappe.get_doc("Item", item_code)
 
 			# end of life and stock item
-			validate_end_of_life(item_code, item.end_of_life, verbose=0)
+			validate_end_of_life(item_code, item.end_of_life, item.disabled, verbose=0)
 			validate_is_stock_item(item_code, item.is_stock_item, verbose=0)
 
 			# item should not be serialized
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 3fa3ea9..1f0b637 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -113,7 +113,7 @@
 		throw(_("Please specify Company"))
 
 	from erpnext.stock.doctype.item.item import validate_end_of_life
-	validate_end_of_life(item.name, item.end_of_life)
+	validate_end_of_life(item.name, item.end_of_life, item.disabled)
 
 	if args.transaction_type == "selling":
 		# validate if sales item or service item
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index d4b0a20..bf06396 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -23,6 +23,7 @@
 	items_to_consider = frappe.db.sql_list("""select name from `tabItem` item
 		where is_stock_item=1 and has_variants=0
 			and (is_purchase_item=1 or is_sub_contracted_item=1)
+			and disabled=0
 			and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
 			and ((re_order_level is not null and re_order_level > 0)
 				or exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index 51e3836..7d3a2ee 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -79,6 +79,7 @@
 
 	items = frappe.db.sql("""select * from `tabItem` item
 		where is_stock_item = 1
+		and disabled=0
 		{condition}
 		and (end_of_life > %(today)s or end_of_life is null or end_of_life='0000-00-00')
 		and exists (select name from `tabBin` bin where bin.item_code=item.name)"""\
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py
index 9a6e8d3..dc2099d 100644
--- a/erpnext/templates/pages/product_search.py
+++ b/erpnext/templates/pages/product_search.py
@@ -16,6 +16,7 @@
 			web_long_description as website_description, parent_website_route
 		from `tabItem`
 		where show_in_website = 1
+			and disabled=0
 			and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
 			and (variant_of is null or variant_of = '')"""