[fix] Item Re-order if Bin does not exists and fixed Stock Projected Qty report to show per Warehouse Re-order level
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 252deaa..8a1f8c7 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -566,14 +566,14 @@
 		stock_entry = frappe.get_doc(make_stock_entry(production_order.name, "Manufacture", 1))
 		stock_entry.insert()
 		self.assertTrue("_Test Variant Item-S" in [d.item_code for d in stock_entry.items])
-		
+
 	def test_same_serial_nos_in_repack_or_manufacture_entries(self):
 		s1 = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
 		serial_nos = s1.get("items")[0].serial_no
-		
-		s2 = make_stock_entry(item_code="_Test Serialized Item With Series", source="_Test Warehouse - _TC", 
+
+		s2 = make_stock_entry(item_code="_Test Serialized Item With Series", source="_Test Warehouse - _TC",
 			qty=2, basic_rate=100, purpose="Repack", serial_no=serial_nos, do_not_save=True)
-			
+
 		s2.append("items", {
 			"item_code": "_Test Serialized Item",
 			"t_warehouse": "_Test Warehouse - _TC",
@@ -584,7 +584,7 @@
 			"cost_center": "_Test Cost Center - _TC",
 			"serial_no": serial_nos
 		})
-		
+
 		s2.submit()
 		s2.cancel()
 
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 51dd8c1..d4b0a20 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -15,21 +15,37 @@
 
 def _reorder_item():
 	material_requests = {"Purchase": {}, "Transfer": {}}
-
-	item_warehouse_projected_qty = get_item_warehouse_projected_qty()
-
-	warehouse_company = frappe._dict(frappe.db.sql("""select name, company from `tabWarehouse`"""))
+	warehouse_company = frappe._dict(frappe.db.sql("""select name, company from `tabWarehouse`
+		where disabled=0"""))
 	default_company = (frappe.defaults.get_defaults().get("company") or
 		frappe.db.sql("""select name from tabCompany limit 1""")[0][0])
 
+	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 (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)
+				or (variant_of is not null and variant_of != ''
+					and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of))
+			)""",
+		{"today": nowdate()})
+
+	if not items_to_consider:
+		return
+
+	item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider)
+
 	def add_to_material_request(item_code, warehouse, reorder_level, reorder_qty, material_request_type):
-		if warehouse not in item_warehouse_projected_qty[item_code]:
-			# likely a disabled warehouse or a warehouse where BIN does not exist
+		if warehouse not in warehouse_company:
+			# a disabled warehouse
 			return
 
 		reorder_level = flt(reorder_level)
 		reorder_qty = flt(reorder_qty)
-		projected_qty = item_warehouse_projected_qty[item_code][warehouse]
+
+		# projected_qty will be 0 if Bin does not exist
+		projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse))
 
 		if reorder_level and projected_qty < reorder_level:
 			deficiency = reorder_level - projected_qty
@@ -44,7 +60,7 @@
 				"reorder_qty": reorder_qty
 			})
 
-	for item_code in item_warehouse_projected_qty:
+	for item_code in items_to_consider:
 		item = frappe.get_doc("Item", item_code)
 
 		if item.variant_of and not item.get("reorder_levels"):
@@ -62,18 +78,13 @@
 	if material_requests:
 		return create_material_request(material_requests)
 
-def get_item_warehouse_projected_qty():
+def get_item_warehouse_projected_qty(items_to_consider):
 	item_warehouse_projected_qty = {}
 
 	for item_code, warehouse, projected_qty in frappe.db.sql("""select item_code, warehouse, projected_qty
-		from tabBin where ifnull(item_code, '') != '' and ifnull(warehouse, '') != ''
-		and exists (select name from `tabItem`
-			where `tabItem`.name = `tabBin`.item_code and
-			is_stock_item=1 and (is_purchase_item=1 or is_sub_contracted_item=1) and
-			(ifnull(end_of_life, '0000-00-00')='0000-00-00' or end_of_life > %s))
-		and exists (select name from `tabWarehouse`
-			where `tabWarehouse`.name = `tabBin`.warehouse
-			and ifnull(disabled, 0)=0)""", nowdate()):
+		from tabBin where item_code in ({0})
+			and (warehouse != "" and warehouse is not null)"""\
+		.format(", ".join(["%s"] * len(items_to_consider))), items_to_consider):
 
 		item_warehouse_projected_qty.setdefault(item_code, {})[warehouse] = flt(projected_qty)
 
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 d7dcb23..348cca2 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -6,48 +6,58 @@
 from frappe import _
 
 def execute(filters=None):
-	columns = get_columns()
-		
-	data = frappe.db.sql("""select 
-			item.name, item.item_name, description, item_group, brand, warehouse, item.stock_uom, 
-			actual_qty, planned_qty, indented_qty, ordered_qty, reserved_qty, 
-			projected_qty, item.re_order_level, item.re_order_qty, 
-			(item.re_order_level - projected_qty) as shortage_qty
-		from `tabBin` bin, 
-			(select name, company from tabWarehouse 
-				{warehouse_conditions}) wh,
-			(select name, item_name, description, stock_uom, item_group, 
-				brand, re_order_level, re_order_qty 
-				from `tabItem` {item_conditions}) item
-		where item_code = item.name and warehouse = wh.name
-		order by item.name, wh.name"""\
-		.format(item_conditions=get_item_conditions(filters),
-			warehouse_conditions=get_warehouse_conditions(filters)), filters)
-	
-	return columns, data
-	
+	filters = frappe._dict(filters or {})
+	return get_columns(), get_data(filters)
+
 def get_columns():
-	return [_("Item Code") + ":Link/Item:140", _("Item Name") + "::100", _("Description") + "::200", 
-		_("Item Group") + ":Link/Item Group:100", _("Brand") + ":Link/Brand:100", _("Warehouse") + ":Link/Warehouse:120", 
-		_("UOM") + ":Link/UOM:100", _("Actual Qty") + ":Float:100", _("Planned Qty") + ":Float:100", 
-		_("Requested Qty") + ":Float:110", _("Ordered Qty") + ":Float:100", _("Reserved Qty") + ":Float:100", 
-		_("Projected Qty") + ":Float:100", _("Reorder Level") + ":Float:100", _("Reorder Qty") + ":Float:100", 
+	return [_("Item Code") + ":Link/Item:140", _("Item Name") + "::100", _("Description") + "::200",
+		_("Item Group") + ":Link/Item Group:100", _("Brand") + ":Link/Brand:100", _("Warehouse") + ":Link/Warehouse:120",
+		_("UOM") + ":Link/UOM:100", _("Actual Qty") + ":Float:100", _("Planned Qty") + ":Float:100",
+		_("Requested Qty") + ":Float:110", _("Ordered Qty") + ":Float:100", _("Reserved Qty") + ":Float:100",
+		_("Projected Qty") + ":Float:100", _("Reorder Level") + ":Float:100", _("Reorder Qty") + ":Float:100",
 		_("Shortage Qty") + ":Float:100"]
-	
-def get_item_conditions(filters):
-	conditions = []
-	if filters.get("item_code"):
-		conditions.append("name=%(item_code)s")
-	if filters.get("brand"):
-		conditions.append("brand=%(brand)s")
-	
-	return "where {}".format(" and ".join(conditions)) if conditions else ""
-	
-def get_warehouse_conditions(filters):
-	conditions = []
-	if filters.get("company"):
-		conditions.append("company=%(company)s")
-	if filters.get("warehouse"):
-		conditions.append("name=%(warehouse)s")
-		
-	return "where {}".format(" and ".join(conditions)) if conditions else ""
\ No newline at end of file
+
+def get_data(filters):
+	item_map = {}
+	warehouse_company = {}
+	data = []
+
+	for bin in get_bin_list(filters):
+		item = item_map.setdefault(bin.item_code, frappe.get_doc("Item", bin.item_code))
+		company = warehouse_company.setdefault(bin.warehouse, frappe.db.get_value("Warehouse", bin.warehouse, "company"))
+
+		if filters.brand and filters.brand != item.brand:
+			continue
+
+		elif filters.company and filters.company != company:
+			continue
+
+		re_order_level = re_order_qty = 0
+
+		if bin.warehouse==item.default_warehouse:
+			re_order_level = item.re_order_level or 0
+			re_order_qty = item.re_order_qty or 0
+
+		for d in item.get("reorder_levels"):
+			if d.warehouse == bin.warehouse:
+				re_order_level = d.warehouse_reorder_level
+				re_order_qty = d.warehouse_reorder_qty
+
+		data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
+			item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty, bin.reserved_qty,
+			bin.projected_qty, re_order_level, re_order_qty, re_order_level - bin.projected_qty])
+
+	return data
+
+def get_bin_list(filters):
+	bin_filters = frappe._dict()
+	if filters.item_code:
+		bin_filters.item_code = filters.item_code
+	if filters.warehouse:
+		bin_filters.warehouse = filters.warehouse
+
+	bin_list = frappe.get_all("Bin", fields=["item_code", "warehouse",
+		"actual_qty", "planned_qty", "indented_qty", "ordered_qty", "reserved_qty", "projected_qty"],
+		filters=bin_filters, order_by="item_code, warehouse")
+
+	return bin_list