Merge branch 'develop' into shift_management
diff --git a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py
index f28a074..88e1055 100644
--- a/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py
+++ b/erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py
@@ -27,4 +27,4 @@
 	for col in column_list:
 		sanitize_searchfield(col) 
 	return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
-		.format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0]
+		.format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0]
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 3dab054..71f2e12 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1619,22 +1619,23 @@
 
 	for pos_payment_method in pos_profile.get('payments'):
 		pos_payment_method = pos_payment_method.as_dict()
-		
+
 		payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
-		payment_mode[0].default = pos_payment_method.default
-		append_payment(payment_mode[0])
+		if payment_mode:
+			payment_mode[0].default = pos_payment_method.default
+			append_payment(payment_mode[0])
 
 def get_all_mode_of_payments(doc):
 	return frappe.db.sql("""
-		select mpa.default_account, mpa.parent, mp.type as type 
-		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp 
+		select mpa.default_account, mpa.parent, mp.type as type
+		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
 		where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
 	{'company': doc.company}, as_dict=1)
 
 def get_mode_of_payment_info(mode_of_payment, company):
 	return frappe.db.sql("""
-		select mpa.default_account, mpa.parent, mp.type as type 
-		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp 
+		select mpa.default_account, mpa.parent, mp.type as type
+		from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
 		where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
 	(company, mode_of_payment), as_dict=1)
 
diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py
index e7e1a37..3d68bc8 100644
--- a/erpnext/hr/doctype/job_offer/job_offer.py
+++ b/erpnext/hr/doctype/job_offer/job_offer.py
@@ -24,8 +24,13 @@
 		check_vacancies = frappe.get_single("HR Settings").check_vacancies
 		if staffing_plan and check_vacancies:
 			job_offers = self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date)
-			if staffing_plan.vacancies - len(job_offers) <= 0:
-				frappe.throw(_("There are no vacancies under staffing plan {0}").format(frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent))))
+
+			if not staffing_plan.get("vacancies") or staffing_plan.vacancies - len(job_offers) <= 0:
+				error_variable = 'for ' + frappe.bold(self.designation)
+				if staffing_plan.get("parent"):
+					error_variable = frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent))
+
+				frappe.throw(_("There are no vacancies under staffing plan {0}").format(error_variable))
 
 	def on_change(self):
 		update_job_applicant(self.status, self.job_applicant)
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
index 210a73c..e9e129c 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
@@ -5,20 +5,23 @@
 
 frappe.ui.form.on("Leave Allocation", {
 	onload: function(frm) {
+		// Ignore cancellation of doctype on cancel all.
+		frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
+
 		if(!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today());
 
 		frm.set_query("employee", function() {
 			return {
 				query: "erpnext.controllers.queries.employee_query"
-			}
+			};
 		});
 		frm.set_query("leave_type", function() {
 			return {
 				filters: {
 					is_lwp: 0
 				}
-			}
-		})
+			};
+		});
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index 4001a45..d62e418 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -19,6 +19,10 @@
 		frm.set_query("employee", erpnext.queries.employee);
 	},
 	onload: function(frm) {
+
+		// Ignore cancellation of doctype on cancel all.
+		frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
+
 		if (!frm.doc.posting_date) {
 			frm.set_value("posting_date", frappe.datetime.get_today());
 		}
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.js b/erpnext/hr/doctype/leave_encashment/leave_encashment.js
index 701c2f0..71a3422 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.js
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.js
@@ -2,6 +2,10 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Leave Encashment', {
+	onload: function(frm) {
+		// Ignore cancellation of doctype on cancel all.
+		frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"];
+	},
 	setup: function(frm) {
 		frm.set_query("leave_type", function() {
 			return {
@@ -33,7 +37,7 @@
 				doc: frm.doc,
 				callback: function(r) {
 					frm.refresh_fields();
-					}
+				}
 			});
 		}
 	}
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py
index 71773f1..bac6e63 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.py
@@ -135,10 +135,7 @@
 			"validation": {
 				"docstatus": ["=", 1]
 			},
-			"postprocess": update_accounts,
-			"field_no_map": [
-				"is_secured_loan"
-			]
+			"postprocess": update_accounts
 		}
 	}, target_doc)
 
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index d44088b..6c27e12 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -10,22 +10,20 @@
 from erpnext.controllers.accounts_controller import AccountsController
 from erpnext.accounts.general_ledger import make_gl_entries
 from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_demand_loans
+from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
+from frappe.utils import get_datetime
 
 class LoanDisbursement(AccountsController):
 
 	def validate(self):
 		self.set_missing_values()
 
-	def before_submit(self):
-		self.set_status_and_amounts()
-
-	def before_cancel(self):
-		self.set_status_and_amounts(cancel=1)
-
 	def on_submit(self):
+		self.set_status_and_amounts()
 		self.make_gl_entries()
 
 	def on_cancel(self):
+		self.set_status_and_amounts(cancel=1)
 		self.make_gl_entries(cancel=1)
 		self.ignore_linked_doctypes = ['GL Entry']
 
@@ -45,29 +43,69 @@
 	def set_status_and_amounts(self, cancel=0):
 
 		loan_details = frappe.get_all("Loan",
-			fields = ["loan_amount", "disbursed_amount", "total_principal_paid", "status", "is_term_loan"],
-			filters= { "name": self.against_loan }
-		)[0]
-
-		if loan_details.status == "Disbursed" and not loan_details.is_term_loan:
-			process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1),
-				loan=self.against_loan)
+			fields = ["loan_amount", "disbursed_amount", "total_payment", "total_principal_paid", "total_interest_payable",
+				"status", "is_term_loan", "is_secured_loan"], filters= { "name": self.against_loan })[0]
 
 		if cancel:
 			disbursed_amount = loan_details.disbursed_amount - self.disbursed_amount
+			total_payment = loan_details.total_payment
+
+			if loan_details.disbursed_amount > loan_details.loan_amount:
+				topup_amount = loan_details.disbursed_amount - loan_details.loan_amount
+				if topup_amount > self.disbursed_amount:
+					topup_amount = self.disbursed_amount
+
+				total_payment = total_payment - topup_amount
+
 			if disbursed_amount == 0:
 				status = "Sanctioned"
-			elif disbursed_amount >= loan_details.disbursed_amount:
+			elif disbursed_amount >= loan_details.loan_amount:
 				status = "Disbursed"
 			else:
 				status = "Partially Disbursed"
 		else:
 			disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount
+			total_payment = loan_details.total_payment
 
-			if flt(disbursed_amount) - flt(loan_details.total_principal_paid) > flt(loan_details.loan_amount):
+			if disbursed_amount > loan_details.loan_amount and loan_details.is_term_loan:
 				frappe.throw(_("Disbursed Amount cannot be greater than loan amount"))
 
-			if flt(disbursed_amount) >= loan_details.disbursed_amount:
+			if loan_details.status == 'Disbursed':
+				pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
+					- flt(loan_details.total_principal_paid)
+			else:
+				pending_principal_amount = loan_details.disbursed_amount
+
+			security_value = 0.0
+			if loan_details.is_secured_loan:
+				security_value = get_total_pledged_security_value(self.against_loan)
+
+			if not security_value:
+				security_value = loan_details.loan_amount
+
+			if pending_principal_amount + self.disbursed_amount > flt(security_value):
+				allowed_amount = security_value - pending_principal_amount
+				if allowed_amount < 0:
+					allowed_amount = 0
+
+				frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(allowed_amount))
+
+			if loan_details.status == "Disbursed" and not loan_details.is_term_loan:
+				process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1),
+					loan=self.against_loan)
+
+			if disbursed_amount > loan_details.loan_amount:
+				topup_amount = disbursed_amount - loan_details.loan_amount
+
+				if topup_amount < 0:
+					topup_amount = 0
+
+				if topup_amount > self.disbursed_amount:
+					topup_amount = self.disbursed_amount
+
+				total_payment = total_payment + topup_amount
+
+			if flt(disbursed_amount) >= loan_details.loan_amount:
 				status = "Disbursed"
 			else:
 				status = "Partially Disbursed"
@@ -75,7 +113,8 @@
 		frappe.db.set_value("Loan", self.against_loan, {
 			"disbursement_date": self.disbursement_date,
 			"disbursed_amount": disbursed_amount,
-			"status": status
+			"status": status,
+			"total_payment": total_payment
 		})
 
 	def make_gl_entries(self, cancel=0, adv_adj=0):
@@ -116,3 +155,24 @@
 
 		if gle_map:
 			make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj)
+
+def get_total_pledged_security_value(loan):
+	update_time = get_datetime()
+
+	loan_security_price_map = frappe._dict(frappe.get_all("Loan Security Price",
+		fields=["loan_security", "loan_security_price"],
+		filters = {
+			"valid_from": ("<=", update_time),
+			"valid_upto": (">=", update_time)
+		}, as_list=1))
+
+	hair_cut_map = frappe._dict(frappe.get_all('Loan Security',
+		fields=["name", "haircut"], as_list=1))
+
+	security_value = 0.0
+	pledged_securities = get_pledged_security_qty(loan)
+
+	for security, qty in pledged_securities.items():
+		security_value += (loan_security_price_map.get(security) * qty * hair_cut_map.get(security))/100
+
+	return security_value
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index b56fa80..c5111fd 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -85,8 +85,8 @@
 	if no_of_days <= 0:
 		return
 
-	pending_principal_amount = loan.total_payment - loan.total_interest_payable \
-		- loan.total_amount_paid
+	pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
+		- flt(loan.total_principal_paid)
 
 	interest_per_day = (pending_principal_amount * loan.rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100)
 	payable_interest = interest_per_day * no_of_days
diff --git a/erpnext/www/support/index.html b/erpnext/www/support/index.html
index 93da503..12b4c2c 100644
--- a/erpnext/www/support/index.html
+++ b/erpnext/www/support/index.html
@@ -9,6 +9,33 @@
 			<p class="hero-subtitle">{{ greeting_subtitle }}</p>
 			{% endif %}
 		</div>
+		<div class="search-container">
+			<div class="website-search" id="search-container">
+				<div class="dropdown">
+					<div class="search-icon">
+						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
+							fill="none"
+							stroke="currentColor" stroke-width="2" stroke-linecap="round"
+							stroke-linejoin="round"
+							class="feather feather-search">
+							<circle cx="11" cy="11" r="8"></circle>
+							<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+						</svg>
+					</div>
+					<input type="search" class="form-control" placeholder="Search the docs (Press ? to focus)" />
+					<div class="overflow-hidden shadow dropdown-menu w-100">
+					</div>
+				</div>
+			</div>
+			<button class="navbar-toggler" type="button"
+				data-toggle="collapse"
+				data-target="#navbarSupportedContent"
+				aria-controls="navbarSupportedContent"
+				aria-expanded="false"
+				aria-label="Toggle navigation">
+				<span class="navbar-toggler-icon"></span>
+			</button>
+		</div>
 	</div>
 </section>
 
@@ -54,5 +81,21 @@
 	</div>
 </section>
 {% endif %}
+{% endblock %}
 
-{% endblock %}
\ No newline at end of file
+{%- block script -%}
+<script>
+	frappe.ready(() => {
+		frappe.setup_search('#search-container', 'kb');
+	});
+</script>
+{%- endblock -%}
+
+{%- block style -%}
+<style>
+	.search-container {
+		margin-top: 1.2rem;
+		max-width: 500px;
+	}	
+</style>
+{%- endblock -%}