Merge pull request #12602 from PawanMeh/fixes_8540

Select multiple raw material items for Sub Contract Stock Entries
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index d500081..3635b0b 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -143,23 +143,124 @@
 		var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; });
 		var me = this;
 
-		if(items.length===1) {
-			me._make_stock_entry(items[0]);
-			return;
+		if(items.length >= 1){
+			me.raw_material_data = [];
+			me.show_dialog = 1;
+			let title = "";
+			let fields = [
+			{fieldtype:'Section Break', label: __('Raw Materials')},
+			{fieldname: 'sub_con_rm_items', fieldtype: 'Table',
+				fields: [
+					{
+						fieldtype:'Data',
+						fieldname:'item_code',
+						label: __('Item'),
+						read_only:1,
+						in_list_view:1
+					},
+					{
+						fieldtype:'Data',
+						fieldname:'rm_item_code',
+						label: __('Raw Material'),
+						read_only:1,
+						in_list_view:1
+					},
+					{
+						fieldtype:'Float',
+						read_only:1,
+						fieldname:'qty',
+						label: __('Quantity'),
+						read_only:1,
+						in_list_view:1
+					},
+					{
+						fieldtype:'Data',
+						read_only:1,
+						fieldname:'warehouse',
+						label: __('Reserve Warehouse'),
+						in_list_view:1
+					},
+					{
+						fieldtype:'Float',
+						read_only:1,
+						fieldname:'rate',
+						label: __('Rate'),
+						hidden:1
+					},
+					{
+						fieldtype:'Float',
+						read_only:1,
+						fieldname:'amount',
+						label: __('Amount'),
+						hidden:1
+					},
+					{
+						fieldtype:'Link',
+						read_only:1,
+						fieldname:'uom',
+						label: __('UOM'),
+						hidden:1
+					}
+				],
+				data: me.raw_material_data,
+				get_data: function() {
+					return me.raw_material_data;
+				}
+			}
+		]
+
+		me.dialog = new frappe.ui.Dialog({
+			title: title,fields: fields
+			});
+
+		if (me.frm.doc['supplied_items']) {
+			me.frm.doc['supplied_items'].forEach((item, index) => {
+			if (item.rm_item_code && item.main_item_code) {
+					me.raw_material_data.push ({
+						'name':index,
+						'item_code': item.main_item_code,
+						'rm_item_code': item.rm_item_code,
+						'item_name': item.rm_item_code,
+						'qty': item.required_qty,
+						'warehouse':item.reserve_warehouse,
+						'rate':item.rate,
+						'amount':item.amount,
+						'stock_uom':item.stock_uom
+					});
+					me.dialog.fields_dict.sub_con_rm_items.grid.refresh();
+				}
+			})
 		}
-		frappe.prompt({fieldname:"item", options: items, fieldtype:"Select",
-			label: __("Select Item for Transfer"), reqd: 1}, function(data) {
-			me._make_stock_entry(data.item);
-		}, __("Select Item"), __("Make"));
+
+		me.dialog.show()
+		this.dialog.set_primary_action(__('Transfer'), function() {
+			me.values = me.dialog.get_values();
+			if(me.values) {
+				me.values.sub_con_rm_items.map((row,i) => {
+					if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
+						frappe.throw(__("Item Code, warehouse, quantity are required on row" + (i+1)));
+					}
+				})
+				me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
+				me.dialog.hide()
+				}
+			});
+		}
+
+		me.dialog.get_close_btn().on('click', () => {
+			me.dialog.hide();
+		});
+
 	},
 
-	_make_stock_entry: function(item) {
+	_make_rm_stock_entry: function(rm_items) {
 		frappe.call({
-			method:"erpnext.buying.doctype.purchase_order.purchase_order.make_stock_entry",
+			method:"erpnext.buying.doctype.purchase_order.purchase_order.make_rm_stock_entry",
 			args: {
 				purchase_order: cur_frm.doc.name,
-				item_code: item
-			},
+				rm_items: rm_items
+			}
+			,
 			callback: function(r) {
 				var doclist = frappe.model.sync(r.message);
 				frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index b448988..ce0b89b 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -401,23 +401,51 @@
 	return doc
 
 @frappe.whitelist()
-def make_stock_entry(purchase_order, item_code):
-	purchase_order = frappe.get_doc("Purchase Order", purchase_order)
+def make_rm_stock_entry(purchase_order, rm_items):
 
-	stock_entry = frappe.new_doc("Stock Entry")
-	stock_entry.purpose = "Subcontract"
-	stock_entry.purchase_order = purchase_order.name
-	stock_entry.supplier = purchase_order.supplier
-	stock_entry.supplier_name = purchase_order.supplier_name
-	stock_entry.supplier_address = purchase_order.supplier_address
-	stock_entry.address_display = purchase_order.address_display
-	stock_entry.company = purchase_order.company
-	stock_entry.from_bom = 1
-	po_item = [d for d in purchase_order.items if d.item_code == item_code][0]
-	stock_entry.fg_completed_qty = po_item.qty
-	stock_entry.bom_no = po_item.bom
-	stock_entry.get_items()
-	return stock_entry.as_dict()
+	if isinstance(rm_items, basestring):
+		rm_items_list = json.loads(rm_items)
+	else:
+		frappe.throw(_("No Items available for transfer"))
+
+	if rm_items_list:
+		item_code_list = list(set(d["item_code"] for d in rm_items_list))
+	else:
+		frappe.throw(_("No Items selected for transfer"))
+
+	if purchase_order:
+		purchase_order = frappe.get_doc("Purchase Order", purchase_order)
+
+	if item_code_list:
+		item_wh = frappe._dict(frappe.db.sql("""select item_code, description
+										from `tabItem` where name in ({0})""".
+										format(", ".join(["%s"] * len(item_code_list))), item_code_list))
+		stock_entry = frappe.new_doc("Stock Entry")
+		stock_entry.purpose = "Subcontract"
+		stock_entry.purchase_order = purchase_order.name
+		stock_entry.supplier = purchase_order.supplier
+		stock_entry.supplier_name = purchase_order.supplier_name
+		stock_entry.supplier_address = purchase_order.supplier_address
+		stock_entry.address_display = purchase_order.address_display
+		stock_entry.company = purchase_order.company
+		stock_entry.from_bom = 1
+		for item_code in item_code_list:
+			po_item = [d for d in purchase_order.items if d.item_code == item_code][0]
+			bom_no = po_item.bom
+			for rm_item_data in rm_items_list:
+				if rm_item_data["item_code"] == item_code:
+					items_dict = {rm_item_data["rm_item_code"]:
+								{"item_name":rm_item_data["item_name"],
+								"description":item_wh.get(rm_item_data["rm_item_code"]),
+								'qty':rm_item_data["qty"],
+								'from_warehouse':rm_item_data["warehouse"],
+								'stock_uom':rm_item_data["stock_uom"],
+								'bom_no':bom_no}}
+					stock_entry.add_to_stock_entry_detail(items_dict, bom_no)
+		return stock_entry.as_dict()
+	else:
+		frappe.throw(_("No Items selected for transfer"))
+	return purchase_order.name
 
 @frappe.whitelist()
 def update_status(status, name):
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 5ae4d3b..86a1337 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -6,8 +6,9 @@
 import frappe
 import frappe.defaults
 from frappe.utils import flt, add_days, nowdate
-from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_stock_entry as make_subcontract_transfer_entry)
+from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_rm_stock_entry as make_subcontract_transfer_entry)
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+import json
 
 class TestPurchaseOrder(unittest.TestCase):
 	def test_make_purchase_receipt(self):
@@ -202,11 +203,11 @@
 		self.assertEquals(bin2.projected_qty, bin1.projected_qty - 10)
 
 		# Create stock transfer
-		se = frappe.get_doc(make_subcontract_transfer_entry(po.name, "_Test FG Item"))
+		rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item",
+					"qty":6,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":600,"stock_uom":"Nos"}]
+		rm_item_string = json.dumps(rm_item)
+		se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
 		se.to_warehouse = "_Test Warehouse 1 - _TC"
-		for d in se.get("items"):
-			if d.item_code == "_Test Item":
-				d.qty = 6
 		se.save()
 		se.submit()
 
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 58fe442..8fa6517 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -588,9 +588,16 @@
 	if bom.docstatus != 1:
 		if not getattr(frappe.flags, "in_test", False):
 			frappe.throw(_("BOM {0} must be submitted").format(bom_no))
-	if item and not (bom.item.lower() == item.lower() or \
-		bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower()):
-		frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
+	if item:
+		rm_item_exists = False
+		for d in bom.items:
+			if (d.item_code.lower() == item.lower()):
+				rm_item_exists = True
+		if bom.item.lower() == item.lower() or \
+			bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower():
+ 				rm_item_exists = True
+		if not rm_item_exists:
+			frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
 
 @frappe.whitelist()
 def get_children(doctype, parent=None, is_root=False, **filters):
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 4de2002..9bccfad 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -66,6 +66,7 @@
 		self.calculate_rate_and_amount(update_finished_item_rate=False)
 
 	def on_submit(self):
+
 		self.update_stock_ledger()
 
 		from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
@@ -412,7 +413,7 @@
 		"""validation: finished good quantity should be same as manufacturing quantity"""
 		items_with_target_warehouse = []
 		for d in self.get('items'):
-			if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty) and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse):
+			if self.purpose != "Subcontract" and d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty) and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse):
 				frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \
 					format(d.idx, d.transfer_qty, self.fg_completed_qty))
 
@@ -804,7 +805,7 @@
 	def add_to_stock_entry_detail(self, item_dict, bom_no=None):
 		expense_account, cost_center = frappe.db.get_values("Company", self.company, \
 			["default_expense_account", "cost_center"])[0]
-				
+
 		for d in item_dict:
 			stock_uom = item_dict[d].get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom")