Merge branch 'develop' into razorpay-subscription
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index fa4d40e..e1b331b 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -152,10 +152,9 @@
 				return [parent_account]
 			elif account_name == child:
 				parent_account_list = return_parent(data, parent_account)
-				if not parent_account_list:
+				if not parent_account_list and parent_account:
 					frappe.throw(_("The parent account {0} does not exists in the uploaded template").format(
 						frappe.bold(parent_account)))
-
 				return [child] + parent_account_list
 
 	charts_map, paths = {}, []
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index c5c5483..9292b63 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -174,7 +174,8 @@
 				read_only: 0,
 				fieldtype:'Date',
 				label: __('Release Date'),
-				default: me.frm.doc.release_date
+				default: me.frm.doc.release_date,
+				reqd: 1
 			},
 			{
 				fieldname: 'hold_comment',
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 3cd988c..3af236c 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -73,9 +73,9 @@
   "base_total",
   "base_net_total",
   "column_break_28",
+  "total_net_weight",
   "total",
   "net_total",
-  "total_net_weight",
   "taxes_section",
   "tax_category",
   "column_break_49",
@@ -1298,7 +1298,7 @@
  "idx": 204,
  "is_submittable": 1,
  "links": [],
- "modified": "2019-12-30 19:13:49.610538",
+ "modified": "2020-04-17 13:05:25.199832",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index e239f91..918fa14 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-05-24 19:29:05",
@@ -74,9 +75,9 @@
   "base_total",
   "base_net_total",
   "column_break_32",
+  "total_net_weight",
   "total",
   "net_total",
-  "total_net_weight",
   "taxes_section",
   "taxes_and_charges",
   "column_break_38",
@@ -1577,7 +1578,8 @@
  "icon": "fa fa-file-text",
  "idx": 181,
  "is_submittable": 1,
- "modified": "2020-02-10 04:57:11.221180",
+ "links": [],
+ "modified": "2020-04-17 12:38:41.435728",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 0e54b62..a2819af 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1926,16 +1926,6 @@
 		item.taxes = []
 		item.save()
 
-	def test_customer_provided_parts_si(self):
-		create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
-		si = create_sales_invoice(item_code='CUST-0987', rate=0)
-		self.assertEqual(si.get("items")[0].allow_zero_valuation_rate, 1)
-		self.assertEqual(si.get("items")[0].amount, 0)
-
-		# test if Sales Invoice with rate is allowed
-		si2 = create_sales_invoice(item_code='CUST-0987', do_not_save=True)
-		self.assertRaises(frappe.ValidationError, si2.save)
-
 def create_sales_invoice(**args):
 	si = frappe.new_doc("Sales Invoice")
 	args = frappe._dict(args)
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html
index 9a2205a..378fa37 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.html
+++ b/erpnext/accounts/report/general_ledger/general_ledger.html
@@ -2,7 +2,7 @@
 <h4 class="text-center">
 	{% if (filters.party_name) { %}
 		{%= filters.party_name %}
-	{% } else if (filters.party) { %}
+	{% } else if (filters.party && filters.party.length) { %}
 		{%= filters.party %}
 	{% } else if (filters.account) { %}
 		{%= filters.account %}
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 649b363..f776d93 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -365,6 +365,7 @@
 
 	columns = [
 		{
+			"label": _("GL Entry"),
 			"fieldname": "gl_entry",
 			"fieldtype": "Link",
 			"options": "GL Entry",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 4d83690..578858c 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "autoname": "naming_series:",
  "creation": "2013-05-21 16:16:39",
@@ -63,9 +64,9 @@
   "base_total",
   "base_net_total",
   "column_break_26",
+  "total_net_weight",
   "total",
   "net_total",
-  "total_net_weight",
   "taxes_section",
   "tax_category",
   "column_break_50",
@@ -170,8 +171,8 @@
    "search_index": 1
   },
   {
-   "description": "Fetch items based on Default Supplier.",
    "depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))",
+   "description": "Fetch items based on Default Supplier.",
    "fieldname": "get_items_from_open_material_requests",
    "fieldtype": "Button",
    "label": "Get Items from Open Material Requests"
@@ -1054,7 +1055,8 @@
  "icon": "fa fa-file-text",
  "idx": 105,
  "is_submittable": 1,
- "modified": "2020-01-14 18:54:39.694448",
+ "links": [],
+ "modified": "2020-04-17 13:04:28.185197",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order",
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 4037f2f..55a2c43 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -383,9 +383,6 @@
 			# Customer Provided parts will have zero valuation rate
 			if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
 				d.allow_zero_valuation_rate = 1
-				if d.parenttype in ["Delivery Note", "Sales Invoice"] and d.rate:
-					frappe.throw(_("Row #{0}: {1} cannot have {2} as it is a Customer Provided Item")
-						.format(d.idx, frappe.bold(d.item_code), frappe.bold("Rate")))
 
 def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
 		warehouse_account=None, company=None):
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
index 104ac57..d84c823 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js
@@ -2,15 +2,40 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Tally Migration', {
-	onload: function(frm) {
+	onload: function (frm) {
+		let reload_status = true;
 		frappe.realtime.on("tally_migration_progress_update", function (data) {
+			if (reload_status) {
+				frappe.model.with_doc(frm.doc.doctype, frm.doc.name, () => {
+					frm.refresh_header();
+				});
+				reload_status = false;
+			}
 			frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
-			if (data.count == data.total) {
-				window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title);
+			let error_occurred = data.count === -1;
+			if (data.count == data.total || error_occurred) {
+				window.setTimeout((title) => {
+					frm.dashboard.hide_progress(title);
+					frm.reload_doc();
+					if (error_occurred) {
+						frappe.msgprint({
+							message: __("An error has occurred during {0}. Check {1} for more details",
+								[
+									repl("<a href='#Form/Tally Migration/%(tally_document)s' class='variant-click'>%(tally_document)s</a>", {
+										tally_document: frm.docname
+									}),
+									"<a href='#List/Error Log' class='variant-click'>Error Log</a>"
+								]
+							),
+							title: __("Tally Migration Error"),
+							indicator: "red"
+						});
+					}
+				}, 2000, data.title);
 			}
 		});
 	},
-	refresh: function(frm) {
+	refresh: function (frm) {
 		if (frm.doc.master_data && !frm.doc.is_master_data_imported) {
 			if (frm.doc.is_master_data_processed) {
 				if (frm.doc.status != "Importing Master Data") {
@@ -34,17 +59,17 @@
 			}
 		}
 	},
-	add_button: function(frm, label, method) {
+	add_button: function (frm, label, method) {
 		frm.add_custom_button(
 			label,
-			() => frm.call({
-				doc: frm.doc,
-				method: method,
-				freeze: true,
-				callback: () => {
-					frm.remove_custom_button(label);
-				}
-			})
+			() => {
+				frm.call({
+					doc: frm.doc,
+					method: method,
+					freeze: true
+				});
+				frm.reload_doc();
+			}
 		);
 	}
 });
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
index 26415ca..dc6f093 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "beta": 1,
  "creation": "2019-02-01 14:27:09.485238",
  "doctype": "DocType",
@@ -14,6 +15,7 @@
   "tally_debtors_account",
   "company_section",
   "tally_company",
+  "default_uom",
   "column_break_8",
   "erpnext_company",
   "processed_files_section",
@@ -43,6 +45,7 @@
    "label": "Status"
   },
   {
+   "description": "Data exported from Tally that consists of the Chart of Accounts, Customers, Suppliers, Addresses, Items and UOMs",
    "fieldname": "master_data",
    "fieldtype": "Attach",
    "in_list_view": 1,
@@ -50,6 +53,7 @@
   },
   {
    "default": "Sundry Creditors",
+   "description": "Creditors Account set in Tally",
    "fieldname": "tally_creditors_account",
    "fieldtype": "Data",
    "label": "Tally Creditors Account",
@@ -61,6 +65,7 @@
   },
   {
    "default": "Sundry Debtors",
+   "description": "Debtors Account set in Tally",
    "fieldname": "tally_debtors_account",
    "fieldtype": "Data",
    "label": "Tally Debtors Account",
@@ -72,6 +77,7 @@
    "fieldtype": "Section Break"
   },
   {
+   "description": "Company Name as per Imported Tally Data",
    "fieldname": "tally_company",
    "fieldtype": "Data",
    "label": "Tally Company",
@@ -82,9 +88,11 @@
    "fieldtype": "Column Break"
   },
   {
+   "description": "Your Company set in ERPNext",
    "fieldname": "erpnext_company",
    "fieldtype": "Data",
-   "label": "ERPNext Company"
+   "label": "ERPNext Company",
+   "read_only_depends_on": "eval:doc.is_master_data_processed == 1"
   },
   {
    "fieldname": "processed_files_section",
@@ -155,24 +163,28 @@
    "options": "Cost Center"
   },
   {
+   "default": "0",
    "fieldname": "is_master_data_processed",
    "fieldtype": "Check",
    "label": "Is Master Data Processed",
    "read_only": 1
   },
   {
+   "default": "0",
    "fieldname": "is_day_book_data_processed",
    "fieldtype": "Check",
    "label": "Is Day Book Data Processed",
    "read_only": 1
   },
   {
+   "default": "0",
    "fieldname": "is_day_book_data_imported",
    "fieldtype": "Check",
    "label": "Is Day Book Data Imported",
    "read_only": 1
   },
   {
+   "default": "0",
    "fieldname": "is_master_data_imported",
    "fieldtype": "Check",
    "label": "Is Master Data Imported",
@@ -188,13 +200,23 @@
    "fieldtype": "Column Break"
   },
   {
+   "description": "Day Book Data exported from Tally that consists of all historic transactions",
    "fieldname": "day_book_data",
    "fieldtype": "Attach",
    "in_list_view": 1,
    "label": "Day Book Data"
+  },
+  {
+   "default": "Unit",
+   "description": "UOM in case unspecified in imported data",
+   "fieldname": "default_uom",
+   "fieldtype": "Link",
+   "label": "Default UOM",
+   "options": "UOM"
   }
  ],
- "modified": "2019-04-29 05:46:54.394967",
+ "links": [],
+ "modified": "2020-04-16 13:03:28.894919",
  "modified_by": "Administrator",
  "module": "ERPNext Integrations",
  "name": "Tally Migration",
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
index 01eee5b..13474e1 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
@@ -4,20 +4,23 @@
 
 from __future__ import unicode_literals
 
-from decimal import Decimal
 import json
 import re
 import traceback
 import zipfile
+from decimal import Decimal
+
+from bs4 import BeautifulSoup as bs
+
 import frappe
+from erpnext import encode_company_abbr
+from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
 from frappe import _
 from frappe.custom.doctype.custom_field.custom_field import create_custom_field
 from frappe.model.document import Document
 from frappe.model.naming import getseries, revert_series_if_last
 from frappe.utils.data import format_datetime
-from bs4 import BeautifulSoup as bs
-from erpnext import encode_company_abbr
-from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
+
 
 PRIMARY_ACCOUNT = "Primary"
 VOUCHER_CHUNK_SIZE = 500
@@ -39,13 +42,15 @@
 			return string
 
 		master_file = frappe.get_doc("File", {"file_url": data_file})
+		master_file_path = master_file.get_full_path()
 
-		with zipfile.ZipFile(master_file.get_full_path()) as zf:
-			encoded_content = zf.read(zf.namelist()[0])
-			try:
-				content = encoded_content.decode("utf-8-sig")
-			except UnicodeDecodeError:
-				content = encoded_content.decode("utf-16")
+		if zipfile.is_zipfile(master_file_path):
+			with zipfile.ZipFile(master_file_path) as zf:
+				encoded_content = zf.read(zf.namelist()[0])
+				try:
+					content = encoded_content.decode("utf-8-sig")
+				except UnicodeDecodeError:
+					content = encoded_content.decode("utf-16")
 
 		master = bs(sanitize(emptify(content)), "xml")
 		collection = master.BODY.IMPORTDATA.REQUESTDATA
@@ -58,13 +63,14 @@
 				"file_name":  key + ".json",
 				"attached_to_doctype": self.doctype,
 				"attached_to_name": self.name,
-				"content": json.dumps(value)
+				"content": json.dumps(value),
+				"is_private": True
 			}).insert()
 			setattr(self, key, f.file_url)
 
 	def _process_master_data(self):
 		def get_company_name(collection):
-			return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string
+			return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string.strip()
 
 		def get_coa_customers_suppliers(collection):
 			root_type_map = {
@@ -97,17 +103,17 @@
 				# If Ledger doesn't have PARENT field then don't create Account
 				# For example "Profit & Loss A/c"
 				if account.PARENT:
-					yield account.PARENT.string, account["NAME"], 0
+					yield account.PARENT.string.strip(), account["NAME"], 0
 
 		def get_parent(account):
 			if account.PARENT:
-				return account.PARENT.string
+				return account.PARENT.string.strip()
 			return {
 				("Yes", "No"): "Application of Funds (Assets)",
 				("Yes", "Yes"): "Expenses",
 				("No", "Yes"): "Income",
 				("No", "No"): "Source of Funds (Liabilities)",
-			}[(account.ISDEEMEDPOSITIVE.string, account.ISREVENUE.string)]
+			}[(account.ISDEEMEDPOSITIVE.string.strip(), account.ISREVENUE.string.strip())]
 
 		def get_children_and_parent_dict(accounts):
 			children, parents = {}, {}
@@ -145,38 +151,38 @@
 			parties, addresses = [], []
 			for account in collection.find_all("LEDGER"):
 				party_type = None
-				if account.NAME.string in customers:
+				if account.NAME.string.strip() in customers:
 					party_type = "Customer"
 					parties.append({
 						"doctype": party_type,
-						"customer_name": account.NAME.string,
-						"tax_id": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None,
+						"customer_name": account.NAME.string.strip(),
+						"tax_id": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None,
 						"customer_group": "All Customer Groups",
 						"territory": "All Territories",
 						"customer_type": "Individual",
 					})
-				elif account.NAME.string in suppliers:
+				elif account.NAME.string.strip() in suppliers:
 					party_type = "Supplier"
 					parties.append({
 						"doctype": party_type,
-						"supplier_name": account.NAME.string,
-						"pan": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None,
+						"supplier_name": account.NAME.string.strip(),
+						"pan": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None,
 						"supplier_group": "All Supplier Groups",
 						"supplier_type": "Individual",
 					})
 				if party_type:
-					address = "\n".join([a.string for a in account.find_all("ADDRESS")])
+					address = "\n".join([a.string.strip() for a in account.find_all("ADDRESS")])
 					addresses.append({
 						"doctype": "Address",
 						"address_line1": address[:140].strip(),
 						"address_line2": address[140:].strip(),
-						"country": account.COUNTRYNAME.string if account.COUNTRYNAME else None,
-						"state": account.LEDSTATENAME.string if account.LEDSTATENAME else None,
-						"gst_state": account.LEDSTATENAME.string if account.LEDSTATENAME else None,
-						"pin_code": account.PINCODE.string if account.PINCODE else None,
-						"mobile": account.LEDGERPHONE.string if account.LEDGERPHONE else None,
-						"phone": account.LEDGERPHONE.string if account.LEDGERPHONE else None,
-						"gstin": account.PARTYGSTIN.string if account.PARTYGSTIN else None,
+						"country": account.COUNTRYNAME.string.strip() if account.COUNTRYNAME else None,
+						"state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None,
+						"gst_state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None,
+						"pin_code": account.PINCODE.string.strip() if account.PINCODE else None,
+						"mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
+						"phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
+						"gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None,
 						"links": [{"link_doctype": party_type, "link_name": account["NAME"]}],
 					})
 			return parties, addresses
@@ -184,41 +190,50 @@
 		def get_stock_items_uoms(collection):
 			uoms = []
 			for uom in collection.find_all("UNIT"):
-				uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string})
+				uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string.strip()})
 
 			items = []
 			for item in collection.find_all("STOCKITEM"):
+				stock_uom = item.BASEUNITS.string.strip() if item.BASEUNITS else self.default_uom
 				items.append({
 					"doctype": "Item",
-					"item_code" : item.NAME.string,
-					"stock_uom": item.BASEUNITS.string,
+					"item_code" : item.NAME.string.strip(),
+					"stock_uom": stock_uom.strip(),
 					"is_stock_item": 0,
 					"item_group": "All Item Groups",
 					"item_defaults": [{"company": self.erpnext_company}]
 				})
+
 			return items, uoms
 
+		try:
+			self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5)
+			collection = self.get_collection(self.master_data)
+			company = get_company_name(collection)
+			self.tally_company = company
+			self.erpnext_company = company
 
-		self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5)
-		collection = self.get_collection(self.master_data)
+			self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5)
+			chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection)
 
-		company = get_company_name(collection)
-		self.tally_company = company
-		self.erpnext_company = company
+			self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5)
+			parties, addresses = get_parties_addresses(collection, customers, suppliers)
 
-		self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5)
-		chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection)
-		self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5)
-		parties, addresses = get_parties_addresses(collection, customers, suppliers)
-		self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5)
-		items, uoms = get_stock_items_uoms(collection)
-		data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms}
-		self.publish("Process Master Data", _("Done"), 5, 5)
+			self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5)
+			items, uoms = get_stock_items_uoms(collection)
+			data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms}
 
-		self.dump_processed_data(data)
-		self.is_master_data_processed = 1
-		self.status = ""
-		self.save()
+			self.publish("Process Master Data", _("Done"), 5, 5)
+			self.dump_processed_data(data)
+
+			self.is_master_data_processed = 1
+
+		except:
+			self.publish("Process Master Data", _("Process Failed"), -1, 5)
+			self.log()
+
+		finally:
+			self.set_status()
 
 	def publish(self, title, message, count, total):
 		frappe.publish_realtime("tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total})
@@ -256,7 +271,6 @@
 					except:
 						self.log(address)
 
-
 		def create_items_uoms(items_file_url, uoms_file_url):
 			uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url})
 			for uom in json.loads(uoms_file.get_content()):
@@ -273,25 +287,35 @@
 				except:
 					self.log(item)
 
-		self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
-		create_company_and_coa(self.chart_of_accounts)
-		self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4)
-		create_parties_and_addresses(self.parties, self.addresses)
-		self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4)
-		create_items_uoms(self.items, self.uoms)
-		self.publish("Import Master Data", _("Done"), 4, 4)
-		self.status = ""
-		self.is_master_data_imported = 1
-		self.save()
+		try:
+			self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
+			create_company_and_coa(self.chart_of_accounts)
+
+			self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4)
+			create_parties_and_addresses(self.parties, self.addresses)
+
+			self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4)
+			create_items_uoms(self.items, self.uoms)
+
+			self.publish("Import Master Data", _("Done"), 4, 4)
+
+			self.is_master_data_imported = 1
+
+		except:
+			self.publish("Import Master Data", _("Process Failed"), -1, 5)
+			self.log()
+
+		finally:
+			self.set_status()
 
 	def _process_day_book_data(self):
 		def get_vouchers(collection):
 			vouchers = []
 			for voucher in collection.find_all("VOUCHER"):
-				if voucher.ISCANCELLED.string == "Yes":
+				if voucher.ISCANCELLED.string.strip() == "Yes":
 					continue
 				inventory_entries = voucher.find_all("INVENTORYENTRIES.LIST") + voucher.find_all("ALLINVENTORYENTRIES.LIST") + voucher.find_all("INVENTORYENTRIESIN.LIST") + voucher.find_all("INVENTORYENTRIESOUT.LIST")
-				if voucher.VOUCHERTYPENAME.string not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries:
+				if voucher.VOUCHERTYPENAME.string.strip() not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries:
 					function = voucher_to_invoice
 				else:
 					function = voucher_to_journal_entry
@@ -307,15 +331,15 @@
 			accounts = []
 			ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
 			for entry in ledger_entries:
-				account = {"account": encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company), "cost_center": self.default_cost_center}
-				if entry.ISPARTYLEDGER.string == "Yes":
-					party_details = get_party(entry.LEDGERNAME.string)
+				account = {"account": encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company), "cost_center": self.default_cost_center}
+				if entry.ISPARTYLEDGER.string.strip() == "Yes":
+					party_details = get_party(entry.LEDGERNAME.string.strip())
 					if party_details:
 						party_type, party_account = party_details
 						account["party_type"] = party_type
 						account["account"] = party_account
-						account["party"] = entry.LEDGERNAME.string
-				amount = Decimal(entry.AMOUNT.string)
+						account["party"] = entry.LEDGERNAME.string.strip()
+				amount = Decimal(entry.AMOUNT.string.strip())
 				if amount > 0:
 					account["credit_in_account_currency"] = str(abs(amount))
 				else:
@@ -324,21 +348,21 @@
 
 			journal_entry = {
 				"doctype": "Journal Entry",
-				"tally_guid": voucher.GUID.string,
-				"posting_date": voucher.DATE.string,
+				"tally_guid": voucher.GUID.string.strip(),
+				"posting_date": voucher.DATE.string.strip(),
 				"company": self.erpnext_company,
 				"accounts": accounts,
 			}
 			return journal_entry
 
 		def voucher_to_invoice(voucher):
-			if voucher.VOUCHERTYPENAME.string in ["Sales", "Credit Note"]:
+			if voucher.VOUCHERTYPENAME.string.strip() in ["Sales", "Credit Note"]:
 				doctype = "Sales Invoice"
 				party_field = "customer"
 				account_field = "debit_to"
 				account_name = encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
 				price_list_field = "selling_price_list"
-			elif voucher.VOUCHERTYPENAME.string in ["Purchase", "Debit Note"]:
+			elif voucher.VOUCHERTYPENAME.string.strip() in ["Purchase", "Debit Note"]:
 				doctype = "Purchase Invoice"
 				party_field = "supplier"
 				account_field = "credit_to"
@@ -351,10 +375,10 @@
 
 			invoice = {
 				"doctype": doctype,
-				party_field: voucher.PARTYNAME.string,
-				"tally_guid": voucher.GUID.string,
-				"posting_date": voucher.DATE.string,
-				"due_date": voucher.DATE.string,
+				party_field: voucher.PARTYNAME.string.strip(),
+				"tally_guid": voucher.GUID.string.strip(),
+				"posting_date": voucher.DATE.string.strip(),
+				"due_date": voucher.DATE.string.strip(),
 				"items": get_voucher_items(voucher, doctype),
 				"taxes": get_voucher_taxes(voucher),
 				account_field: account_name,
@@ -375,15 +399,15 @@
 			for entry in inventory_entries:
 				qty, uom = entry.ACTUALQTY.string.strip().split()
 				items.append({
-					"item_code": entry.STOCKITEMNAME.string,
-					"description": entry.STOCKITEMNAME.string,
+					"item_code": entry.STOCKITEMNAME.string.strip(),
+					"description": entry.STOCKITEMNAME.string.strip(),
 					"qty": qty.strip(),
 					"uom": uom.strip(),
 					"conversion_factor": 1,
-					"price_list_rate": entry.RATE.string.split("/")[0],
+					"price_list_rate": entry.RATE.string.strip().split("/")[0],
 					"cost_center": self.default_cost_center,
 					"warehouse": self.default_warehouse,
-					account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string, self.erpnext_company),
+					account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string.strip(), self.erpnext_company),
 				})
 			return items
 
@@ -391,13 +415,13 @@
 			ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
 			taxes = []
 			for entry in ledger_entries:
-				if entry.ISPARTYLEDGER.string == "No":
-					tax_account = encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company)
+				if entry.ISPARTYLEDGER.string.strip() == "No":
+					tax_account = encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company)
 					taxes.append({
 						"charge_type": "Actual",
 						"account_head": tax_account,
 						"description": tax_account,
-						"tax_amount": entry.AMOUNT.string,
+						"tax_amount": entry.AMOUNT.string.strip(),
 						"cost_center": self.default_cost_center,
 					})
 			return taxes
@@ -408,15 +432,24 @@
 			elif frappe.db.exists({"doctype": "Customer", "customer_name": party}):
 				return "Customer", encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
 
-		self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3)
-		collection = self.get_collection(self.day_book_data)
-		self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3)
-		vouchers = get_vouchers(collection)
-		self.publish("Process Day Book Data", _("Done"), 3, 3)
-		self.dump_processed_data({"vouchers": vouchers})
-		self.status = ""
-		self.is_day_book_data_processed = 1
-		self.save()
+		try:
+			self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3)
+			collection = self.get_collection(self.day_book_data)
+
+			self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3)
+			vouchers = get_vouchers(collection)
+
+			self.publish("Process Day Book Data", _("Done"), 3, 3)
+			self.dump_processed_data({"vouchers": vouchers})
+
+			self.is_day_book_data_processed = 1
+
+		except:
+			self.publish("Process Day Book Data", _("Process Failed"), -1, 5)
+			self.log()
+
+		finally:
+			self.set_status()
 
 	def _import_day_book_data(self):
 		def create_fiscal_years(vouchers):
@@ -454,23 +487,31 @@
 				"currency": "INR"
 			}).insert()
 
-		frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
-		frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
-		frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
+		try:
+			frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
+			frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
+			frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
 
-		vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
-		vouchers = json.loads(vouchers_file.get_content())
+			vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
+			vouchers = json.loads(vouchers_file.get_content())
 
-		create_fiscal_years(vouchers)
-		create_price_list()
-		create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
+			create_fiscal_years(vouchers)
+			create_price_list()
+			create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
 
-		total = len(vouchers)
-		is_last = False
-		for index in range(0, total, VOUCHER_CHUNK_SIZE):
-			if index + VOUCHER_CHUNK_SIZE >= total:
-				is_last = True
-			frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last)
+			total = len(vouchers)
+			is_last = False
+
+			for index in range(0, total, VOUCHER_CHUNK_SIZE):
+				if index + VOUCHER_CHUNK_SIZE >= total:
+					is_last = True
+				frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last)
+
+		except:
+			self.log()
+
+		finally:
+			self.set_status()
 
 	def _import_vouchers(self, start, total, is_last=False):
 		frappe.flags.in_migrate = True
@@ -494,25 +535,26 @@
 		frappe.flags.in_migrate = False
 
 	def process_master_data(self):
-		self.status = "Processing Master Data"
-		self.save()
+		self.set_status("Processing Master Data")
 		frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600)
 
 	def import_master_data(self):
-		self.status = "Importing Master Data"
-		self.save()
+		self.set_status("Importing Master Data")
 		frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600)
 
 	def process_day_book_data(self):
-		self.status = "Processing Day Book Data"
-		self.save()
+		self.set_status("Processing Day Book Data")
 		frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600)
 
 	def import_day_book_data(self):
-		self.status = "Importing Day Book Data"
-		self.save()
+		self.set_status("Importing Day Book Data")
 		frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
 
 	def log(self, data=None):
-		message = "\n".join(["Data", json.dumps(data, default=str, indent=4), "Exception", traceback.format_exc()])
+		data = data or self.status
+		message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
 		return frappe.log_error(title="Tally Migration Error", message=message)
+
+	def set_status(self, status=""):
+		self.status = status
+		self.save()
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index b3c803b..223c4e3 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -776,22 +776,16 @@
 
 		for payment in self.get('loans'):
 			amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
+			total_amount = amounts['interest_amount'] + amounts['payable_principal_amount']
+			if payment.total_payment > total_amount:
+				frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2}
+					against loan {3}""").format(payment.idx, frappe.bold(payment.total_payment),
+					frappe.bold(total_amount), frappe.bold(payment.loan)))
 
-			if payment.interest_amount > amounts['interest_amount']:
-				frappe.throw(_("""Row {0}: Paid Interest amount {1} is greater than pending interest amount {2}
-					against loan {3}""").format(payment.idx, frappe.bold(payment.interest_amount),
-					frappe.bold(amounts['interest_amount']), frappe.bold(payment.loan)))
-
-			if payment.principal_amount > amounts['payable_principal_amount']:
-				frappe.throw(_("""Row {0}: Paid Principal amount {1} is greater than pending principal amount {2}
-					against loan {3}""").format(payment.idx, frappe.bold(payment.principal_amount),
-					frappe.bold(amounts['payable_principal_amount']), frappe.bold(payment.loan)))
-
-			payment.total_payment = payment.interest_amount + payment.principal_amount
 			self.total_interest_amount += payment.interest_amount
 			self.total_principal_amount += payment.principal_amount
 
-		self.total_loan_repayment = self.total_interest_amount + self.total_principal_amount
+			self.total_loan_repayment += payment.total_payment
 
 	def get_loan_details(self):
 
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 108672b..2d1ad33 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -149,13 +149,19 @@
 
 		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68)
 		repayment_entry.save()
+		repayment_entry.submit()
 
 		penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year))
-
-		self.assertEquals(flt(repayment_entry.interest_payable, 2), flt(accrued_interest_amount, 2))
 		self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2))
 
-		repayment_entry.submit()
+		amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
+			'paid_principal_amount'])
+
+		loan.load_from_db()
+
+		self.assertEquals(amounts[0], repayment_entry.interest_payable)
+		self.assertEquals(flt(loan.total_principal_paid, 2), flt(repayment_entry.amount_paid -
+			 penalty_amount - amounts[0], 2))
 
 	def test_loan_closure_repayment(self):
 		pledges = []
@@ -189,15 +195,19 @@
 		process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
 
 		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
-			"Loan Closure", 13315.0681)
-		repayment_entry.save()
+			"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
+		repayment_entry.submit()
 
-		repayment_entry.amount_paid = repayment_entry.payable_amount
+		amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
+			'paid_principal_amount'])
 
-		self.assertEquals(flt(repayment_entry.interest_payable, 3), flt(accrued_interest_amount, 3))
+		unaccrued_interest_amount =  (loan.loan_amount * loan.rate_of_interest * 6) \
+			/ (days_in_year(get_datetime(first_date).year) * 100)
+
+		self.assertEquals(flt(amounts[0] + unaccrued_interest_amount, 3),
+			flt(accrued_interest_amount, 3))
 		self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
 
-		repayment_entry.submit()
 		loan.load_from_db()
 		self.assertEquals(loan.status, "Loan Closure Requested")
 
@@ -227,57 +237,15 @@
 		process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
 
 		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(get_last_day(nowdate()), 5),
-			"Regular Payment", 89768.7534247)
+			"Regular Payment", 89768.75)
 
-		repayment_entry.save()
 		repayment_entry.submit()
 
-		repayment_entry.load_from_db()
+		amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
+			'paid_principal_amount'])
 
-		self.assertEquals(repayment_entry.interest_payable, 11250.00)
-		self.assertEquals(repayment_entry.payable_principal_amount, 78303.00)
-
-	def test_partial_loan_repayment(self):
-		pledges = []
-		pledges.append({
-			"loan_security": "Test Security 1",
-			"qty": 4000.00,
-			"haircut": 50
-		})
-
-		loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
-
-		loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name,
-			posting_date=get_first_day(nowdate()))
-
-		loan.submit()
-
-		self.assertEquals(loan.loan_amount, 1000000)
-
-		first_date = '2019-10-01'
-		last_date = '2019-10-30'
-
-		no_of_days = date_diff(last_date, first_date) + 1
-
-		accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
-			/ (days_in_year(get_datetime().year) * 100)
-
-		make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
-
-		process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 15))
-		process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 30))
-
-		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 1), "Regular Payment", 6500)
-		repayment_entry.save()
-		repayment_entry.submit()
-
-		penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year))
-
-		lia1 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 1}, 'name')
-		lia2 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 0}, 'name')
-
-		self.assertTrue(lia1)
-		self.assertTrue(lia2)
+		self.assertEquals(amounts[0], 11250.00)
+		self.assertEquals(amounts[1], 78303.00)
 
 	def test_security_shortfall(self):
 		pledges = []
@@ -294,7 +262,7 @@
 
 		make_loan_disbursement_entry(loan.name, loan.loan_amount)
 
-		frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 100
+		frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
 			where loan_security='Test Security 2'""")
 
 		create_process_loan_security_shortfall()
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json
index a261120..5fc3e8f 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json
@@ -15,12 +15,13 @@
   "company",
   "posting_date",
   "is_term_loan",
-  "is_paid",
   "section_break_7",
   "pending_principal_amount",
   "payable_principal_amount",
+  "paid_principal_amount",
   "column_break_14",
   "interest_amount",
+  "paid_interest_amount",
   "section_break_15",
   "process_loan_interest_accrual",
   "repayment_schedule_name",
@@ -103,13 +104,6 @@
   },
   {
    "default": "0",
-   "fieldname": "is_paid",
-   "fieldtype": "Check",
-   "label": "Is Paid",
-   "read_only": 1
-  },
-  {
-   "default": "0",
    "fetch_from": "loan.is_term_loan",
    "fieldname": "is_term_loan",
    "fieldtype": "Check",
@@ -143,12 +137,24 @@
    "hidden": 1,
    "label": "Repayment Schedule Name",
    "read_only": 1
+  },
+  {
+   "fieldname": "paid_principal_amount",
+   "fieldtype": "Currency",
+   "label": "Paid Principal Amount",
+   "options": "Company:company:default_currency"
+  },
+  {
+   "fieldname": "paid_interest_amount",
+   "fieldtype": "Currency",
+   "label": "Paid Interest Amount",
+   "options": "Company:company:default_currency"
   }
  ],
  "in_create": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-04-10 18:31:02.369857",
+ "modified": "2020-04-16 11:24:23.258404",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Interest Accrual",
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 4b930c5..789c129 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -30,9 +30,8 @@
   "reference_number",
   "column_break_21",
   "reference_date",
-  "paid_accrual_entries",
-  "partial_paid_entry",
   "principal_amount_paid",
+  "repayment_details",
   "amended_from"
  ],
  "fields": [
@@ -156,13 +155,6 @@
    "read_only": 1
   },
   {
-   "fieldname": "paid_accrual_entries",
-   "fieldtype": "Text",
-   "hidden": 1,
-   "label": "Paid Accrual Entries",
-   "read_only": 1
-  },
-  {
    "default": "0",
    "fetch_from": "against_loan.is_term_loan",
    "fieldname": "is_term_loan",
@@ -198,13 +190,6 @@
    "fieldtype": "Column Break"
   },
   {
-   "fieldname": "partial_paid_entry",
-   "fieldtype": "Text",
-   "hidden": 1,
-   "label": "Partial Paid Entry",
-   "read_only": 1
-  },
-  {
    "default": "0.0",
    "fieldname": "principal_amount_paid",
    "fieldtype": "Currency",
@@ -225,11 +210,18 @@
    "fieldtype": "Date",
    "label": "Due Date",
    "read_only": 1
+  },
+  {
+   "fieldname": "repayment_details",
+   "fieldtype": "Table",
+   "hidden": 1,
+   "label": "Repayment Details",
+   "options": "Loan Repayment Detail"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-02-26 06:18:54.934538",
+ "modified": "2020-04-16 18:14:45.166754",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Repayment",
@@ -264,7 +256,6 @@
    "write": 1
   }
  ],
- "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 2d2ca4c..87e8a15 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -19,11 +19,11 @@
 	def validate(self):
 		amounts = calculate_amounts(self.against_loan, self.posting_date, self.payment_type)
 		self.set_missing_values(amounts)
-
-	def before_submit(self):
-		self.mark_as_paid()
+		self.validate_amount()
+		self.allocate_amounts(amounts['pending_accrual_entries'])
 
 	def on_submit(self):
+		self.update_paid_amount()
 		self.make_gl_entries()
 
 	def on_cancel(self):
@@ -38,32 +38,25 @@
 			self.cost_center = erpnext.get_default_cost_center(self.company)
 
 		if not self.interest_payable:
-			self.interest_payable = amounts['interest_amount']
+			self.interest_payable = flt(amounts['interest_amount'], 2)
 
 		if not self.penalty_amount:
-			self.penalty_amount = amounts['penalty_amount']
+			self.penalty_amount = flt(amounts['penalty_amount'], 2)
 
 		if not self.pending_principal_amount:
-			self.pending_principal_amount = amounts['pending_principal_amount']
+			self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2)
 
 		if not self.payable_principal_amount and self.is_term_loan:
-			self.payable_principal_amount = amounts['payable_principal_amount']
+			self.payable_principal_amount = flt(amounts['payable_principal_amount'], 2)
 
 		if not self.payable_amount:
-			self.payable_amount = amounts['payable_amount']
-
-		if amounts.get('paid_accrual_entries'):
-			self.paid_accrual_entries = frappe.as_json(amounts.get('paid_accrual_entries'))
+			self.payable_amount = flt(amounts['payable_amount'], 2)
 
 		if amounts.get('due_date'):
 			self.due_date = amounts.get('due_date')
 
-	def mark_as_paid(self):
-		paid_entries = []
-		paid_amount = self.amount_paid
-		interest_paid = paid_amount
-
-		if not paid_amount:
+	def validate_amount(self):
+		if not self.amount_paid:
 			frappe.throw(_("Amount paid cannot be zero"))
 
 		if self.amount_paid < self.penalty_amount:
@@ -74,37 +67,15 @@
 			msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount)
 			frappe.throw(msg)
 
+	def update_paid_amount(self):
 		loan = frappe.get_doc("Loan", self.against_loan)
 
-		if self.paid_accrual_entries:
-			paid_accrual_entries = json.loads(self.paid_accrual_entries)
-
-		if paid_amount - self.penalty_amount > 0 and self.paid_accrual_entries:
-
-			interest_paid = paid_amount - self.penalty_amount
-
-			for lia, interest_amount in iteritems(paid_accrual_entries):
-				if interest_amount <= interest_paid:
-					paid_entries.append(lia)
-					interest_paid -= interest_amount
-				elif interest_paid:
-					self.partial_paid_entry = frappe.as_json({"name": lia, "interest_amount": interest_amount})
-					frappe.db.set_value("Loan Interest Accrual", lia, "interest_amount",
-						interest_amount - interest_paid)
-					interest_paid = 0
-
-		if paid_entries:
-			self.paid_accrual_entries = frappe.as_json(paid_entries)
-		else:
-			self.paid_accrual_entries = ""
-
-		if interest_paid:
-			self.principal_amount_paid = interest_paid
-
-		if paid_entries:
-			frappe.db.sql("""UPDATE `tabLoan Interest Accrual`
-				SET is_paid = 1 where name in (%s)""" #nosec
-				% ", ".join(['%s']*len(paid_entries)), tuple(paid_entries))
+		for payment in self.repayment_details:
+			frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
+				SET paid_principal_amount = `paid_principal_amount` + %s,
+					paid_interest_amount = `paid_interest_amount` + %s
+				WHERE name = %s""",
+				(flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual))
 
 		if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2):
 			frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested")
@@ -116,21 +87,14 @@
 		update_shortfall_status(self.against_loan, self.principal_amount_paid)
 
 	def mark_as_unpaid(self):
-
 		loan = frappe.get_doc("Loan", self.against_loan)
 
-		if self.paid_accrual_entries:
-			paid_accrual_entries = json.loads(self.paid_accrual_entries)
-
-		if self.paid_accrual_entries:
-			frappe.db.sql("""UPDATE `tabLoan Interest Accrual`
-				SET is_paid = 0 where name in (%s)""" #nosec
-				% ", ".join(['%s']*len(paid_accrual_entries)), tuple(paid_accrual_entries))
-
-		if self.partial_paid_entry:
-			partial_paid_entry = json.loads(self.partial_paid_entry)
-			frappe.db.set_value("Loan Interest Accrual", partial_paid_entry["name"], "interest_amount",
-				partial_paid_entry["interest_amount"])
+		for payment in self.repayment_details:
+			frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
+				SET paid_principal_amount = `paid_principal_amount` - %s,
+					paid_interest_amount = `paid_interest_amount` - %s
+				WHERE name = %s""",
+				(payment.paid_principal_amount, payment.paid_interest_amount, payment.loan_interest_accrual))
 
 		frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s
 			WHERE name = %s """, (loan.total_amount_paid - self.amount_paid,
@@ -139,6 +103,38 @@
 		if loan.status == "Loan Closure Requested":
 			frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed")
 
+	def allocate_amounts(self, paid_entries):
+		self.set('repayment_details', [])
+		self.principal_amount_paid = 0
+
+		if self.amount_paid - self.penalty_amount > 0 and paid_entries:
+			interest_paid = self.amount_paid - self.penalty_amount
+			for lia, amounts in iteritems(paid_entries):
+				if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid:
+					interest_amount = amounts['interest_amount']
+					paid_principal = amounts['payable_principal_amount']
+					self.principal_amount_paid += paid_principal
+					interest_paid -= (interest_amount + paid_principal)
+				elif interest_paid:
+					if interest_paid >= amounts['interest_amount']:
+						interest_amount = amounts['interest_amount']
+						paid_principal = interest_paid - interest_amount
+						self.principal_amount_paid += paid_principal
+						interest_paid = 0
+					else:
+						interest_amount = interest_paid
+						interest_paid = 0
+						paid_principal=0
+
+				self.append('repayment_details', {
+					'loan_interest_accrual': lia,
+					'paid_interest_amount': interest_amount,
+					'paid_principal_amount': paid_principal
+				})
+
+		if interest_paid:
+			self.principal_amount_paid += interest_paid
+
 	def make_gl_entries(self, cancel=0, adv_adj=0):
 		gle_map = []
 		loan_details = frappe.get_doc("Loan", self.against_loan)
@@ -223,7 +219,7 @@
 		"posting_date": posting_date,
 		"applicant": applicant,
 		"penalty_amount": penalty_amount,
-		"interst_payable": interest_payable,
+		"interest_payable": interest_payable,
 		"payable_principal_amount": payable_principal_amount,
 		"amount_paid": amount_paid,
 		"loan_type": loan_type
@@ -232,15 +228,22 @@
 	return lr
 
 def get_accrued_interest_entries(against_loan):
-	accrued_interest_entries = frappe.get_all("Loan Interest Accrual",
-		fields=["name", "interest_amount", "posting_date", "payable_principal_amount"],
-		filters = {
-			"loan": against_loan,
-			"is_paid": 0,
-			"docstatus": 1
-		}, order_by="posting_date")
 
-	return accrued_interest_entries
+	unpaid_accrued_entries = frappe.db.sql(
+		"""
+			SELECT name, posting_date, interest_amount - paid_interest_amount as interest_amount,
+				payable_principal_amount - paid_principal_amount as payable_principal_amount
+			FROM
+				`tabLoan Interest Accrual`
+			WHERE
+				loan = %s
+			AND (interest_amount - paid_interest_amount > 0 OR
+				payable_principal_amount - paid_principal_amount > 0)
+			AND
+				docstatus = 1
+		""", (against_loan), as_dict=1)
+
+	return unpaid_accrued_entries
 
 # This function returns the amounts that are payable at the time of loan repayment based on posting date
 # So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
@@ -273,8 +276,10 @@
 		total_pending_interest += entry.interest_amount
 		payable_principal_amount += entry.payable_principal_amount
 
-		pending_accrual_entries.setdefault(entry.name,
-			flt(entry.interest_amount) + flt(entry.payable_principal_amount))
+		pending_accrual_entries.setdefault(entry.name, {
+			'interest_amount': flt(entry.interest_amount),
+			'payable_principal_amount': flt(entry.payable_principal_amount)
+		})
 
 		final_due_date = due_date
 
@@ -291,7 +296,7 @@
 	amounts["interest_amount"] = total_pending_interest
 	amounts["penalty_amount"] = penalty_amount
 	amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount
-	amounts["paid_accrual_entries"] = pending_accrual_entries
+	amounts["pending_accrual_entries"] = pending_accrual_entries
 
 	if final_due_date:
 		amounts["due_date"] = final_due_date
diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/__init__.py b/erpnext/loan_management/doctype/loan_repayment_detail/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/loan_management/doctype/loan_repayment_detail/__init__.py
diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json
new file mode 100644
index 0000000..cff1dbb
--- /dev/null
+++ b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json
@@ -0,0 +1,43 @@
+{
+ "actions": [],
+ "creation": "2020-04-15 18:31:54.026923",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "loan_interest_accrual",
+  "paid_principal_amount",
+  "paid_interest_amount"
+ ],
+ "fields": [
+  {
+   "fieldname": "loan_interest_accrual",
+   "fieldtype": "Link",
+   "label": "Loan Interest Accrual",
+   "options": "Loan Interest Accrual"
+  },
+  {
+   "fieldname": "paid_principal_amount",
+   "fieldtype": "Currency",
+   "label": "Paid Principal Amount",
+   "options": "Company:company:default_currency"
+  },
+  {
+   "fieldname": "paid_interest_amount",
+   "fieldtype": "Currency",
+   "label": "Paid Interest Amount",
+   "options": "Company:company:default_currency"
+  }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-04-15 21:50:03.837019",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Repayment Detail",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py
new file mode 100644
index 0000000..a83b9b5
--- /dev/null
+++ b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class LoanRepaymentDetail(Document):
+	pass
diff --git a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
index f7e2116..2f4fe24 100644
--- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
+++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
@@ -28,7 +28,6 @@
   {
    "fieldname": "loan_account",
    "fieldtype": "Link",
-   "in_list_view": 1,
    "label": "Loan Account",
    "options": "Account",
    "read_only": 1
@@ -50,21 +49,23 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Principal Amount",
-   "options": "Company:company:default_currency"
+   "options": "Company:company:default_currency",
+   "read_only": 1
   },
   {
    "fieldname": "interest_amount",
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Interest Amount",
-   "options": "Company:company:default_currency"
+   "options": "Company:company:default_currency",
+   "read_only": 1
   },
   {
    "fieldname": "total_payment",
    "fieldtype": "Currency",
+   "in_list_view": 1,
    "label": "Total Payment",
-   "options": "Company:company:default_currency",
-   "read_only": 1
+   "options": "Company:company:default_currency"
   },
   {
    "fieldname": "loan_repayment_entry",
@@ -84,7 +85,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-04-09 20:01:53.546364",
+ "modified": "2020-04-16 13:17:04.798335",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Salary Slip Loan",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index 8c7876d..bab0dfb 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -82,7 +82,9 @@
 			frm.set_value('current_time' , 0);
 		}
 
-		frm.save();
+		frm.save("Save", () => {}, "", () => {
+			frm.doc.time_logs.pop(-1);
+		});
 	},
 
 	complete_job: function(frm, completed_time, completed_qty) {
@@ -105,6 +107,24 @@
 		});
 	},
 
+	validate: function(frm) {
+		if ((!frm.doc.time_logs || !frm.doc.time_logs.length) && frm.doc.started_time) {
+			frm.trigger("reset_timer");
+		}
+	},
+
+	employee: function(frm) {
+		if (frm.doc.job_started && !frm.doc.current_time) {
+			frm.trigger("reset_timer");
+		}
+	},
+
+	reset_timer: function(frm) {
+		frm.set_value('started_time' , '');
+		frm.set_value('job_started', 0);
+		frm.set_value('current_time' , 0);
+	},
+
 	make_dashboard: function(frm) {
 		if(frm.doc.__islocal)
 			return;
@@ -137,12 +157,12 @@
 					updateStopwatch(current);
 				}, 1000);
 			}
-	
+
 			function updateStopwatch(increment) {
 				var hours = Math.floor(increment / 3600);
 				var minutes = Math.floor((increment - (hours * 3600)) / 60);
 				var seconds = increment - (hours * 3600) - (minutes * 60);
-	
+
 				$(section).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString());
 				$(section).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString());
 				$(section).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString());
@@ -205,5 +225,10 @@
 frappe.ui.form.on('Job Card Time Log', {
 	completed_qty: function(frm) {
 		frm.events.set_total_completed_qty(frm);
+	},
+
+	to_time: function(frm) {
+		frm.set_value('job_started', 0);
+		frm.set_value('started_time', '');
 	}
 })
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index f8c60f2..e9627a5 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -9,7 +9,7 @@
 from frappe.model.mapper import get_mapped_doc
 from frappe.model.document import Document
 from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate,
-	get_time, add_to_date, time_diff, add_days, get_datetime_str)
+	get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form)
 
 from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
 
@@ -189,11 +189,15 @@
 
 	def validate_job_card(self):
 		if not self.time_logs:
-			frappe.throw(_("Time logs are required for job card {0}").format(self.name))
+			frappe.throw(_("Time logs are required for {0} {1}")
+				.format(frappe.bold("Job Card"), get_link_to_form("Job Card", self.name)))
 
 		if self.for_quantity and self.total_completed_qty != self.for_quantity:
-			frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})"
-				.format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity))))
+			total_completed_qty = frappe.bold(_("Total Completed Qty"))
+			qty_to_manufacture = frappe.bold(_("Qty to Manufacture"))
+
+			frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})"
+				.format(total_completed_qty, frappe.bold(self.total_completed_qty), qty_to_manufacture,frappe.bold(self.for_quantity))))
 
 	def update_work_order(self):
 		if not self.work_order:
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 9c8aa45..d541866 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -240,6 +240,8 @@
 			});
 		}, __("Job Card"), __("Create"));
 
+		dialog.fields_dict["operations"].grid.wrapper.find('.grid-add-row').hide();
+
 		var pending_qty = 0;
 		frm.doc.operations.forEach(data => {
 			if(data.completed_qty != frm.doc.qty) {
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index 49abab1..f3cecd9 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -83,7 +83,6 @@
    "oldfieldname": "status",
    "oldfieldtype": "Select",
    "options": "Open\nCompleted\nCancelled",
-   "reqd": 1,
    "search_index": 1
   },
   {
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 54e87f7..6462d3b 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -60,9 +60,9 @@
   "base_total",
   "base_net_total",
   "column_break_33",
+  "total_net_weight",
   "total",
   "net_total",
-  "total_net_weight",
   "taxes_section",
   "tax_category",
   "column_break_38",
@@ -1196,7 +1196,7 @@
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2019-12-30 19:15:28.605085",
+ "modified": "2020-04-17 12:50:39.640534",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order",
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index f175687..7011cf9 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -287,7 +287,7 @@
 		if (in_list(['serial_no', 'batch_no'], field)) {
 			args[field] = value;
 		}
-		
+
 		// add to cur_frm
 		const item = this.frm.add_child('items', args);
 		frappe.flags.hide_serial_batch_dialog = true;
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index 6f9d83d..9f5dee9 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -66,9 +66,9 @@
   "base_total",
   "base_net_total",
   "column_break_33",
+  "total_net_weight",
   "total",
   "net_total",
-  "total_net_weight",
   "taxes_section",
   "tax_category",
   "column_break_39",
@@ -1256,7 +1256,7 @@
  "idx": 146,
  "is_submittable": 1,
  "links": [],
- "modified": "2019-12-31 19:17:13.122644",
+ "modified": "2020-04-17 12:51:41.288600",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 47a72b2..d7a93fb 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -434,15 +434,6 @@
 		update_delivery_note_status(dn.name, "Closed")
 		self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed")
 
-	def test_customer_provided_parts_dn(self):
-		create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
-		dn = create_delivery_note(item_code='CUST-0987', rate=0)
-		self.assertEqual(dn.get("items")[0].allow_zero_valuation_rate, 1)
-
-		# test if Delivery Note with rate is allowed against Customer Provided Item
-		dn2 = create_delivery_note(item_code='CUST-0987', do_not_save=True)
-		self.assertRaises(frappe.ValidationError, dn2.save)
-
 	def test_dn_billing_status_case1(self):
 		# SO -> DN -> SI
 		so = make_sales_order()
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 5b242a5..2d98557 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -175,12 +175,11 @@
 
 				frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty)
 
-		target_ref_field = 'qty' if self.material_request_type == "Manufacture" else 'stock_qty'
 		self._update_percent_field({
 			"target_dt": "Material Request Item",
 			"target_parent_dt": self.doctype,
 			"target_parent_field": "per_ordered",
-			"target_ref_field": target_ref_field,
+			"target_ref_field": "stock_qty",
 			"target_field": "ordered_qty",
 			"name": self.name,
 		}, update_modified)
@@ -499,7 +498,7 @@
 	default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
 
 	for d in mr.items:
-		if (d.qty - d.ordered_qty) >0:
+		if (d.stock_qty - d.ordered_qty) > 0:
 			if frappe.db.exists("BOM", {"item": d.item_code, "is_default": 1}):
 				wo_order = frappe.new_doc("Work Order")
 				wo_order.update({
@@ -531,7 +530,7 @@
 		msgprint(_("The following Work Orders were created:") + '\n' + new_line_sep(message))
 
 	if errors:
-		frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors))
+		frappe.throw(_("Work Order cannot be created for following reason:") + '\n' + new_line_sep(errors))
 
 	return work_orders
 
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index b925aed..19924b1 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -7,7 +7,8 @@
 from __future__ import unicode_literals
 import frappe, unittest, erpnext
 from frappe.utils import flt, today
-from erpnext.stock.doctype.material_request.material_request import raise_work_orders
+from erpnext.stock.doctype.material_request.material_request \
+	import raise_work_orders, make_stock_entry, make_purchase_order, make_supplier_quotation
 from erpnext.stock.doctype.item.test_item import create_item
 
 class TestMaterialRequest(unittest.TestCase):
@@ -15,8 +16,6 @@
 		erpnext.set_perpetual_inventory(0)
 
 	def test_make_purchase_order(self):
-		from erpnext.stock.doctype.material_request.material_request import make_purchase_order
-
 		mr = frappe.copy_doc(test_records[0]).insert()
 
 		self.assertRaises(frappe.ValidationError, make_purchase_order,
@@ -30,8 +29,6 @@
 		self.assertEqual(len(po.get("items")), len(mr.get("items")))
 
 	def test_make_supplier_quotation(self):
-		from erpnext.stock.doctype.material_request.material_request import make_supplier_quotation
-
 		mr = frappe.copy_doc(test_records[0]).insert()
 
 		self.assertRaises(frappe.ValidationError, make_supplier_quotation, mr.name)
@@ -45,12 +42,9 @@
 
 
 	def test_make_stock_entry(self):
-		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
-
 		mr = frappe.copy_doc(test_records[0]).insert()
 
-		self.assertRaises(frappe.ValidationError, make_stock_entry,
-			mr.name)
+		self.assertRaises(frappe.ValidationError, make_stock_entry, mr.name)
 
 		mr = frappe.get_doc("Material Request", mr.name)
 		mr.material_request_type = "Material Transfer"
@@ -62,40 +56,40 @@
 
 	def _insert_stock_entry(self, qty1, qty2, warehouse = None ):
 		se = frappe.get_doc({
-				"company": "_Test Company",
-				"doctype": "Stock Entry",
-				"posting_date": "2013-03-01",
-				"posting_time": "00:00:00",
-				"purpose": "Material Receipt",
-				"items": [
-					{
-						"conversion_factor": 1.0,
-						"doctype": "Stock Entry Detail",
-						"item_code": "_Test Item Home Desktop 100",
-						"parentfield": "items",
-						"basic_rate": 100,
-						"qty": qty1,
-						"stock_uom": "_Test UOM 1",
-						"transfer_qty": qty1,
-						"uom": "_Test UOM 1",
-						"t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
-						"cost_center": "_Test Cost Center - _TC"
-					},
-					{
-						"conversion_factor": 1.0,
-						"doctype": "Stock Entry Detail",
-						"item_code": "_Test Item Home Desktop 200",
-						"parentfield": "items",
-						"basic_rate": 100,
-						"qty": qty2,
-						"stock_uom": "_Test UOM 1",
-						"transfer_qty": qty2,
-						"uom": "_Test UOM 1",
-						"t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
-						"cost_center": "_Test Cost Center - _TC"
-					}
-				]
-			})
+			"company": "_Test Company",
+			"doctype": "Stock Entry",
+			"posting_date": "2013-03-01",
+			"posting_time": "00:00:00",
+			"purpose": "Material Receipt",
+			"items": [
+				{
+					"conversion_factor": 1.0,
+					"doctype": "Stock Entry Detail",
+					"item_code": "_Test Item Home Desktop 100",
+					"parentfield": "items",
+					"basic_rate": 100,
+					"qty": qty1,
+					"stock_uom": "_Test UOM 1",
+					"transfer_qty": qty1,
+					"uom": "_Test UOM 1",
+					"t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
+					"cost_center": "_Test Cost Center - _TC"
+				},
+				{
+					"conversion_factor": 1.0,
+					"doctype": "Stock Entry Detail",
+					"item_code": "_Test Item Home Desktop 200",
+					"parentfield": "items",
+					"basic_rate": 100,
+					"qty": qty2,
+					"stock_uom": "_Test UOM 1",
+					"transfer_qty": qty2,
+					"uom": "_Test UOM 1",
+					"t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
+					"cost_center": "_Test Cost Center - _TC"
+				}
+			]
+		})
 
 		se.set_stock_entry_type()
 		se.insert()
@@ -198,14 +192,7 @@
 		mr.insert()
 		mr.submit()
 
-		# check if per complete is None
-		mr.load_from_db()
-		self.assertEqual(mr.per_ordered, 0)
-		self.assertEqual(mr.get("items")[0].ordered_qty, 0)
-		self.assertEqual(mr.get("items")[1].ordered_qty, 0)
-
 		# map a purchase order
-		from erpnext.stock.doctype.material_request.material_request import make_purchase_order
 		po_doc = make_purchase_order(mr.name)
 		po_doc.supplier = "_Test Supplier"
 		po_doc.transaction_date = "2013-07-07"
@@ -276,10 +263,8 @@
 		current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
 		current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
 
-		self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0)
-		self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0)
-
-		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
+		self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
+		self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
 
 		# map a stock entry
 		se_doc = make_stock_entry(mr.name)
@@ -331,8 +316,8 @@
 		current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
 		current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
 
-		self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 27.0)
-		self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 1.5)
+		self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 27.0)
+		self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 1.5)
 
 		# check if per complete is as expected for Stock Entry cancelled
 		se.cancel()
@@ -344,8 +329,8 @@
 		current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
 		current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
 
-		self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0)
-		self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0)
+		self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
+		self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
 
 	def test_completed_qty_for_over_transfer(self):
 		existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
@@ -357,14 +342,7 @@
 		mr.insert()
 		mr.submit()
 
-		# check if per complete is None
-		mr.load_from_db()
-		self.assertEqual(mr.per_ordered, 0)
-		self.assertEqual(mr.get("items")[0].ordered_qty, 0)
-		self.assertEqual(mr.get("items")[1].ordered_qty, 0)
-
 		# map a stock entry
-		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
 
 		se_doc = make_stock_entry(mr.name)
 		se_doc.update({
@@ -425,8 +403,8 @@
 		current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
 		current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
 
-		self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0)
-		self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0)
+		self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
+		self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
 
 	def test_incorrect_mapping_of_stock_entry(self):
 		# submit material request of type Transfer
@@ -435,9 +413,6 @@
 		mr.insert()
 		mr.submit()
 
-		# map a stock entry
-		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
-
 		se_doc = make_stock_entry(mr.name)
 		se_doc.update({
 			"posting_date": "2013-03-01",
@@ -468,8 +443,6 @@
 		mr.insert()
 		mr.submit()
 
-		# map a stock entry
-		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
 		se_doc = make_stock_entry(mr.name)
 		self.assertEqual(se_doc.get("items")[0].s_warehouse, "_Test Warehouse - _TC")
 
@@ -483,8 +456,6 @@
 		return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty"))
 
 	def test_make_stock_entry_for_material_issue(self):
-		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
-
 		mr = frappe.copy_doc(test_records[0]).insert()
 
 		self.assertRaises(frappe.ValidationError, make_stock_entry,
@@ -503,8 +474,6 @@
 			return flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100",
 				"warehouse": "_Test Warehouse - _TC"}, "indented_qty"))
 
-		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
-
 		existing_requested_qty = _get_requested_qty()
 
 		mr = frappe.copy_doc(test_records[0])
@@ -563,9 +532,37 @@
 			item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0]
 		self.assertEqual(requested_qty, new_requested_qty)
 
-	def test_multi_uom_for_purchase(self):
-		from erpnext.stock.doctype.material_request.material_request import make_purchase_order
+	def test_requested_qty_multi_uom(self):
+		existing_requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
 
+		mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture',
+			uom="_Test UOM 1", conversion_factor=12)
+		
+		requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+
+		self.assertEqual(requested_qty, existing_requested_qty + 120)
+
+		work_order = raise_work_orders(mr.name)
+		wo = frappe.get_doc("Work Order", work_order[0])
+		wo.qty = 50
+		wo.wip_warehouse = "_Test Warehouse 1 - _TC"
+		wo.submit()
+
+		requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+		self.assertEqual(requested_qty, existing_requested_qty + 70)
+
+		wo.cancel()
+
+		requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+		self.assertEqual(requested_qty, existing_requested_qty + 120)
+
+		mr.reload()
+		mr.cancel()
+		requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+		self.assertEqual(requested_qty, existing_requested_qty)
+
+
+	def test_multi_uom_for_purchase(self):
 		mr = frappe.copy_doc(test_records[0])
 		mr.material_request_type = 'Purchase'
 		item = mr.items[0]
@@ -607,7 +604,6 @@
 		self.assertEqual(mr.per_ordered, 100)
 
 	def test_customer_provided_parts_mr(self):
-		from erpnext.stock.doctype.material_request.material_request import make_stock_entry
 		create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
 		existing_requested_qty = self._get_requested_qty("_Test Customer", "_Test Warehouse - _TC")
 
@@ -633,6 +629,8 @@
 	mr.append("items", {
 		"item_code": args.item_code or "_Test Item",
 		"qty": args.qty or 10,
+		"uom": args.uom or "_Test UOM",
+		"conversion_factor": args.conversion_factor or 1,
 		"schedule_date": args.schedule_date or today(),
 		"warehouse": args.warehouse or "_Test Warehouse - _TC",
 		"cost_center": args.cost_center or "_Test Cost Center - _TC"
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index 30206b6..5604913 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -1,5 +1,4 @@
 {
- "actions": [],
  "autoname": "hash",
  "creation": "2013-02-22 01:28:02",
  "doctype": "DocType",
@@ -374,7 +373,10 @@
   {
    "fieldname": "received_qty",
    "fieldtype": "Float",
-   "label": "Received Quantity"
+   "label": "Received Quantity",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   },
   {
    "collapsible": 1,
@@ -410,7 +412,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-04-07 18:37:54.495112",
+ "modified": "2020-04-16 09:00:00.992835",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Material Request Item",
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 6b4f73b..1b9ff41 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -111,6 +111,7 @@
 
 		stock_reconciliation = frappe.get_doc({
 			'doctype': 'Stock Reconciliation',
+			'purpose': 'Stock Reconciliation',
 			'company': '_Test Company',
 			'items': [{
 				'item_code': '_Test Serialized Item',
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index e38bb38..eed5749 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -59,9 +59,9 @@
   "base_total",
   "base_net_total",
   "column_break_27",
+  "total_net_weight",
   "total",
   "net_total",
-  "total_net_weight",
   "taxes_charges_section",
   "tax_category",
   "shipping_col",
@@ -1082,7 +1082,7 @@
  "idx": 261,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-04-06 16:31:37.444891",
+ "modified": "2020-04-17 13:06:26.970288",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
index 7f4efba..b7d1497 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
@@ -4,6 +4,7 @@
  "description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
  "doctype": "DocType",
  "document_type": "Document",
+ "engine": "InnoDB",
  "field_order": [
   "naming_series",
   "company",
@@ -44,11 +45,11 @@
    "reqd": 1
   },
   {
-   "default": "Stock Reconciliation",
    "fieldname": "purpose",
    "fieldtype": "Select",
    "label": "Purpose",
-   "options": "Opening Stock\nStock Reconciliation"
+   "options": "\nOpening Stock\nStock Reconciliation",
+   "reqd": 1
   },
   {
    "fieldname": "col1",
@@ -153,7 +154,7 @@
  "idx": 1,
  "is_submittable": 1,
  "max_attachments": 1,
- "modified": "2019-05-26 09:03:09.542141",
+ "modified": "2020-04-08 17:02:47.196206",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Reconciliation",
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index e6d7e3f..51d027f 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -240,6 +240,7 @@
 def create_stock_reconciliation(**args):
 	args = frappe._dict(args)
 	sr = frappe.new_doc("Stock Reconciliation")
+	sr.purpose = args.purpose or "Stock Reconciliation"
 	sr.posting_date = args.posting_date or nowdate()
 	sr.posting_time = args.posting_time or nowtime()
 	sr.set_posting_time = 1
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 2bdb04e..5697315 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -113,24 +113,30 @@
 	return flt(reserved_qty[0][0]) if reserved_qty else 0
 
 def get_indented_qty(item_code, warehouse):
-	inward_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor)
-			from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
-			where mr_item.item_code=%s and mr_item.warehouse=%s
-			and mr.material_request_type in ('Purchase', 'Manufacture')
-			and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name
-			and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse))
-
-	outward_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor)
+	# Ordered Qty is always maintained in stock UOM
+	inward_qty = frappe.db.sql("""
+		select sum(mr_item.stock_qty - mr_item.ordered_qty)
 		from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
 		where mr_item.item_code=%s and mr_item.warehouse=%s
-		and mr.material_request_type in ('Material Issue', 'Material Transfer')
-		and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name
-		and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse))
+			and mr.material_request_type in ('Purchase', 'Manufacture', 'Customer Provided', 'Material Transfer')
+			and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name
+			and mr.status!='Stopped' and mr.docstatus=1
+	""", (item_code, warehouse))
+	inward_qty = flt(inward_qty[0][0]) if inward_qty else 0
 
-	inward_qty, outward_qty = flt(inward_qty[0][0]) if inward_qty else 0, flt(outward_qty[0][0]) if outward_qty else 0
-	indented_qty = inward_qty - outward_qty
+	outward_qty = frappe.db.sql("""
+		select sum(mr_item.stock_qty - mr_item.ordered_qty)
+		from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
+		where mr_item.item_code=%s and mr_item.warehouse=%s
+			and mr.material_request_type = 'Material Issue'
+			and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name
+			and mr.status!='Stopped' and mr.docstatus=1
+	""", (item_code, warehouse))
+	outward_qty = flt(outward_qty[0][0]) if outward_qty else 0
 
-	return indented_qty
+	requested_qty = inward_qty - outward_qty
+
+	return requested_qty
 
 def get_ordered_qty(item_code, warehouse):
 	ordered_qty = frappe.db.sql("""
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index fd72807..117267f 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -335,8 +335,13 @@
 
 	ignore_permissions = False
 	if is_website_user():
-		if not filters: filters = []
-		filters.append(("Issue", "customer", "=", customer)) if customer else filters.append(("Issue", "raised_by", "=", user))
+		if not filters: filters = {}
+
+		if customer:
+			filters["customer"] = customer
+		else:
+			filters["raised_by"] = user
+
 		ignore_permissions = True
 
 	return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions)