Merge pull request #18253 from frappe/jigneshpshah-Report-link

fix: navigation link updated for Item Shortage Report
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
index 29a1a2b..96a533e 100644
--- a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
+++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
@@ -7,7 +7,9 @@
 from frappe import _
 from frappe.model.document import Document
 from requests_oauthlib import OAuth2Session
-import json, requests
+import json
+import requests
+import traceback
 from erpnext import encode_company_abbr
 
 # QuickBooks requires a redirect URL, User will be redirect to this URL
@@ -32,7 +34,6 @@
 class QuickBooksMigrator(Document):
 	def __init__(self, *args, **kwargs):
 		super(QuickBooksMigrator, self).__init__(*args, **kwargs)
-		from pprint import pprint
 		self.oauth = OAuth2Session(
 			client_id=self.client_id,
 			redirect_uri=self.redirect_url,
@@ -46,7 +47,9 @@
 		if self.company:
 			# We need a Cost Center corresponding to the selected erpnext Company
 			self.default_cost_center = frappe.db.get_value('Company', self.company, 'cost_center')
-			self.default_warehouse = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0})[0]["name"]
+			company_warehouses = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0})
+			if company_warehouses:
+				self.default_warehouse = company_warehouses[0].name
 		if self.authorization_endpoint:
 			self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
 
@@ -218,7 +221,7 @@
 
 	def _fetch_general_ledger(self):
 		try:
-			query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint ,self.quickbooks_company_id)
+			query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint, self.quickbooks_company_id)
 			response = self._get(query_uri,
 				params={
 					"columns": ",".join(["tx_date", "txn_type", "credit_amt", "debt_amt"]),
@@ -493,17 +496,17 @@
 						"account_currency": customer["CurrencyRef"]["value"],
 						"company": self.company,
 					})[0]["name"]
-				except Exception as e:
+				except Exception:
 					receivable_account = None
 				erpcustomer = frappe.get_doc({
 					"doctype": "Customer",
 					"quickbooks_id": customer["Id"],
-					"customer_name" : encode_company_abbr(customer["DisplayName"], self.company),
-					"customer_type" : "Individual",
-					"customer_group" : "Commercial",
+					"customer_name": encode_company_abbr(customer["DisplayName"], self.company),
+					"customer_type": "Individual",
+					"customer_group": "Commercial",
 					"default_currency": customer["CurrencyRef"]["value"],
 					"accounts": [{"company": self.company, "account": receivable_account}],
-					"territory" : "All Territories",
+					"territory": "All Territories",
 					"company": self.company,
 				}).insert()
 				if "BillAddr" in customer:
@@ -521,7 +524,7 @@
 					item_dict = {
 						"doctype": "Item",
 						"quickbooks_id": item["Id"],
-						"item_code" : encode_company_abbr(item["Name"], self.company),
+						"item_code": encode_company_abbr(item["Name"], self.company),
 						"stock_uom": "Unit",
 						"is_stock_item": 0,
 						"item_group": "All Item Groups",
@@ -549,14 +552,14 @@
 				erpsupplier = frappe.get_doc({
 					"doctype": "Supplier",
 					"quickbooks_id": vendor["Id"],
-					"supplier_name" : encode_company_abbr(vendor["DisplayName"], self.company),
-					"supplier_group" : "All Supplier Groups",
+					"supplier_name": encode_company_abbr(vendor["DisplayName"], self.company),
+					"supplier_group": "All Supplier Groups",
 					"company": self.company,
 				}).insert()
 				if "BillAddr" in vendor:
 					self._create_address(erpsupplier, "Supplier", vendor["BillAddr"], "Billing")
 				if "ShipAddr" in vendor:
-					self._create_address(erpsupplier, "Supplier",vendor["ShipAddr"], "Shipping")
+					self._create_address(erpsupplier, "Supplier", vendor["ShipAddr"], "Shipping")
 		except Exception as e:
 			self._log_error(e)
 
@@ -829,7 +832,7 @@
 					"currency": invoice["CurrencyRef"]["value"],
 					"conversion_rate": invoice.get("ExchangeRate", 1),
 					"posting_date": invoice["TxnDate"],
-					"due_date":  invoice.get("DueDate", invoice["TxnDate"]),
+					"due_date": invoice.get("DueDate", invoice["TxnDate"]),
 					"credit_to": credit_to_account,
 					"supplier": frappe.get_all("Supplier",
 						filters={
@@ -1200,7 +1203,7 @@
 
 
 	def _create_address(self, entity, doctype, address, address_type):
-		try :
+		try:
 			if not frappe.db.exists({"doctype": "Address", "quickbooks_id": address["Id"]}):
 				frappe.get_doc({
 					"doctype": "Address",
@@ -1252,8 +1255,6 @@
 
 
 	def _log_error(self, execption, data=""):
-		import json, traceback
-		traceback.print_exc()
 		frappe.log_error(title="QuickBooks Migration Error",
 			message="\n".join([
 				"Data",
diff --git a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py
deleted file mode 100644
index b6c6502..0000000
--- a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe, math
-from frappe import _
-from frappe.utils import flt, rounded
-from frappe.model.mapper import get_mapped_doc
-from frappe.model.document import Document
-
-from erpnext.hr.doctype.employee_loan.employee_loan import get_monthly_repayment_amount, check_repayment_method
-
-class EmployeeLoanApplication(Document):
-	def validate(self):
-		check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
-		self.validate_loan_amount()
-		self.get_repayment_details()
-
-	def validate_loan_amount(self):
-		maximum_loan_limit = frappe.db.get_value('Loan Type', self.loan_type, 'maximum_loan_amount')
-		if maximum_loan_limit and self.loan_amount > maximum_loan_limit:
-			frappe.throw(_("Loan Amount cannot exceed Maximum Loan Amount of {0}").format(maximum_loan_limit))
-
-	def get_repayment_details(self):
-		if self.repayment_method == "Repay Over Number of Periods":
-			self.repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
-
-		if self.repayment_method == "Repay Fixed Amount per Period":
-			monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
-			if monthly_interest_rate:
-				monthly_interest_amount = self.loan_amount * monthly_interest_rate
-				if monthly_interest_amount >= self.repayment_amount:
-					frappe.throw(_("Repayment amount {} should be greater than monthly interest amount {}").
-						format(self.repayment_amount, monthly_interest_amount))
-
-				self.repayment_periods = math.ceil((math.log(self.repayment_amount) - 
-					math.log(self.repayment_amount - (monthly_interest_amount))) /
-					(math.log(1 + monthly_interest_rate)))
-			else:
-				self.repayment_periods = self.loan_amount / self.repayment_amount
-
-		self.calculate_payable_amount()
-		
-	def calculate_payable_amount(self):
-		balance_amount = self.loan_amount
-		self.total_payable_amount = 0
-		self.total_payable_interest = 0
-
-		while(balance_amount > 0):
-			interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100))
-			balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount)
-
-			self.total_payable_interest += interest_amount
-			
-		self.total_payable_amount = self.loan_amount + self.total_payable_interest
-		
-@frappe.whitelist()
-def make_employee_loan(source_name, target_doc = None):
-	doclist = get_mapped_doc("Employee Loan Application", source_name, {
-		"Employee Loan Application": {
-			"doctype": "Employee Loan",
-			"validation": {
-				"docstatus": ["=", 1]
-			}
-		}
-	}, target_doc)
-
-	return doclist
\ No newline at end of file
diff --git a/erpnext/hr/doctype/loan/loan.js b/erpnext/hr/doctype/loan/loan.js
index e1b4178..3f5c30c 100644
--- a/erpnext/hr/doctype/loan/loan.js
+++ b/erpnext/hr/doctype/loan/loan.js
@@ -39,31 +39,19 @@
 	},
 
 	refresh: function (frm) {
-		if (frm.doc.docstatus == 1 && frm.doc.status == "Sanctioned") {
-			frm.add_custom_button(__('Create Disbursement Entry'), function() {
-				frm.trigger("make_jv");
-			})
-		}
-		if (frm.doc.repayment_schedule) {
-			let total_amount_paid = 0;
-			$.each(frm.doc.repayment_schedule || [], function(i, row) {
-				if (row.paid) {
-					total_amount_paid += row.total_payment;
-				}
-			});
-			frm.set_value("total_amount_paid", total_amount_paid);
-;		}
-		if (frm.doc.docstatus == 1 && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) {
-			frm.add_custom_button(__('Create Repayment Entry'), function() {
-				frm.trigger("make_repayment_entry");
-			})
+		if (frm.doc.docstatus == 1) {
+			if (frm.doc.status == "Sanctioned") {
+				frm.add_custom_button(__('Create Disbursement Entry'), function() {
+					frm.trigger("make_jv");
+				}).addClass("btn-primary");
+			} else if (frm.doc.status == "Disbursed" && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) {
+				frm.add_custom_button(__('Create Repayment Entry'), function() {
+					frm.trigger("make_repayment_entry");
+				}).addClass("btn-primary");
+			}
 		}
 		frm.trigger("toggle_fields");
 	},
-	status: function (frm) {
-		frm.toggle_reqd("disbursement_date", frm.doc.status == 'Disbursed')
-		frm.toggle_reqd("repayment_start_date", frm.doc.status == 'Disbursed')
-	},
 
 	make_jv: function (frm) {
 		frappe.call({
diff --git a/erpnext/hr/doctype/loan/loan.json b/erpnext/hr/doctype/loan/loan.json
index 587b301..505b601 100644
--- a/erpnext/hr/doctype/loan/loan.json
+++ b/erpnext/hr/doctype/loan/loan.json
@@ -1,5 +1,6 @@
 {
  "allow_copy": 0, 
+ "allow_events_in_timeline": 0, 
  "allow_guest_to_view": 0, 
  "allow_import": 1, 
  "allow_rename": 0, 
@@ -20,6 +21,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "applicant_type", 
    "fieldtype": "Select", 
    "hidden": 0, 
@@ -53,6 +55,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "applicant", 
    "fieldtype": "Dynamic Link", 
    "hidden": 0, 
@@ -86,6 +89,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "applicant_name", 
    "fieldtype": "Data", 
    "hidden": 0, 
@@ -118,6 +122,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "loan_application", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -151,6 +156,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "loan_type", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -184,6 +190,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "column_break_3", 
    "fieldtype": "Column Break", 
    "hidden": 0, 
@@ -215,7 +222,8 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "default": "", 
+   "default": "Today", 
+   "fetch_if_empty": 0, 
    "fieldname": "posting_date", 
    "fieldtype": "Date", 
    "hidden": 0, 
@@ -248,6 +256,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "company", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -282,6 +291,7 @@
    "collapsible": 0, 
    "columns": 0, 
    "default": "Sanctioned", 
+   "fetch_if_empty": 0, 
    "fieldname": "status", 
    "fieldtype": "Select", 
    "hidden": 0, 
@@ -299,7 +309,7 @@
    "precision": "", 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
-   "read_only": 0, 
+   "read_only": 1, 
    "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
@@ -316,6 +326,7 @@
    "collapsible": 0, 
    "columns": 0, 
    "depends_on": "eval:doc.applicant_type==\"Employee\"", 
+   "fetch_if_empty": 0, 
    "fieldname": "repay_from_salary", 
    "fieldtype": "Check", 
    "hidden": 0, 
@@ -348,6 +359,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "section_break_8", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -380,6 +392,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "loan_amount", 
    "fieldtype": "Currency", 
    "hidden": 0, 
@@ -415,6 +428,7 @@
    "columns": 0, 
    "default": "", 
    "fetch_from": "loan_type.rate_of_interest", 
+   "fetch_if_empty": 0, 
    "fieldname": "rate_of_interest", 
    "fieldtype": "Percent", 
    "hidden": 0, 
@@ -448,6 +462,8 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "depends_on": "eval:doc.status==\"Disbursed\"", 
+   "fetch_if_empty": 0, 
    "fieldname": "disbursement_date", 
    "fieldtype": "Date", 
    "hidden": 0, 
@@ -480,6 +496,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "repayment_start_date", 
    "fieldtype": "Date", 
    "hidden": 0, 
@@ -499,7 +516,7 @@
    "read_only": 0, 
    "remember_last_selected_value": 0, 
    "report_hide": 0, 
-   "reqd": 0, 
+   "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
    "translatable": 0, 
@@ -512,6 +529,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "column_break_11", 
    "fieldtype": "Column Break", 
    "hidden": 0, 
@@ -544,6 +562,7 @@
    "collapsible": 0, 
    "columns": 0, 
    "default": "Repay Over Number of Periods", 
+   "fetch_if_empty": 0, 
    "fieldname": "repayment_method", 
    "fieldtype": "Select", 
    "hidden": 0, 
@@ -579,6 +598,7 @@
    "columns": 0, 
    "default": "", 
    "depends_on": "", 
+   "fetch_if_empty": 0, 
    "fieldname": "repayment_periods", 
    "fieldtype": "Int", 
    "hidden": 0, 
@@ -613,6 +633,7 @@
    "columns": 0, 
    "default": "", 
    "depends_on": "", 
+   "fetch_if_empty": 0, 
    "fieldname": "monthly_repayment_amount", 
    "fieldtype": "Currency", 
    "hidden": 0, 
@@ -646,6 +667,7 @@
    "bold": 0, 
    "collapsible": 1, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "account_info", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -678,6 +700,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "mode_of_payment", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -711,6 +734,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "payment_account", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -744,6 +768,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "column_break_9", 
    "fieldtype": "Column Break", 
    "hidden": 0, 
@@ -775,6 +800,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "loan_account", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -808,6 +834,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "interest_income_account", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -841,6 +868,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "section_break_15", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -873,6 +901,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "repayment_schedule", 
    "fieldtype": "Table", 
    "hidden": 0, 
@@ -906,6 +935,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "section_break_17", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
@@ -939,6 +969,7 @@
    "collapsible": 0, 
    "columns": 0, 
    "default": "0", 
+   "fetch_if_empty": 0, 
    "fieldname": "total_payment", 
    "fieldtype": "Currency", 
    "hidden": 0, 
@@ -972,6 +1003,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "column_break_19", 
    "fieldtype": "Column Break", 
    "hidden": 0, 
@@ -1004,6 +1036,7 @@
    "collapsible": 0, 
    "columns": 0, 
    "default": "0", 
+   "fetch_if_empty": 0, 
    "fieldname": "total_interest_payable", 
    "fieldtype": "Currency", 
    "hidden": 0, 
@@ -1037,6 +1070,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "total_amount_paid", 
    "fieldtype": "Currency", 
    "hidden": 0, 
@@ -1070,6 +1104,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fetch_if_empty": 0, 
    "fieldname": "amended_from", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -1106,7 +1141,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-08-21 16:15:53.267145", 
+ "modified": "2019-07-10 13:04:20.953694", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Loan", 
@@ -1149,7 +1184,6 @@
    "set_user_permissions": 0, 
    "share": 0, 
    "submit": 0, 
-   "user_permission_doctypes": "[\"Employee\"]", 
    "write": 0
   }
  ], 
diff --git a/erpnext/hr/doctype/loan/loan.py b/erpnext/hr/doctype/loan/loan.py
index 58c9b8f..a803863 100644
--- a/erpnext/hr/doctype/loan/loan.py
+++ b/erpnext/hr/doctype/loan/loan.py
@@ -6,29 +6,33 @@
 import frappe, math, json
 import erpnext
 from frappe import _
-from frappe.utils import flt, rounded, add_months, nowdate
+from frappe.utils import flt, rounded, add_months, nowdate, getdate
 from erpnext.controllers.accounts_controller import AccountsController
 
 class Loan(AccountsController):
 	def validate(self):
-		check_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods)
+		validate_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods)
+		self.set_missing_fields()
+		self.make_repayment_schedule()
+		self.set_repayment_period()
+		self.calculate_totals()
+
+	def set_missing_fields(self):
 		if not self.company:
 			self.company = erpnext.get_default_company()
+
 		if not self.posting_date:
 			self.posting_date = nowdate()
+
 		if self.loan_type and not self.rate_of_interest:
 			self.rate_of_interest = frappe.db.get_value("Loan Type", self.loan_type, "rate_of_interest")
+
 		if self.repayment_method == "Repay Over Number of Periods":
 			self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
+
 		if self.status == "Repaid/Closed":
 			self.total_amount_paid = self.total_payment
-		if self.status == 'Disbursed' and self.repayment_start_date < self.disbursement_date:
-			frappe.throw(_("Repayment Start Date cannot be before Disbursement Date."))
 
-		if self.status == "Disbursed":
-			self.make_repayment_schedule()
-			self.set_repayment_period()
-			self.calculate_totals()
 
 	def make_jv_entry(self):
 		self.check_permission('write')
@@ -105,20 +109,31 @@
 	frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid)
 
 def update_disbursement_status(doc):
-	disbursement = frappe.db.sql("""select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount
-		from `tabGL Entry` where account = %s and against_voucher_type = 'Loan' and against_voucher = %s""",
-		(doc.payment_account, doc.name), as_dict=1)[0]
-	if disbursement.disbursed_amount == doc.loan_amount:
-		frappe.db.set_value("Loan", doc.name , "status", "Disbursed")
-	if disbursement.disbursed_amount == 0:
-		frappe.db.set_value("Loan", doc.name , "status", "Sanctioned")
-	if disbursement.disbursed_amount > doc.loan_amount:
-		frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount))
-	if disbursement.disbursed_amount > 0:
-		frappe.db.set_value("Loan", doc.name , "disbursement_date", disbursement.posting_date)
-		frappe.db.set_value("Loan", doc.name , "repayment_start_date", disbursement.posting_date)
+	disbursement = frappe.db.sql("""
+		select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount
+		from `tabGL Entry`
+		where account = %s and against_voucher_type = 'Loan' and against_voucher = %s
+	""", (doc.payment_account, doc.name), as_dict=1)[0]
 
-def check_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods):
+	disbursement_date = None
+	if not disbursement or disbursement.disbursed_amount == 0:
+		status = "Sanctioned"
+	elif disbursement.disbursed_amount == doc.loan_amount:
+		disbursement_date = disbursement.posting_date
+		status = "Disbursed"
+	elif disbursement.disbursed_amount > doc.loan_amount:
+		frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount))
+
+	if status == 'Disbursed' and getdate(disbursement_date) > getdate(frappe.db.get_value("Loan", doc.name, "repayment_start_date")):
+			frappe.throw(_("Disbursement Date cannot be after Loan Repayment Start Date"))
+
+	frappe.db.sql("""
+		update `tabLoan`
+		set status = %s, disbursement_date = %s
+		where name = %s
+	""", (status, disbursement_date, doc.name))
+
+def validate_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods):
 	if repayment_method == "Repay Over Number of Periods" and not repayment_periods:
 		frappe.throw(_("Please enter Repayment Periods"))
 
@@ -222,4 +237,4 @@
 		"reference_name": loan,
 		})
 	journal_entry.set("accounts", account_amt_list)
-	return journal_entry.as_dict()
\ No newline at end of file
+	return journal_entry.as_dict()
diff --git a/erpnext/hr/doctype/loan_application/loan_application.js b/erpnext/hr/doctype/loan_application/loan_application.js
index febcbd8..a73b62a 100644
--- a/erpnext/hr/doctype/loan_application/loan_application.js
+++ b/erpnext/hr/doctype/loan_application/loan_application.js
@@ -23,9 +23,8 @@
 	},
 	add_toolbar_buttons: function(frm) {
 		if (frm.doc.status == "Approved") {
-			frm.add_custom_button(__('Loan'), function() {
+			frm.add_custom_button(__('Create Loan'), function() {
 				frappe.call({
-					type: "GET",
 					method: "erpnext.hr.doctype.loan_application.loan_application.make_loan",
 					args: {
 						"source_name": frm.doc.name
@@ -37,7 +36,7 @@
 						}
 					}
 				});
-			})
+			}).addClass("btn-primary");
 		}
 	}
 });
diff --git a/erpnext/hr/doctype/loan_application/loan_application.py b/erpnext/hr/doctype/loan_application/loan_application.py
index 706c964..5dbcf15 100644
--- a/erpnext/hr/doctype/loan_application/loan_application.py
+++ b/erpnext/hr/doctype/loan_application/loan_application.py
@@ -9,11 +9,11 @@
 from frappe.model.mapper import get_mapped_doc
 from frappe.model.document import Document
 
-from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, check_repayment_method
+from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, validate_repayment_method
 
 class LoanApplication(Document):
 	def validate(self):
-		check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
+		validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
 		self.validate_loan_amount()
 		self.get_repayment_details()
 
@@ -29,14 +29,14 @@
 		if self.repayment_method == "Repay Fixed Amount per Period":
 			monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
 			if monthly_interest_rate:
-				self.repayment_periods = math.ceil((math.log(self.repayment_amount) - 
+				self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
 					math.log(self.repayment_amount - (self.loan_amount*monthly_interest_rate))) /
 					(math.log(1 + monthly_interest_rate)))
 			else:
 				self.repayment_periods = self.loan_amount / self.repayment_amount
 
 		self.calculate_payable_amount()
-		
+
 	def calculate_payable_amount(self):
 		balance_amount = self.loan_amount
 		self.total_payable_amount = 0
@@ -47,9 +47,9 @@
 			balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount)
 
 			self.total_payable_interest += interest_amount
-			
+
 		self.total_payable_amount = self.loan_amount + self.total_payable_interest
-		
+
 @frappe.whitelist()
 def make_loan(source_name, target_doc = None):
 	doclist = get_mapped_doc("Loan Application", source_name, {
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 75eb794..766f675 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -594,6 +594,7 @@
 				sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * %(qty)s as qty,
 				item.description,
 				item.image,
+				bom.project,
 				item.stock_uom,
 				item.allow_alternative_item,
 				item_default.default_warehouse,
diff --git a/erpnext/patches/v12_0/set_priority_for_support.py b/erpnext/patches/v12_0/set_priority_for_support.py
index cc29039..5096ed4 100644
--- a/erpnext/patches/v12_0/set_priority_for_support.py
+++ b/erpnext/patches/v12_0/set_priority_for_support.py
@@ -33,19 +33,23 @@
 		service_level_priorities = frappe.get_list("Service Level", fields=["name", "priority", "response_time", "response_time_period", "resolution_time", "resolution_time_period"])
 
 		frappe.reload_doc("support", "doctype", "service_level")
+		frappe.reload_doc("support", "doctype", "support_settings")
+		frappe.db.set_value('Support Settings', None, 'track_service_level_agreement', 1)
 
 		for service_level in service_level_priorities:
 			if service_level:
 				doc = frappe.get_doc("Service Level", service_level.name)
-				doc.append("priorities", {
-					"priority": service_level.priority,
-					"default_priority": 1,
-					"response_time": service_level.response_time,
-					"response_time_period": service_level.response_time_period,
-					"resolution_time": service_level.resolution_time,
-					"resolution_time_period": service_level.resolution_time_period
-				})
-				doc.save(ignore_permissions=True)
+				if not doc.priorities:
+					doc.append("priorities", {
+						"priority": service_level.priority,
+						"default_priority": 1,
+						"response_time": service_level.response_time,
+						"response_time_period": service_level.response_time_period,
+						"resolution_time": service_level.resolution_time,
+						"resolution_time_period": service_level.resolution_time_period
+					})
+					doc.flags.ignore_validate = True
+					doc.save(ignore_permissions=True)
 	except frappe.db.TableMissingError:
 		frappe.reload_doc("support", "doctype", "service_level")
 
@@ -73,6 +77,7 @@
 					"resolution_time": service_level_agreement.resolution_time,
 					"resolution_time_period": service_level_agreement.resolution_time_period
 				})
+				doc.flags.ignore_validate = True
 				doc.save(ignore_permissions=True)
 	except frappe.db.TableMissingError:
 		frappe.reload_doc("support", "doctype", "service_level_agreement")
\ No newline at end of file
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index cf48be4..0cd648e 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -573,7 +573,6 @@
 				if(!r.exc) {
 					var doc = frappe.model.sync(r.message);
 					cur_frm.dirty();
-					erpnext.utils.clear_duplicates();
 					cur_frm.refresh();
 				}
 			}
@@ -604,28 +603,6 @@
 	}
 }
 
-erpnext.utils.clear_duplicates = function() {
-	if(!cur_frm.doc.items) return;
-	const unique_items = new Map();
-	/*
-		Create a Map of items with
-		item_code => [qty, warehouse, batch_no]
-	*/
-	let items = [];
-
-	for (let item of cur_frm.doc.items) {
-		if (!(unique_items.has(item.item_code) && unique_items.get(item.item_code)[0] === item.qty &&
-			unique_items.get(item.item_code)[1] === item.warehouse && unique_items.get(item.item_code)[2] === item.batch_no &&
-			unique_items.get(item.item_code)[3] === item.delivery_date && unique_items.get(item.item_code)[4] === item.required_date &&
-			unique_items.get(item.item_code)[5] === item.rate)) {
-
-			unique_items.set(item.item_code, [item.qty, item.warehouse, item.batch_no, item.delivery_date, item.required_date, item.rate]);
-			items.push(item);
-		}
-	}
-	cur_frm.doc.items = items;
-}
-
 frappe.form.link_formatters['Item'] = function(value, doc) {
 	if(doc && doc.item_name && doc.item_name !== value) {
 		return value? value + ': ' + doc.item_name: doc.item_name;
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 5351f32..96e31ff 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -211,6 +211,7 @@
 							d.stock_uom = item.stock_uom;
 							d.conversion_factor = 1;
 							d.qty = item.qty;
+							d.project = item.project;
 						});
 					}
 					d.hide();