Merge pull request #23640 from abhishekbalam/address_customisation

diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index 355fe96..6b07197 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -37,6 +37,11 @@
 erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
 	onload: function() {
 		var me = this;
+
+		this.frm.set_query("party", function() {
+			check_mandatory(me.frm);
+		});
+
 		this.frm.set_query("party_type", function() {
 			return {
 				"filters": {
@@ -46,37 +51,39 @@
 		});
 
 		this.frm.set_query('receivable_payable_account', function() {
-			if(!me.frm.doc.company || !me.frm.doc.party_type) {
-				frappe.msgprint(__("Please select Company and Party Type first"));
-			} else {
-				return{
-					filters: {
-						"company": me.frm.doc.company,
-						"is_group": 0,
-						"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
-					}
-				};
-			}
-
+			check_mandatory(me.frm);
+			return {
+				filters: {
+					"company": me.frm.doc.company,
+					"is_group": 0,
+					"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
+				}
+			};
 		});
 
 		this.frm.set_query('bank_cash_account', function() {
-			if(!me.frm.doc.company) {
-				frappe.msgprint(__("Please select Company first"));
-			} else {
-				return{
-					filters:[
-						['Account', 'company', '=', me.frm.doc.company],
-						['Account', 'is_group', '=', 0],
-						['Account', 'account_type', 'in', ['Bank', 'Cash']]
-					]
-				};
-			}
+			check_mandatory(me.frm, true);
+			return {
+				filters:[
+					['Account', 'company', '=', me.frm.doc.company],
+					['Account', 'is_group', '=', 0],
+					['Account', 'account_type', 'in', ['Bank', 'Cash']]
+				]
+			};
 		});
 
 		this.frm.set_value('party_type', '');
 		this.frm.set_value('party', '');
 		this.frm.set_value('receivable_payable_account', '');
+
+		var check_mandatory = (frm, only_company=false) => {
+			var title = __("Mandatory");
+			if (only_company && !frm.doc.company) {
+				frappe.throw({message: __("Please Select a Company First"), title: title});
+			} else if (!frm.doc.company || !frm.doc.party_type) {
+				frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title});
+			}
+		};
 	},
 
 	refresh: function() {
@@ -90,7 +97,7 @@
 
 	party: function() {
 		var me = this
-		if(!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
+		if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
 			return frappe.call({
 				method: "erpnext.accounts.party.get_party_account",
 				args: {
@@ -99,7 +106,7 @@
 					party: me.frm.doc.party
 				},
 				callback: function(r) {
-					if(!r.exc && r.message) {
+					if (!r.exc && r.message) {
 						me.frm.set_value("receivable_payable_account", r.message);
 					}
 				}
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 2f8b634..791b03a 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -99,6 +99,7 @@
 				and `tabGL Entry`.against_voucher_type = %(voucher_type)s
 				and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
 				and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
+				and `tabGL Entry`.is_cancelled = 0
 			GROUP BY `tab{doc}`.name
 			Having
 				amount > 0
diff --git a/erpnext/accounts/doctype/payment_term/payment_term.json b/erpnext/accounts/doctype/payment_term/payment_term.json
index 723d3bd..e77c244 100644
--- a/erpnext/accounts/doctype/payment_term/payment_term.json
+++ b/erpnext/accounts/doctype/payment_term/payment_term.json
@@ -45,6 +45,7 @@
    "unique": 0
   }, 
   {
+   "description": "Provide the invoice portion in percent",
    "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 1, 
@@ -170,6 +171,7 @@
    "unique": 0
   }, 
   {
+   "description": "Give number of days according to prior selection",
    "allow_bulk_edit": 0, 
    "allow_on_submit": 0, 
    "bold": 1, 
@@ -305,7 +307,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-03-08 10:47:32.830478", 
+ "modified": "2020-10-14 10:47:32.830478", 
  "modified_by": "Administrator", 
  "module": "Accounts", 
  "name": "Payment Term", 
@@ -381,4 +383,4 @@
  "sort_order": "DESC", 
  "track_changes": 1, 
  "track_seen": 0
-}
\ No newline at end of file
+}
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/accounts/party.py b/erpnext/accounts/party.py
index 2f800bb..e8d3cd3 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -203,7 +203,7 @@
 	return out
 
 @frappe.whitelist()
-def get_party_account(party_type, party, company):
+def get_party_account(party_type, party, company=None):
 	"""Returns the account for the given `party`.
 		Will first search in party (Customer / Supplier) record, if not found,
 		will search in group (Customer Group / Supplier Group),
diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
index e9dc776..cf0048c 100644
--- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
+++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
@@ -6,18 +6,28 @@
 import frappe
 import unittest
 from frappe.utils import nowdate,flt, cstr,random_string
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.vehicle_log.vehicle_log import make_expense_claim
 
 class TestVehicleLog(unittest.TestCase):
+	def setUp(self):
+		employee_id = frappe.db.sql("""select name from `tabEmployee` where name='testdriver@example.com'""")
+		self.employee_id = employee_id[0][0] if employee_id else None
+
+		if not self.employee_id:
+			self.employee_id = make_employee("testdriver@example.com", company="_Test Company")
+
+		self.license_plate = get_vehicle(self.employee_id)
+	
+	def tearDown(self):
+		frappe.delete_doc("Vehicle", self.license_plate, force=1)
+		frappe.delete_doc("Employee", self.employee_id, force=1)
+
 	def test_make_vehicle_log_and_syncing_of_odometer_value(self):
-		employee_id = frappe.db.sql("""select name from `tabEmployee` where status='Active' order by modified desc limit 1""")
-		employee_id = employee_id[0][0] if employee_id else None
-
-		license_plate = get_vehicle(employee_id)
-
 		vehicle_log = frappe.get_doc({
 			"doctype": "Vehicle Log",
-			"license_plate": cstr(license_plate),
-			"employee":employee_id,
+			"license_plate": cstr(self.license_plate),
+			"employee": self.employee_id,
 			"date":frappe.utils.nowdate(),
 			"odometer":5010,
 			"fuel_qty":frappe.utils.flt(50),
@@ -27,7 +37,7 @@
 		vehicle_log.submit()
 
 		#checking value of vehicle odometer value on submit.
-		vehicle = frappe.get_doc("Vehicle", license_plate)
+		vehicle = frappe.get_doc("Vehicle", self.license_plate)
 		self.assertEqual(vehicle.last_odometer, vehicle_log.odometer)
 
 		#checking value vehicle odometer on vehicle log cancellation.
@@ -40,6 +50,28 @@
 
 		self.assertEqual(vehicle.last_odometer, current_odometer - distance_travelled)
 
+		vehicle_log.delete()
+	
+	def test_vehicle_log_fuel_expense(self):
+		vehicle_log = frappe.get_doc({
+			"doctype": "Vehicle Log",
+			"license_plate": cstr(self.license_plate),
+			"employee": self.employee_id,
+			"date": frappe.utils.nowdate(),
+			"odometer":5010,
+			"fuel_qty":frappe.utils.flt(50),
+			"price": frappe.utils.flt(500)
+		})
+		vehicle_log.save()
+		vehicle_log.submit()
+
+		expense_claim = make_expense_claim(vehicle_log.name)
+		fuel_expense = expense_claim.expenses[0].amount
+		self.assertEqual(fuel_expense, 50*500)
+
+		vehicle_log.cancel()
+		frappe.delete_doc("Expense Claim", expense_claim.name)
+		frappe.delete_doc("Vehicle Log", vehicle_log.name)
 
 def get_vehicle(employee_id):
 	license_plate=random_string(10).upper()
diff --git a/erpnext/hr/doctype/vehicle_log/vehicle_log.py b/erpnext/hr/doctype/vehicle_log/vehicle_log.py
index 8affab2..04c94e3 100644
--- a/erpnext/hr/doctype/vehicle_log/vehicle_log.py
+++ b/erpnext/hr/doctype/vehicle_log/vehicle_log.py
@@ -32,7 +32,7 @@
 	vehicle_log = frappe.get_doc("Vehicle Log", docname)
 	service_expense = sum([flt(d.expense_amount) for d in vehicle_log.service_detail])
 
-	claim_amount = service_expense + flt(vehicle_log.price)
+	claim_amount = service_expense + (flt(vehicle_log.price) * flt(vehicle_log.fuel_qty) or 1)
 	if not claim_amount:
 		frappe.throw(_("No additional expenses has been added"))
 
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/quality_management/doctype/quality_procedure/quality_procedure.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
index cf2644e..ac87622 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.js
@@ -10,5 +10,13 @@
 				}
 			};
 		});
+
+		frm.set_query('parent_quality_procedure', function(){
+			return {
+				filters: {
+					is_group: 1
+				}
+			};
+		});
 	}
 });
\ No newline at end of file
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
index b3c0d94..1ed921c 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
@@ -21,8 +21,7 @@
    "fieldname": "parent_quality_procedure",
    "fieldtype": "Link",
    "label": "Parent Procedure",
-   "options": "Quality Procedure",
-   "read_only": 1
+   "options": "Quality Procedure"
   },
   {
    "default": "0",
@@ -73,7 +72,7 @@
  ],
  "is_tree": 1,
  "links": [],
- "modified": "2020-06-17 17:25:03.434953",
+ "modified": "2020-10-13 11:46:07.744194",
  "modified_by": "Administrator",
  "module": "Quality Management",
  "name": "Quality Procedure",
diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
index 1952e57..797c26b 100644
--- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
+++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py
@@ -4,7 +4,7 @@
 
 from __future__ import unicode_literals
 import frappe
-from frappe.utils.nestedset import NestedSet
+from frappe.utils.nestedset import NestedSet, rebuild_tree
 from frappe import _
 
 class QualityProcedure(NestedSet):
@@ -42,6 +42,8 @@
 			doc.save(ignore_permissions=True)
 
 	def set_parent(self):
+		rebuild_tree('Quality Procedure', 'parent_quality_procedure')
+
 		for process in self.processes:
 			# Set parent for only those children who don't have a parent
 			parent_quality_procedure = frappe.db.get_value("Quality Procedure", process.procedure, "parent_quality_procedure")
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")
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index ecee97c..e2121fc 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -109,6 +109,10 @@
 					frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty);
 					frappe.model.set_value(cdt, cdn, "amount", r.message.rate * r.message.qty);
 					frappe.model.set_value(cdt, cdn, "current_serial_no", r.message.serial_nos);
+
+					if (frm.doc.purpose == "Stock Reconciliation") {
+						frappe.model.set_value(cdt, cdn, "serial_no", r.message.serial_nos);
+					}
 				}
 			});
 		}
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index b81f8a0..00b8f69 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -67,6 +67,8 @@
 
 				if item_dict.get("serial_nos"):
 					item.current_serial_no = item_dict.get("serial_nos")
+					if self.purpose == "Stock Reconciliation":
+						item.serial_no = item.current_serial_no
 
 				item.current_qty = item_dict.get("qty")
 				item.current_valuation_rate = item_dict.get("rate")
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 1571416..23d48d4 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -124,7 +124,7 @@
 		to_delete_records.append(sr.name)
 
 		sr = create_stock_reconciliation(item_code=serial_item_code,
-			warehouse = serial_warehouse, qty=5, rate=300, serial_no = '\n'.join(serial_nos))
+			warehouse = serial_warehouse, qty=5, rate=300)
 
 		serial_nos1 = get_serial_nos(sr.items[0].serial_no)
 		self.assertEqual(len(serial_nos1), 5)