Merge pull request #23666 from marination/payment-reco-mandatory-check

fix: Payment Reconciliation client side validations
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index d62e73b..8925b87 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -361,6 +361,7 @@
    "fieldname": "bill_date",
    "fieldtype": "Date",
    "label": "Supplier Invoice Date",
+   "no_copy": 1,
    "oldfieldname": "bill_date",
    "oldfieldtype": "Date",
    "print_hide": 1
@@ -1333,8 +1334,7 @@
  "icon": "fa fa-file-text",
  "idx": 204,
  "is_submittable": 1,
- "links": [],
- "modified": "2020-08-03 23:20:04.466153",
+ "modified": "2020-09-21 12:22:09.164068",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 5b14d05..b6552d5 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -322,12 +322,13 @@
 		work_orders = []
 		bom_data = {}
 
-		get_sub_assembly_items(item.get("bom_no"), bom_data)
+		get_sub_assembly_items(item.get("bom_no"), bom_data, item.get("qty"))
 
 		for key, data in bom_data.items():
 			data.update({
-				'qty': data.get("stock_qty") * item.get("qty"),
+				'qty': data.get("stock_qty"),
 				'production_plan': self.name,
+				'use_multi_level_bom': item.get("use_multi_level_bom"),
 				'company': self.company,
 				'fg_warehouse': item.get("fg_warehouse"),
 				'update_consumed_material_cost_in_project': 0
@@ -781,7 +782,7 @@
 #		"description": item_details.get("description")
 	}
 
-def get_sub_assembly_items(bom_no, bom_data):
+def get_sub_assembly_items(bom_no, bom_data, to_produce_qty):
 	data = get_children('BOM', parent = bom_no)
 	for d in data:
 		if d.expandable:
@@ -798,6 +799,6 @@
 				})
 
 			bom_item = bom_data.get(key)
-			bom_item["stock_qty"] += d.stock_qty / d.parent_bom_qty
+			bom_item["stock_qty"] += (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
 
-			get_sub_assembly_items(bom_item.get("bom_no"), bom_data)
+			get_sub_assembly_items(bom_item.get("bom_no"), bom_data, bom_item["stock_qty"])
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index ca67d71..e728cc2 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -158,6 +158,46 @@
 		self.assertTrue(mr.material_request_type, 'Customer Provided')
 		self.assertTrue(mr.customer, '_Test Customer')
 
+	def test_production_plan_with_multi_level_bom(self):
+		#|Item Code			|	Qty	|
+		#|Test BOM 1	 		|	1	|
+		#|	Test BOM 2		|	2	|
+		#|		Test BOM 3	|	3	|
+
+		for item_code in ["Test BOM 1", "Test BOM 2", "Test BOM 3", "Test RM BOM 1"]:
+			create_item(item_code, is_stock_item=1)
+
+		# created bom upto 3 level
+		if not frappe.db.get_value('BOM', {'item': "Test BOM 3"}):
+			make_bom(item = "Test BOM 3", raw_materials = ["Test RM BOM 1"], rm_qty=3)
+
+		if not frappe.db.get_value('BOM', {'item': "Test BOM 2"}):
+			make_bom(item = "Test BOM 2", raw_materials = ["Test BOM 3"], rm_qty=3)
+
+		if not frappe.db.get_value('BOM', {'item': "Test BOM 1"}):
+			make_bom(item = "Test BOM 1", raw_materials = ["Test BOM 2"], rm_qty=2)
+
+		item_code = "Test BOM 1"
+		pln = frappe.new_doc('Production Plan')
+		pln.company = "_Test Company"
+		pln.append("po_items", {
+			"item_code": item_code,
+			"bom_no": frappe.db.get_value('BOM', {'item': "Test BOM 1"}),
+			"planned_qty": 3,
+			"make_work_order_for_sub_assembly_items": 1
+		})
+
+		pln.submit()
+		pln.make_work_order()
+
+		#last level sub-assembly work order produce qty
+		to_produce_qty = frappe.db.get_value("Work Order",
+			{"production_plan": pln.name, "production_item": "Test BOM 3"}, "qty")
+
+		self.assertEqual(to_produce_qty, 18.0)
+		pln.cancel()
+		frappe.delete_doc("Production Plan", pln.name)
+
 def create_production_plan(**args):
 	args = frappe._dict(args)
 
@@ -205,7 +245,7 @@
 
 		bom.append('items', {
 			'item_code': item,
-			'qty': 1,
+			'qty': args.rm_qty or 1.0,
 			'uom': item_doc.stock_uom,
 			'stock_uom': item_doc.stock_uom,
 			'rate': item_doc.valuation_rate or args.rate,
@@ -213,4 +253,4 @@
 
 	bom.insert(ignore_permissions=True)
 	bom.submit()
-	return bom
\ No newline at end of file
+	return bom
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 62a5d4e..fe3fa82 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -5,7 +5,7 @@
 import frappe
 import json
 import frappe.utils
-from frappe.utils import cstr, flt, getdate, cint, nowdate, add_days, get_link_to_form
+from frappe.utils import cstr, flt, getdate, cint, nowdate, add_days, get_link_to_form, strip_html
 from frappe import _
 from six import string_types
 from frappe.model.utils import get_fetch_values
@@ -993,15 +993,20 @@
 	))
 	for item in raw_materials:
 		item_doc = frappe.get_cached_doc('Item', item.get('item_code'))
+
 		schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days))
-		material_request.append('items', {
-		'item_code': item.get('item_code'),
-		'qty': item.get('quantity'),
-		'schedule_date': schedule_date,
-		'warehouse': item.get('warehouse'),
-		'sales_order': sales_order,
-		'project': project
+		row = material_request.append('items', {
+			'item_code': item.get('item_code'),
+			'qty': item.get('quantity'),
+			'schedule_date': schedule_date,
+			'warehouse': item.get('warehouse'),
+			'sales_order': sales_order,
+			'project': project
 		})
+
+		if not (strip_html(item.get("description")) and strip_html(item_doc.description)):
+			row.description = item_doc.item_name or item.get('item_code')
+
 	material_request.insert()
 	material_request.flags.ignore_permissions = 1
 	material_request.run_method("set_missing_values")