fix: valuation of "finished good" item in purchase receipt (#19268)

* fix: Remove redundant purchase orders and unwanted condition

* fix: [WIP] Purchase receipt value

* fix: Add raw material cost based on transfered raw material

* fix: get_qty_to_be_received

* fix: Remove debugger statement

* fix: Reset rm_supp_cost before setting subcontracted raw_materials

* test: Fix and modify tests for backflush_based_on_stock_entry

* fix: Add non stock items to Purchase Receipt from Purchase Order

* fix: Ignore valuation rate check for non stock raw material

* fix: Rename check all rows

* fix: Remove amount from test

* test: Fix item rate error

* fix: handling of serial nos in backflush

* fix: Add serial no. of raw materials

* fix: [WIP] Handle Batch nos for purchase reciept backflushed raw material

* fix: Raw material batch number selection in purchase receipt

* Update test_purchase_order.py
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index c5fa98d..7b5e5c5 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -18,6 +18,7 @@
 			return {
 				filters: {
 					"company": frm.doc.company,
+					"name": ['!=', frm.doc.supplier_warehouse],
 					"is_group": 0
 				}
 			}
@@ -283,6 +284,8 @@
 			})
 		}
 
+		me.dialog.get_field('sub_con_rm_items').check_all_rows()
+
 		me.dialog.show()
 		this.dialog.set_primary_action(__('Transfer'), function() {
 			me.values = me.dialog.get_values();
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 4506db6..a0a1e8e 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -519,47 +519,62 @@
 	def test_backflush_based_on_stock_entry(self):
 		item_code = "_Test Subcontracted FG Item 1"
 		make_subcontracted_item(item_code)
+		make_item('Sub Contracted Raw Material 1', {
+			'is_stock_item': 1,
+			'is_sub_contracted_item': 1
+		})
 
 		update_backflush_based_on("Material Transferred for Subcontract")
-		po = create_purchase_order(item_code=item_code, qty=1,
+
+		order_qty = 5
+		po = create_purchase_order(item_code=item_code, qty=order_qty,
 			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
 
-		make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
 		make_stock_entry(target="_Test Warehouse - _TC",
 			item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
 		make_stock_entry(target="_Test Warehouse - _TC",
 			item_code = "Test Extra Item 1", qty=100, basic_rate=100)
 		make_stock_entry(target="_Test Warehouse - _TC",
 			item_code = "Test Extra Item 2", qty=10, basic_rate=100)
+		make_stock_entry(target="_Test Warehouse - _TC",
+			item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100)
 
-		rm_item = [
-			{"item_code":item_code,"rm_item_code":"_Test Item","item_name":"_Test Item",
-				"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":100,"stock_uom":"Nos"},
+		rm_items = [
+			{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item",
+				"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
 			{"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100",
-				"qty":2,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"},
+				"qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
 			{"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1",
-				"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}]
+				"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
+			{'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos',
+				'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}]
 
-		rm_item_string = json.dumps(rm_item)
+		rm_item_string = json.dumps(rm_items)
 		se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
-		se.append('items', {
-			'item_code': "Test Extra Item 2",
-			"qty": 1,
-			"rate": 100,
-			"s_warehouse": "_Test Warehouse - _TC",
-			"t_warehouse": "_Test Warehouse 1 - _TC"
-		})
-		se.set_missing_values()
 		se.submit()
 
 		pr = make_purchase_receipt(po.name)
+
+		received_qty = 2
+		# partial receipt
+		pr.get('items')[0].qty = received_qty
 		pr.save()
 		pr.submit()
 
-		se_items = sorted([d.item_code for d in se.get('items')])
-		supplied_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
+		transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name])
+		issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
 
-		self.assertEquals(se_items, supplied_items)
+		self.assertEquals(transferred_items, issued_items)
+		self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000)
+
+
+		transferred_rm_map = frappe._dict()
+		for item in rm_items:
+			transferred_rm_map[item.get('rm_item_code')] = item
+
+		for item in pr.get('supplied_items'):
+			self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
+
 		update_backflush_based_on("BOM")
 
 	def test_advance_payment_entry_unlink_against_purchase_order(self):
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index d12643a..3ec7aff 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -221,7 +221,7 @@
 				"backflush_raw_materials_of_subcontract_based_on")
 			if (self.doctype == 'Purchase Receipt' and
 				backflush_raw_materials_based_on != 'BOM'):
-				self.update_raw_materials_supplied_based_on_stock_entries(raw_material_table)
+				self.update_raw_materials_supplied_based_on_stock_entries()
 			else:
 				for item in self.get("items"):
 					if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
@@ -241,41 +241,95 @@
 		if self.is_subcontracted == "No" and self.get("supplied_items"):
 			self.set('supplied_items', [])
 
-	def update_raw_materials_supplied_based_on_stock_entries(self, raw_material_table):
-		self.set(raw_material_table, [])
-		purchase_orders = [d.purchase_order for d in self.items]
-		if purchase_orders:
-			items = get_subcontracted_raw_materials_from_se(purchase_orders)
-			backflushed_raw_materials = get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, self.name)
+	def update_raw_materials_supplied_based_on_stock_entries(self):
+		self.set('supplied_items', [])
 
-			for d in items:
-				qty = d.qty - backflushed_raw_materials.get(d.item_code, 0)
-				rm = self.append(raw_material_table, {})
-				rm.rm_item_code = d.item_code
-				rm.item_name = d.item_name
-				rm.main_item_code = d.main_item_code
-				rm.description = d.description
-				rm.stock_uom = d.stock_uom
-				rm.required_qty = qty
-				rm.consumed_qty = qty
-				rm.serial_no = d.serial_no
-				rm.batch_no = d.batch_no
+		purchase_orders = set([d.purchase_order for d in self.items])
 
-				# get raw materials rate
-				from erpnext.stock.utils import get_incoming_rate
-				rm.rate = get_incoming_rate({
-					"item_code": d.item_code,
-					"warehouse": self.supplier_warehouse,
-					"posting_date": self.posting_date,
-					"posting_time": self.posting_time,
-					"qty": -1 * qty,
-					"serial_no": rm.serial_no
-				})
-				if not rm.rate:
-					rm.rate = get_valuation_rate(d.item_code, self.supplier_warehouse,
-						self.doctype, self.name, currency=self.company_currency, company = self.company)
+		# qty of raw materials backflushed (for each item per purchase order)
+		backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders)
 
-				rm.amount = qty * flt(rm.rate)
+		# qty of "finished good" item yet to be received
+		qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
+
+		for item in self.get('items'):
+			# reset raw_material cost
+			item.rm_supp_cost = 0
+
+			# qty of raw materials transferred to the supplier
+			transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code)
+
+			non_stock_items = get_non_stock_items(item.purchase_order, item.item_code)
+
+			item_key = '{}{}'.format(item.item_code, item.purchase_order)
+
+			fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
+
+			raw_material_data = backflushed_raw_materials_map.get(item_key, {})
+
+			consumed_qty = raw_material_data.get('qty', 0)
+			consumed_serial_nos = raw_material_data.get('serial_nos', '')
+			consumed_batch_nos = raw_material_data.get('batch_nos', '')
+
+			transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
+			backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
+
+			for raw_material in transferred_raw_materials + non_stock_items:
+				transferred_qty = raw_material.qty
+
+				rm_qty_to_be_consumed = transferred_qty - consumed_qty
+
+				# backflush all remaining transferred qty in the last Purchase Receipt
+				if fg_yet_to_be_received == item.qty:
+					qty = rm_qty_to_be_consumed
+				else:
+					qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty
+
+					if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'):
+						qty = frappe.utils.ceil(qty)
+
+				if qty > rm_qty_to_be_consumed:
+					qty = rm_qty_to_be_consumed
+
+				if not qty: continue
+
+				if raw_material.serial_nos:
+					set_serial_nos(raw_material, consumed_serial_nos, qty)
+
+				if raw_material.batch_nos:
+					batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
+						qty, transferred_batch_qty_map, backflushed_batch_qty_map)
+					for batch_data in batches_qty:
+						qty = batch_data['qty']
+						raw_material.batch_no = batch_data['batch']
+						self.append_raw_material_to_be_backflushed(item, raw_material, qty)
+				else:
+					self.append_raw_material_to_be_backflushed(item, raw_material, qty)
+
+	def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty):
+		rm = self.append('supplied_items', {})
+		rm.update(raw_material_data)
+
+		rm.required_qty = qty
+		rm.consumed_qty = qty
+
+		if not raw_material_data.get('non_stock_item'):
+			from erpnext.stock.utils import get_incoming_rate
+			rm.rate = get_incoming_rate({
+				"item_code": raw_material_data.rm_item_code,
+				"warehouse": self.supplier_warehouse,
+				"posting_date": self.posting_date,
+				"posting_time": self.posting_time,
+				"qty": -1 * qty,
+				"serial_no": rm.serial_no
+			})
+
+			if not rm.rate:
+				rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse,
+					self.doctype, self.name, currency=self.company_currency, company=self.company)
+
+		rm.amount = qty * flt(rm.rate)
+		fg_item_doc.rm_supp_cost += rm.amount
 
 	def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
 		exploded_item = 1
@@ -387,9 +441,11 @@
 			item_codes = list(set(item.item_code for item in
 				self.get("items")))
 			if item_codes:
-				self._sub_contracted_items = [r[0] for r in frappe.db.sql("""select name
-					from `tabItem` where name in (%s) and is_sub_contracted_item=1""" % \
-					(", ".join((["%s"]*len(item_codes))),), item_codes)]
+				items = frappe.get_all('Item', filters={
+					'name': ['in', item_codes],
+					'is_sub_contracted_item': 1
+				})
+				self._sub_contracted_items = [item.name for item in items]
 
 		return self._sub_contracted_items
 
@@ -722,28 +778,72 @@
 
 	return bom_items
 
-def get_subcontracted_raw_materials_from_se(purchase_orders):
-	return frappe.db.sql("""
-		select
-			sed.item_name, sed.item_code, sum(sed.qty) as qty, sed.description,
-			sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no
-		from `tabStock Entry` se,`tabStock Entry Detail` sed
-		where
-			se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor'
-			and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != ''
-		group by sed.item_code, sed.t_warehouse
-	""" % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1)
+def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
+	common_query = """
+		SELECT
+			sed.item_code AS rm_item_code,
+			SUM(sed.qty) AS qty,
+			sed.description,
+			sed.stock_uom,
+			sed.subcontracted_item AS main_item_code,
+			{serial_no_concat_syntax} AS serial_nos,
+			{batch_no_concat_syntax} AS batch_nos
+		FROM `tabStock Entry` se,`tabStock Entry Detail` sed
+		WHERE
+			se.name = sed.parent
+			AND se.docstatus=1
+			AND se.purpose='Send to Subcontractor'
+			AND se.purchase_order = %s
+			AND IFNULL(sed.t_warehouse, '') != ''
+			AND sed.subcontracted_item = %s
+		GROUP BY sed.item_code, sed.subcontracted_item
+	"""
+	raw_materials = frappe.db.multisql({
+		'mariadb': common_query.format(
+			serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)",
+			batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)"
+		),
+		'postgres': common_query.format(
+			serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')",
+			batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')"
+		)
+	}, (purchase_order, fg_item), as_dict=1)
 
-def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchase_receipt):
-	return frappe._dict(frappe.db.sql("""
-		select
-			prsi.rm_item_code as item_code, sum(prsi.consumed_qty) as qty
-		from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
-		where
-			pr.name = pri.parent and pr.name = prsi.parent and pri.purchase_order in (%s)
-			and pri.item_code = prsi.main_item_code and pr.name != '%s' and pr.docstatus = 1
-		group by prsi.rm_item_code
-	""" % (','.join(['%s'] * len(purchase_orders)), purchase_receipt), tuple(purchase_orders)))
+	return raw_materials
+
+def get_backflushed_subcontracted_raw_materials(purchase_orders):
+	common_query = """
+		SELECT
+			CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key,
+			SUM(prsi.consumed_qty) AS qty,
+			{serial_no_concat_syntax} AS serial_nos,
+			{batch_no_concat_syntax} AS batch_nos
+		FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
+		WHERE
+			pr.name = pri.parent
+			AND pr.name = prsi.parent
+			AND pri.purchase_order IN %s
+			AND pri.item_code = prsi.main_item_code
+			AND pr.docstatus = 1
+		GROUP BY prsi.rm_item_code, pri.purchase_order
+	"""
+
+	backflushed_raw_materials = frappe.db.multisql({
+		'mariadb': common_query.format(
+			serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)",
+			batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)"
+		),
+		'postgres': common_query.format(
+			serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')",
+			batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')"
+		)
+	}, (purchase_orders, ), as_dict=1)
+
+	backflushed_raw_materials_map = frappe._dict()
+	for item in backflushed_raw_materials:
+		backflushed_raw_materials_map.setdefault(item.item_key, item)
+
+	return backflushed_raw_materials_map
 
 def get_asset_item_details(asset_items):
 	asset_items_data = {}
@@ -776,3 +876,125 @@
 			error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message))
 
 		frappe.throw(error_message)
+
+def get_qty_to_be_received(purchase_orders):
+	return frappe._dict(frappe.db.sql("""
+		SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key,
+		SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received
+		FROM `tabPurchase Order Item` poi
+		WHERE
+			poi.`parent` in %s
+		GROUP BY poi.`item_code`, poi.`parent`
+		HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`)
+	""", (purchase_orders)))
+
+def get_non_stock_items(purchase_order, fg_item_code):
+	return frappe.db.sql("""
+		SELECT
+			pois.main_item_code,
+			pois.rm_item_code,
+			item.description,
+			pois.required_qty AS qty,
+			pois.rate,
+			1 as non_stock_item,
+			pois.stock_uom
+		FROM `tabPurchase Order Item Supplied` pois, `tabItem` item
+		WHERE
+			pois.`rm_item_code` = item.`name`
+			AND item.is_stock_item = 0
+			AND pois.`parent` = %s
+			AND pois.`main_item_code` = %s
+	""", (purchase_order, fg_item_code), as_dict=1)
+
+
+def set_serial_nos(raw_material, consumed_serial_nos, qty):
+	serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \
+		set(get_serial_nos(consumed_serial_nos))
+	if serial_nos and qty <= len(serial_nos):
+		raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)])
+
+def get_transferred_batch_qty_map(purchase_order, fg_item):
+	# returns
+	# {
+	# 	(item_code, fg_code): {
+	# 		batch1: 10, # qty
+	# 		batch2: 16
+	# 	},
+	# }
+	transferred_batch_qty_map = {}
+	transferred_batches = frappe.db.sql("""
+		SELECT
+			sed.batch_no,
+			SUM(sed.qty) AS qty,
+			sed.item_code
+		FROM `tabStock Entry` se,`tabStock Entry Detail` sed
+		WHERE
+			se.name = sed.parent
+			AND se.docstatus=1
+			AND se.purpose='Send to Subcontractor'
+			AND se.purchase_order = %s
+			AND sed.subcontracted_item = %s
+			AND sed.batch_no IS NOT NULL
+		GROUP BY
+			sed.batch_no,
+			sed.item_code
+	""", (purchase_order, fg_item), as_dict=1)
+
+	for batch_data in transferred_batches:
+		transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
+		transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
+
+	return transferred_batch_qty_map
+
+def get_backflushed_batch_qty_map(purchase_order, fg_item):
+	# returns
+	# {
+	# 	(item_code, fg_code): {
+	# 		batch1: 10, # qty
+	# 		batch2: 16
+	# 	},
+	# }
+	backflushed_batch_qty_map = {}
+	backflushed_batches = frappe.db.sql("""
+		SELECT
+			pris.batch_no,
+			SUM(pris.consumed_qty) AS qty,
+			pris.rm_item_code AS item_code
+		FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris
+		WHERE
+			pr.name = pri.parent
+			AND pri.parent = pris.parent
+			AND pri.purchase_order = %s
+			AND pri.item_code = pris.main_item_code
+			AND pr.docstatus = 1
+			AND pris.main_item_code = %s
+			AND pris.batch_no IS NOT NULL
+		GROUP BY
+			pris.rm_item_code, pris.batch_no
+	""", (purchase_order, fg_item), as_dict=1)
+
+	for batch_data in backflushed_batches:
+		backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
+		backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
+
+	return backflushed_batch_qty_map
+
+def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map):
+	# Returns available batches to be backflushed based on requirements
+	transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
+	backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
+
+	available_batches = []
+
+	for (batch, transferred_qty) in transferred_batches.items():
+		backflushed_qty = backflushed_batches.get(batch, 0)
+		available_qty = transferred_qty - backflushed_qty
+
+		if available_qty >= required_qty:
+			available_batches.append({'batch': batch, 'qty': required_qty})
+			break
+		else:
+			available_batches.append({'batch': batch, 'qty': available_qty})
+			required_qty -= available_qty
+
+	return available_batches
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index 2ca4d16..31a9fdb 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -9,6 +9,7 @@
 from six import string_types
 from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order
 from frappe.model.document import Document
+import click
 
 class BOMUpdateTool(Document):
 	def replace_bom(self):
@@ -17,7 +18,8 @@
 		frappe.cache().delete_key('bom_children')
 		bom_list = self.get_parent_boms(self.new_bom)
 		updated_bom = []
-
+		with click.progressbar(bom_list) as bom_list:
+			pass
 		for bom in bom_list:
 			try:
 				bom_obj = frappe.get_cached_doc('BOM', bom)
diff --git a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
index caf7eb8..48c0f42 100644
--- a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
+++ b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json
@@ -15,7 +15,7 @@
  "prepared_report": 0,
  "query": "SELECT\n\t`poi_pri`.`purchase_order` as \"Purchase Order:Link/Purchase Order:120\",\n\t`poi_pri`.`status` as \"Status:Data:120\",\n\t`poi_pri`.`transaction_date` as \"Date:Date:100\",\n\t`poi_pri`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`poi_pri`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`poi_pri`.`supplier_name` as \"Supplier Name::150\",\n\t`poi_pri`.`item_code` as \"Item Code:Link/Item:120\",\n\t`poi_pri`.`qty` as \"Qty:Float:100\",\n\t`poi_pri`.`base_amount` as  \"Base Amount:Currency:100\",\n\t`poi_pri`.`received_qty` as \"Received Qty:Float:100\",\n\t`poi_pri`.`received_amount` as \"Received Qty Amount:Currency:100\",\n\t`poi_pri`.`qty_to_receive` as \"Qty to Receive:Float:100\",\n\t`poi_pri`.`amount_to_be_received` as \"Amount to Receive:Currency:100\",\n\t`poi_pri`.`billed_amount` as  \"Billed Amount:Currency:100\",\n\t`poi_pri`.`amount_to_be_billed` as  \"Amount To Be Billed:Currency:100\",\n\tSUM(`pii`.`qty`) AS \"Billed Qty:Float:100\",\n\t`poi_pri`.qty - SUM(`pii`.`qty`) AS \"Qty To Be Billed:Float:100\",\n\t`poi_pri`.`warehouse` as \"Warehouse:Link/Warehouse:150\",\n\t`poi_pri`.`item_name` as \"Item Name::150\",\n\t`poi_pri`.`description` as \"Description::200\",\n\t`poi_pri`.`brand` as \"Brand::100\",\n\t`poi_pri`.`project` as \"Project\",\n\t`poi_pri`.`company` as \"Company:Link/Company:\"\nFROM\n\t(SELECT\n\t\t`po`.`name` AS 'purchase_order',\n\t\t`po`.`status`,\n\t\t`po`.`company`,\n\t\t`poi`.`warehouse`,\n\t\t`poi`.`brand`,\n\t\t`poi`.`description`,\n\t\t`po`.`transaction_date`,\n\t\t`poi`.`schedule_date`,\n\t\t`po`.`supplier`,\n\t\t`po`.`supplier_name`,\n\t\t`poi`.`project`,\n\t\t`poi`.`item_code`,\n\t\t`poi`.`item_name`,\n\t\t`poi`.`qty`,\n\t\t`poi`.`base_amount`,\n\t\t`poi`.`received_qty`,\n\t\t(`poi`.billed_amt * ifnull(`po`.conversion_rate, 1)) as billed_amount,\n\t\t(`poi`.base_amount - (`poi`.billed_amt * ifnull(`po`.conversion_rate, 1))) as amount_to_be_billed,\n\t\t`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0) AS 'qty_to_receive',\n\t\t(`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0)) * `poi`.`rate` AS 'amount_to_be_received',\n\t\tSUM(`pri`.`amount`) AS 'received_amount',\n\t\t`poi`.`name` AS 'poi_name',\n\t\t`pri`.`name` AS 'pri_name'\n\tFROM\n\t\t`tabPurchase Order` po\n\t\tLEFT JOIN `tabPurchase Order Item` poi\n\t\tON `poi`.`parent` = `po`.`name`\n\t\tLEFT JOIN `tabPurchase Receipt Item` pri\n\t\tON `pri`.`purchase_order_item` = `poi`.`name`\n\t\t\tAND `pri`.`docstatus`=1\n\tWHERE\n\t\t`po`.`status` not in ('Stopped', 'Closed')\n\t\tAND `po`.`docstatus` = 1\n\t\tAND IFNULL(`poi`.`received_qty`, 0) < IFNULL(`poi`.`qty`, 0)\n\tGROUP BY `poi`.`name`\n\tORDER BY `po`.`transaction_date` ASC\n\t) poi_pri\n\tLEFT JOIN `tabPurchase Invoice Item` pii\n\tON `pii`.`po_detail` = `poi_pri`.`poi_name`\n\t\tAND `pii`.`docstatus`=1\nGROUP BY `poi_pri`.`poi_name`",
  "ref_doctype": "Purchase Order",
- "report_name": "Purchase Order Items To Be Received or Billed1",
+ "report_name": "Purchase Order Items To Be Received or Billed",
  "report_type": "Query Report",
  "roles": [
   {
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index db7f6ad..d757ecb 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -122,8 +122,8 @@
 	cf_field = cf_join = ""
 	if include_uom:
 		cf_field = ", ucd.conversion_factor"
-		cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \
-			% (include_uom)
+		cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s" \
+			% frappe.db.escape(include_uom)
 
 	res = frappe.db.sql("""
 		select