Merge branch 'develop' into patient-history-enhancements
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 38d8a62..5a5c448 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -132,16 +132,10 @@
 
 	return caller
 
-def get_last_membership():
+def get_last_membership(member):
 	'''Returns last membership if exists'''
 	last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
-		dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
+		dict(member=member, paid=1), order_by='to_date desc', limit=1)
 
-	return last_membership and last_membership[0]
-
-def is_member():
-	'''Returns true if the user is still a member'''
-	last_membership = get_last_membership()
-	if last_membership and getdate(last_membership.to_date) > getdate():
-		return True
-	return False
+	if last_membership:
+		return last_membership[0]
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js
index 9a6c389..65c5ff1 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js
@@ -2,7 +2,6 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Accounting Dimension', {
-
 	refresh: function(frm) {
 		frm.set_query('document_type', () => {
 			let invalid_doctypes = frappe.model.core_doctypes_list;
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index f888d9e..52e9ff8 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -203,7 +203,7 @@
 	return all_dimensions
 
 @frappe.whitelist()
-def get_dimension_filters():
+def get_dimensions(with_cost_center_and_project=False):
 	dimension_filters = frappe.db.sql("""
 		SELECT label, fieldname, document_type
 		FROM `tabAccounting Dimension`
@@ -214,6 +214,18 @@
 		FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
 		WHERE c.parent = p.name""", as_dict=1)
 
+	if with_cost_center_and_project:
+		dimension_filters.extend([
+			{
+				'fieldname': 'cost_center',
+				'document_type': 'Cost Center'
+			},
+			{
+				'fieldname': 'project',
+				'document_type': 'Project'
+			}
+		])
+
 	default_dimensions_map = {}
 	for dimension in default_dimensions:
 		default_dimensions_map.setdefault(dimension.company, {})
diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
index 104880f..fc1d7e3 100644
--- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
@@ -11,37 +11,7 @@
 
 class TestAccountingDimension(unittest.TestCase):
 	def setUp(self):
-		frappe.set_user("Administrator")
-
-		if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
-			dimension = frappe.get_doc({
-				"doctype": "Accounting Dimension",
-				"document_type": "Department",
-			}).insert()
-		else:
-			dimension1 = frappe.get_doc("Accounting Dimension", "Department")
-			dimension1.disabled = 0
-			dimension1.save()
-
-		if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
-			dimension1 = frappe.get_doc({
-				"doctype": "Accounting Dimension",
-				"document_type": "Location",
-			})
-
-			dimension1.append("dimension_defaults", {
-				"company": "_Test Company",
-				"reference_document": "Location",
-				"default_dimension": "Block 1",
-				"mandatory_for_bs": 1
-			})
-
-			dimension1.insert()
-			dimension1.save()
-		else:
-			dimension1 = frappe.get_doc("Accounting Dimension", "Location")
-			dimension1.disabled = 0
-			dimension1.save()
+		create_dimension()
 
 	def test_dimension_against_sales_invoice(self):
 		si = create_sales_invoice(do_not_save=1)
@@ -101,6 +71,38 @@
 	def tearDown(self):
 		disable_dimension()
 
+def create_dimension():
+	frappe.set_user("Administrator")
+
+	if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
+		frappe.get_doc({
+			"doctype": "Accounting Dimension",
+			"document_type": "Department",
+		}).insert()
+	else:
+		dimension = frappe.get_doc("Accounting Dimension", "Department")
+		dimension.disabled = 0
+		dimension.save()
+
+	if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
+		dimension1 = frappe.get_doc({
+			"doctype": "Accounting Dimension",
+			"document_type": "Location",
+		})
+
+		dimension1.append("dimension_defaults", {
+			"company": "_Test Company",
+			"reference_document": "Location",
+			"default_dimension": "Block 1",
+			"mandatory_for_bs": 1
+		})
+
+		dimension1.insert()
+		dimension1.save()
+	else:
+		dimension1 = frappe.get_doc("Accounting Dimension", "Location")
+		dimension1.disabled = 0
+		dimension1.save()
 
 def disable_dimension():
 	dimension1 = frappe.get_doc("Accounting Dimension", "Department")
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/__init__.py b/erpnext/accounts/doctype/accounting_dimension_filter/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/__init__.py
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js
new file mode 100644
index 0000000..74b7b51
--- /dev/null
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.js
@@ -0,0 +1,82 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Accounting Dimension Filter', {
+	refresh: function(frm, cdt, cdn) {
+		if (frm.doc.accounting_dimension) {
+			frm.set_df_property('dimensions', 'label', frm.doc.accounting_dimension, cdn, 'dimension_value');
+		}
+
+		let help_content =
+			`<table class="table table-bordered" style="background-color: #f9f9f9;">
+				<tr><td>
+					<p>
+						<i class="fa fa-hand-right"></i>
+						{{__('Note: On checking Is Mandatory the accounting dimension will become mandatory against that specific account for all accounting transactions')}}
+					</p>
+				</td></tr>
+			</table>`;
+
+		frm.set_df_property('dimension_filter_help', 'options', help_content);
+	},
+	onload: function(frm) {
+		frm.set_query('applicable_on_account', 'accounts', function() {
+			return {
+				filters: {
+					'company': frm.doc.company
+				}
+			};
+		});
+
+		frappe.db.get_list('Accounting Dimension',
+			{fields: ['document_type']}).then((res) => {
+			let options = ['Cost Center', 'Project'];
+
+			res.forEach((dimension) => {
+				options.push(dimension.document_type);
+			});
+
+			frm.set_df_property('accounting_dimension', 'options', options);
+		});
+
+		frm.trigger('setup_filters');
+	},
+
+	setup_filters: function(frm) {
+		let filters = {};
+
+		if (frm.doc.accounting_dimension) {
+			frappe.model.with_doctype(frm.doc.accounting_dimension, function() {
+				if (frappe.model.is_tree(frm.doc.accounting_dimension)) {
+					filters['is_group'] = 0;
+				}
+
+				if (frappe.meta.has_field(frm.doc.accounting_dimension, 'company')) {
+					filters['company'] = frm.doc.company;
+				}
+
+				frm.set_query('dimension_value', 'dimensions', function() {
+					return {
+						filters: filters
+					};
+				});
+			});
+		}
+	},
+
+	accounting_dimension: function(frm) {
+		frm.clear_table("dimensions");
+		let row = frm.add_child("dimensions");
+		row.accounting_dimension = frm.doc.accounting_dimension;
+		frm.refresh_field("dimensions");
+		frm.trigger('setup_filters');
+	},
+});
+
+frappe.ui.form.on('Allowed Dimension', {
+	dimensions_add: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		row.accounting_dimension = frm.doc.accounting_dimension;
+		frm.refresh_field("dimensions");
+	}
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json
new file mode 100644
index 0000000..c0327ad
--- /dev/null
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json
@@ -0,0 +1,134 @@
+{
+ "actions": [],
+ "autoname": "format:{accounting_dimension}-{#####}",
+ "creation": "2020-11-08 18:28:11.906146",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "accounting_dimension",
+  "disabled",
+  "column_break_2",
+  "company",
+  "allow_or_restrict",
+  "section_break_4",
+  "accounts",
+  "column_break_6",
+  "dimensions",
+  "section_break_10",
+  "dimension_filter_help"
+ ],
+ "fields": [
+  {
+   "fieldname": "accounting_dimension",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Accounting Dimension",
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "section_break_4",
+   "fieldtype": "Section Break",
+   "hide_border": 1,
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "column_break_6",
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "allow_or_restrict",
+   "fieldtype": "Select",
+   "label": "Allow Or Restrict Dimension",
+   "options": "Allow\nRestrict",
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "accounts",
+   "fieldtype": "Table",
+   "label": "Applicable On Account",
+   "options": "Applicable On Account",
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "depends_on": "eval:doc.accounting_dimension",
+   "fieldname": "dimensions",
+   "fieldtype": "Table",
+   "label": "Applicable Dimension",
+   "options": "Allowed Dimension",
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "disabled",
+   "fieldtype": "Check",
+   "label": "Disabled",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "dimension_filter_help",
+   "fieldtype": "HTML",
+   "label": "Dimension Filter Help",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "section_break_10",
+   "fieldtype": "Section Break",
+   "show_days": 1,
+   "show_seconds": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-12-16 15:27:23.659285",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Accounting Dimension Filter",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
new file mode 100644
index 0000000..6aef9ca
--- /dev/null
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
@@ -0,0 +1,61 @@
+# -*- 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 import _, scrub
+from frappe.model.document import Document
+
+class AccountingDimensionFilter(Document):
+	def validate(self):
+		self.validate_applicable_accounts()
+
+	def validate_applicable_accounts(self):
+		accounts = frappe.db.sql(
+			"""
+				SELECT a.applicable_on_account as account
+				FROM `tabApplicable On Account` a, `tabAccounting Dimension Filter` d
+				WHERE d.name = a.parent
+				and d.name != %s
+				and d.accounting_dimension = %s
+			""", (self.name, self.accounting_dimension), as_dict=1)
+
+		account_list = [d.account for d in accounts]
+
+		for account in self.get('accounts'):
+			if account.applicable_on_account in account_list:
+				frappe.throw(_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
+					account.idx, frappe.bold(account.applicable_on_account), frappe.bold(self.accounting_dimension)))
+
+def get_dimension_filter_map():
+	filters = frappe.db.sql("""
+		SELECT
+			a.applicable_on_account, d.dimension_value, p.accounting_dimension,
+			p.allow_or_restrict, a.is_mandatory
+		FROM
+			`tabApplicable On Account` a, `tabAllowed Dimension` d,
+			`tabAccounting Dimension Filter` p
+		WHERE
+			p.name = a.parent
+			AND p.disabled = 0
+			AND p.name = d.parent
+	""", as_dict=1)
+
+	dimension_filter_map = {}
+
+	for f in filters:
+		f.fieldname = scrub(f.accounting_dimension)
+
+		build_map(dimension_filter_map, f.fieldname, f.applicable_on_account, f.dimension_value,
+			f.allow_or_restrict, f.is_mandatory)
+
+	return dimension_filter_map
+
+def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
+	map_object.setdefault((dimension, account), {
+		'allowed_dimensions': [],
+		'is_mandatory': is_mandatory,
+		'allow_or_restrict': allow_or_restrict
+	})
+	map_object[(dimension, account)]['allowed_dimensions'].append(filter_value)
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
new file mode 100644
index 0000000..7877abd
--- /dev/null
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension
+from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
+
+class TestAccountingDimensionFilter(unittest.TestCase):
+	def setUp(self):
+		create_dimension()
+		create_accounting_dimension_filter()
+		self.invoice_list = []
+
+	def test_allowed_dimension_validation(self):
+		si = create_sales_invoice(do_not_save=1)
+		si.items[0].cost_center = 'Main - _TC'
+		si.department = 'Accounts - _TC'
+		si.location = 'Block 1'
+		si.save()
+
+		self.assertRaises(InvalidAccountDimensionError, si.submit)
+		self.invoice_list.append(si)
+
+	def test_mandatory_dimension_validation(self):
+		si = create_sales_invoice(do_not_save=1)
+		si.department = ''
+		si.location = 'Block 1'
+
+		# Test with no department for Sales Account
+		si.items[0].department = ''
+		si.items[0].cost_center = '_Test Cost Center 2 - _TC'
+		si.save()
+
+		self.assertRaises(MandatoryAccountDimensionError, si.submit)
+		self.invoice_list.append(si)
+
+	def tearDown(self):
+		disable_dimension_filter()
+		disable_dimension()
+
+		for si in self.invoice_list:
+			si.load_from_db()
+			if si.docstatus == 1:
+				si.cancel()
+
+def create_accounting_dimension_filter():
+	if not frappe.db.get_value('Accounting Dimension Filter',
+		{'accounting_dimension': 'Cost Center'}):
+		frappe.get_doc({
+			'doctype': 'Accounting Dimension Filter',
+			'accounting_dimension': 'Cost Center',
+			'allow_or_restrict': 'Allow',
+			'company': '_Test Company',
+			'accounts': [{
+				'applicable_on_account': 'Sales - _TC',
+			}],
+			'dimensions': [{
+				'accounting_dimension': 'Cost Center',
+				'dimension_value': '_Test Cost Center 2 - _TC'
+			}]
+		}).insert()
+	else:
+		doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
+		doc.disabled = 0
+		doc.save()
+
+	if not frappe.db.get_value('Accounting Dimension Filter',
+		{'accounting_dimension': 'Department'}):
+		frappe.get_doc({
+			'doctype': 'Accounting Dimension Filter',
+			'accounting_dimension': 'Department',
+			'allow_or_restrict': 'Allow',
+			'company': '_Test Company',
+			'accounts': [{
+				'applicable_on_account': 'Sales - _TC',
+				'is_mandatory': 1
+			}],
+			'dimensions': [{
+				'accounting_dimension': 'Department',
+				'dimension_value': 'Accounts - _TC'
+			}]
+		}).insert()
+	else:
+		doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
+		doc.disabled = 0
+		doc.save()
+
+def disable_dimension_filter():
+	doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
+	doc.disabled = 1
+	doc.save()
+
+	doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
+	doc.disabled = 1
+	doc.save()
diff --git a/erpnext/accounts/doctype/allowed_dimension/__init__.py b/erpnext/accounts/doctype/allowed_dimension/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/allowed_dimension/__init__.py
diff --git a/erpnext/accounts/doctype/allowed_dimension/allowed_dimension.json b/erpnext/accounts/doctype/allowed_dimension/allowed_dimension.json
new file mode 100644
index 0000000..7fe2a3c
--- /dev/null
+++ b/erpnext/accounts/doctype/allowed_dimension/allowed_dimension.json
@@ -0,0 +1,43 @@
+{
+ "actions": [],
+ "creation": "2020-11-08 18:22:36.001131",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "accounting_dimension",
+  "dimension_value"
+ ],
+ "fields": [
+  {
+   "fieldname": "accounting_dimension",
+   "fieldtype": "Link",
+   "label": "Accounting Dimension",
+   "options": "DocType",
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "dimension_value",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "options": "accounting_dimension",
+   "show_days": 1,
+   "show_seconds": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-11-23 09:56:19.744200",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Allowed Dimension",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/allowed_dimension/allowed_dimension.py b/erpnext/accounts/doctype/allowed_dimension/allowed_dimension.py
new file mode 100644
index 0000000..c2afc1a
--- /dev/null
+++ b/erpnext/accounts/doctype/allowed_dimension/allowed_dimension.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 AllowedDimension(Document):
+	pass
diff --git a/erpnext/accounts/doctype/applicable_on_account/__init__.py b/erpnext/accounts/doctype/applicable_on_account/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/applicable_on_account/__init__.py
diff --git a/erpnext/accounts/doctype/applicable_on_account/applicable_on_account.json b/erpnext/accounts/doctype/applicable_on_account/applicable_on_account.json
new file mode 100644
index 0000000..95e98d0
--- /dev/null
+++ b/erpnext/accounts/doctype/applicable_on_account/applicable_on_account.json
@@ -0,0 +1,46 @@
+{
+ "actions": [],
+ "creation": "2020-11-08 18:20:00.944449",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "applicable_on_account",
+  "is_mandatory"
+ ],
+ "fields": [
+  {
+   "fieldname": "applicable_on_account",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Accounts",
+   "options": "Account",
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "columns": 2,
+   "default": "0",
+   "fieldname": "is_mandatory",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "Is Mandatory",
+   "show_days": 1,
+   "show_seconds": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-11-22 19:55:13.324136",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Applicable On Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/applicable_on_account/applicable_on_account.py b/erpnext/accounts/doctype/applicable_on_account/applicable_on_account.py
new file mode 100644
index 0000000..0fccaf3
--- /dev/null
+++ b/erpnext/accounts/doctype/applicable_on_account/applicable_on_account.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 ApplicableOnAccount(Document):
+	pass
diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index de9498e..49b2b18 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -1,5 +1,6 @@
 // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
+frappe.provide('erpnext.integrations');
 
 frappe.ui.form.on('Bank', {
 	onload: function(frm) {
@@ -20,7 +21,12 @@
 			frm.set_df_property('address_and_contact', 'hidden', 0);
 			frappe.contacts.render_address_and_contact(frm);
 		}
-	},
+		if (frm.doc.plaid_access_token) {
+			frm.add_custom_button(__('Refresh Plaid Link'), () => {
+				new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token);
+			});
+		}
+	}
 });
 
 
@@ -40,4 +46,79 @@
 		frm.doc.name).options = options;
 
 	frm.fields_dict.bank_transaction_mapping.grid.refresh();
-};
\ No newline at end of file
+};
+
+erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
+	constructor(access_token) {
+		this.access_token = access_token;
+		this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
+		this.init_config();
+	}
+
+	async init_config() {
+		this.plaid_env = await frappe.db.get_single_value('Plaid Settings', 'plaid_env');
+		this.token = await this.get_link_token_for_update();
+		this.init_plaid();
+	}
+
+	async get_link_token_for_update() {
+		const token = frappe.xcall(
+			'erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_link_token_for_update',
+			{ access_token: this.access_token }
+		)
+		if (!token) {
+			frappe.throw(__('Cannot retrieve link token for update. Check Error Log for more information'));
+		}
+		return token;
+	}
+
+	init_plaid() {
+		const me = this;
+		me.loadScript(me.plaidUrl)
+			.then(() => {
+				me.onScriptLoaded(me);
+			})
+			.then(() => {
+				if (me.linkHandler) {
+					me.linkHandler.open();
+				}
+			})
+			.catch((error) => {
+				me.onScriptError(error);
+			});
+	}
+
+	loadScript(src) {
+		return new Promise(function (resolve, reject) {
+			if (document.querySelector("script[src='" + src + "']")) {
+				resolve();
+				return;
+			}
+			const el = document.createElement('script');
+			el.type = 'text/javascript';
+			el.async = true;
+			el.src = src;
+			el.addEventListener('load', resolve);
+			el.addEventListener('error', reject);
+			el.addEventListener('abort', reject);
+			document.head.appendChild(el);
+		});
+	}
+
+	onScriptLoaded(me) {
+		me.linkHandler = Plaid.create({
+			env: me.plaid_env,
+			token: me.token,
+			onSuccess: me.plaid_success
+		});
+	}
+
+	onScriptError(error) {
+		frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
+		console.log(error);
+	}
+
+	plaid_success(token, response) {
+		frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
+	}
+};
diff --git a/erpnext/accounts/doctype/budget/budget.js b/erpnext/accounts/doctype/budget/budget.js
index cadf1e7..e162e32 100644
--- a/erpnext/accounts/doctype/budget/budget.js
+++ b/erpnext/accounts/doctype/budget/budget.js
@@ -1,24 +1,9 @@
 // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
+frappe.provide("erpnext.accounts.dimensions");
 
 frappe.ui.form.on('Budget', {
 	onload: function(frm) {
-		frm.set_query("cost_center", function() {
-			return {
-				filters: {
-					company: frm.doc.company
-				}
-			}
-		})
-
-		frm.set_query("project", function() {
-			return {
-				filters: {
-					company: frm.doc.company
-				}
-			}
-		})
-		
 		frm.set_query("account", "accounts", function() {
 			return {
 				filters: {
@@ -26,16 +11,18 @@
 					report_type: "Profit and Loss",
 					is_group: 0
 				}
-			}
-		})
-		
+			};
+		});
+
 		frm.set_query("monthly_distribution", function() {
 			return {
 				filters: {
 					fiscal_year: frm.doc.fiscal_year
 				}
-			}
-		})
+			};
+		});
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 288111b..a749f0e 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -11,8 +11,10 @@
 from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled
 from erpnext.accounts.utils import get_account_currency
 from erpnext.accounts.utils import get_fiscal_year
-from erpnext.exceptions import InvalidAccountCurrency
+from erpnext.exceptions import InvalidAccountCurrency, InvalidAccountDimensionError, MandatoryAccountDimensionError
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
+from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
+from six import iteritems
 
 exclude_from_linked_with = True
 class GLEntry(Document):
@@ -39,6 +41,7 @@
 		if not from_repost:
 			self.validate_account_details(adv_adj)
 			self.validate_dimensions_for_pl_and_bs()
+			self.validate_allowed_dimensions()
 
 		validate_frozen_account(self.account, adv_adj)
 		validate_balance_type(self.account, adv_adj)
@@ -76,11 +79,9 @@
 					.format(self.voucher_type, self.voucher_no, self.account))
 
 	def validate_dimensions_for_pl_and_bs(self):
-
 		account_type = frappe.db.get_value("Account", self.account, "report_type")
 
 		for dimension in get_checks_for_pl_and_bs_accounts():
-
 			if account_type == "Profit and Loss" \
 				and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
 				if not self.get(dimension.fieldname):
@@ -93,6 +94,25 @@
 					frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.")
 						.format(dimension.label, self.account))
 
+	def validate_allowed_dimensions(self):
+		dimension_filter_map = get_dimension_filter_map()
+		for key, value in iteritems(dimension_filter_map):
+			dimension = key[0]
+			account = key[1]
+
+			if self.account == account:
+				if value['is_mandatory'] and not self.get(dimension):
+					frappe.throw(_("{0} is mandatory for account {1}").format(
+						frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), MandatoryAccountDimensionError)
+
+				if value['allow_or_restrict'] == 'Allow':
+					if self.get(dimension) and self.get(dimension) not in value['allowed_dimensions']:
+						frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
+							frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
+				else:
+					if self.get(dimension) and self.get(dimension) in value['allowed_dimensions']:
+						frappe.throw(_("Invalid value {0} for {1} against account {2}").format(
+							frappe.bold(self.get(dimension)), frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)), InvalidAccountDimensionError)
 
 	def check_pl_account(self):
 		if self.is_opening=='Yes' and \
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index ff12967..37b03f3 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -120,6 +120,8 @@
 				}
 			}
 		});
+
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	voucher_type: function(frm){
@@ -197,6 +199,7 @@
 		this.load_defaults();
 		this.setup_queries();
 		this.setup_balance_formatter();
+		erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
 	},
 
 	onload_post_render: function() {
@@ -222,15 +225,6 @@
 			return erpnext.journal_entry.account_query(me.frm);
 		});
 
-		me.frm.set_query("cost_center", "accounts", function(doc, cdt, cdn) {
-			return {
-				filters: {
-					company: me.frm.doc.company,
-					is_group: 0
-				}
-			};
-		});
-
 		me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
 			const row = locals[cdt][cdn];
 
@@ -406,6 +400,8 @@
 			}
 		}
 		cur_frm.cscript.update_totals(doc);
+
+		erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'accounts');
 	},
 
 });
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.js b/erpnext/accounts/doctype/loyalty_program/loyalty_program.js
index 524a671..f90f867 100644
--- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.js
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.js
@@ -1,6 +1,8 @@
 // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
+frappe.provide("erpnext.accounts.dimensions");
+
 frappe.ui.form.on('Loyalty Program', {
 	setup: function(frm) {
 		var help_content =
@@ -46,20 +48,17 @@
 			};
 		});
 
-		frm.set_query("cost_center", function() {
-			return {
-				filters: {
-					company: frm.doc.company
-				}
-			};
-		});
-
 		frm.set_value("company", frappe.defaults.get_user_default("Company"));
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	refresh: function(frm) {
 		if (frm.doc.loyalty_program_type === "Single Tier Program" && frm.doc.collection_rules.length > 1) {
 			frappe.throw(__("Please select the Multiple Tier Program type for more than one collection rules."));
 		}
+	},
+
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	}
 });
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
index 3ce5701..c087980 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js
@@ -36,6 +36,8 @@
 			frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
 			frm.page.set_indicator(__('In Progress'), 'orange');
 		});
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	refresh: function(frm) {
@@ -100,6 +102,7 @@
 				}
 			})
 		}
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	invoice_type: function(frm) {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 9bdd26b..f5c488d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -1,6 +1,7 @@
 // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 {% include "erpnext/public/js/controllers/accounts.js" %}
+frappe.provide("erpnext.accounts.dimensions");
 
 frappe.ui.form.on('Payment Entry', {
 	onload: function(frm) {
@@ -8,6 +9,8 @@
 			if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
 			if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
 		}
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	setup: function(frm) {
@@ -88,15 +91,6 @@
 			}
 		});
 
-		frm.set_query("cost_center", "deductions", function() {
-			return {
-				filters: {
-					"is_group": 0,
-					"company": frm.doc.company
-				}
-			}
-		});
-
 		frm.set_query("reference_doctype", "references", function() {
 			if (frm.doc.party_type=="Customer") {
 				var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
@@ -167,6 +161,7 @@
 	company: function(frm) {
 		frm.events.hide_unhide_fields(frm);
 		frm.events.set_dynamic_labels(frm);
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	contact_person: function(frm) {
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index 7dd5b01..a74fa06 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -8,7 +8,7 @@
 from erpnext.accounts.utils import get_account_currency
 from erpnext.controllers.accounts_controller import AccountsController
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
-	get_dimension_filters)
+	get_dimensions)
 
 class PeriodClosingVoucher(AccountsController):
 	def validate(self):
@@ -58,7 +58,7 @@
 		for dimension in accounting_dimensions:
 			dimension_fields.append('t1.{0}'.format(dimension))
 
-		dimension_filters, default_dimensions = get_dimension_filters()
+		dimension_filters, default_dimensions = get_dimensions()
 
 		pl_accounts = self.get_pl_balances(dimension_fields)
 
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js
index 7f4f755..efdeb1a 100755
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.js
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js
@@ -57,6 +57,8 @@
 				}
 			};
 		});
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	refresh: function(frm) {
@@ -67,6 +69,7 @@
 
 	company: function(frm) {
 		frm.trigger("toggle_display_account_head");
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	toggle_display_account_head: function(frm) {
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 3863768..4a952a3 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -26,6 +26,11 @@
 			};
 		});
 	},
+
+	company: function() {
+		erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
+	},
+
 	onload: function() {
 		this._super();
 
@@ -41,6 +46,8 @@
 		if (this.frm.doc.supplier && this.frm.doc.__islocal) {
 			this.frm.trigger('supplier');
 		}
+
+		erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
 	},
 
 	refresh: function(doc) {
@@ -511,15 +518,6 @@
 				}
 			}
 		}
-
-		frm.set_query("cost_center", function() {
-			return {
-				filters: {
-					company: frm.doc.company,
-					is_group: 0
-				}
-			};
-		});
 	},
 
 	onload: function(frm) {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 89b716c..72199a9 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -5,14 +5,17 @@
 cur_frm.pformat.print_heading = 'Invoice';
 
 {% include 'erpnext/selling/sales_common.js' %};
-
-
 frappe.provide("erpnext.accounts");
+
+
 erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
 	setup: function(doc) {
 		this.setup_posting_date_time_check();
 		this._super(doc);
 	},
+	company: function() {
+		erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
+	},
 	onload: function() {
 		var me = this;
 		this._super();
@@ -33,6 +36,7 @@
 			me.frm.refresh_fields();
 		}
 		erpnext.queries.setup_warehouse_query(this.frm);
+		erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
 	},
 
 	refresh: function(doc, dt, dn) {
@@ -571,15 +575,6 @@
 			};
 		});
 
-		frm.set_query("cost_center", function() {
-			return {
-				filters: {
-					company: frm.doc.company,
-					is_group: 0
-				}
-			};
-		});
-
 		frm.set_query("unrealized_profit_loss_account", function() {
 			return {
 				filters: {
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 5435d3b..3a6dbeb 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1861,23 +1861,6 @@
 	def test_einvoice_json(self):
 		from erpnext.regional.india.e_invoice.utils import make_einvoice
 
-		customer_gstin = '27AACCM7806M1Z3'
-		customer_gstin_dtls = {
-			'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
-			'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
-			'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
-		}
-		company_gstin = '27AAECE4835E1ZR'
-		company_gstin_dtls = {
-			'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
-			'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
-			'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
-		}
-		# set cache gstin details to avoid fetching details which will require connection to GSP servers
-		frappe.local.gstin_cache = {}
-		frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
-		frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
-
 		si = make_sales_invoice_for_ewaybill()
 		si.naming_series = 'INV-2020-.#####'
 		si.items = []
@@ -1930,12 +1913,12 @@
 		self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
 		self.assertEqual(value_details['IgstVal'], total_item_igst_value)
 
-		self.assertEqual(
-			value_details['TotInvVal'],
-			value_details['AssVal'] + value_details['CgstVal']
-			+ value_details['SgstVal'] + value_details['IgstVal']
+		calculated_invoice_value = \
+			value_details['AssVal'] + value_details['CgstVal'] \
+			+ value_details['SgstVal'] + value_details['IgstVal'] \
 			+ value_details['OthChrg'] - value_details['Discount']
-		)
+
+		self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
 
 		self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
 		self.assertTrue(einvoice['EwbDtls'])
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.js b/erpnext/accounts/doctype/shipping_rule/shipping_rule.js
index d0904ee..8e4b806 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.js
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.js
@@ -1,16 +1,18 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-frappe.ui.form.on('Shipping Rule', {
-	refresh: function(frm) {
-		frm.set_query("cost_center", function() {
-			return {
-				filters: {
-					company: frm.doc.company
-				}
-			}
-		})
+frappe.provide('erpnext.accounts.dimensions');
 
+frappe.ui.form.on('Shipping Rule', {
+	onload: function(frm) {
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+	},
+
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+	},
+
+	refresh: function(frm) {
 		frm.set_query("account", function() {
 			return {
 				filters: {
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index b2318a2..6f1bb28 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -2,6 +2,7 @@
 // For license information, please see license.txt
 
 frappe.provide("erpnext.asset");
+frappe.provide("erpnext.accounts.dimensions");
 
 frappe.ui.form.on('Asset', {
 	onload: function(frm) {
@@ -32,13 +33,11 @@
 			};
 		});
 
-		frm.set_query("cost_center", function() {
-			return {
-				"filters": {
-					"company": frm.doc.company,
-				}
-			};
-		});
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+	},
+
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	setup: function(frm) {
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
index a6e6974..79c8861 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
@@ -1,6 +1,8 @@
 // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
+frappe.provide("erpnext.accounts.dimensions");
+
 frappe.ui.form.on('Asset Value Adjustment', {
 	setup: function(frm) {
 		frm.add_fetch('company', 'cost_center', 'cost_center');
@@ -13,11 +15,19 @@
 			}
 		});
 	},
+
 	onload: function(frm) {
 		if(frm.is_new() && frm.doc.asset) {
 			frm.trigger("set_current_asset_value");
 		}
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
+
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+	},
+
 	asset: function(frm) {
 		frm.trigger("set_current_asset_value");
 	},
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 38532d1..0b98274 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -2,7 +2,7 @@
 // License: GNU General Public License v3. See license.txt
 
 frappe.provide("erpnext.buying");
-
+frappe.provide("erpnext.accounts.dimensions");
 {% include 'erpnext/public/js/controllers/buying.js' %};
 
 frappe.ui.form.on("Purchase Order", {
@@ -30,6 +30,10 @@
 
 	},
 
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+	},
+
 	onload: function(frm) {
 		set_schedule_date(frm);
 		if (!frm.doc.transaction_date){
@@ -39,6 +43,8 @@
 		erpnext.queries.setup_queries(frm, "Warehouse", function() {
 			return erpnext.queries.warehouse(frm.doc);
 		});
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	}
 });
 
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 6edc020..4dee375 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -336,7 +336,7 @@
 				raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
 
 				consumed_qty = raw_material_data.get('qty', 0)
-				consumed_serial_nos = raw_material_data.get('serial_nos', '')
+				consumed_serial_nos = raw_material_data.get('serial_no', '')
 				consumed_batch_nos = raw_material_data.get('batch_nos', '')
 
 				transferred_qty = raw_material.qty
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 8fe3816..e3aac9a 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -493,6 +493,41 @@
 				'company': filters.get("company", "")
 			})
 
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters):
+	from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import get_dimension_filter_map
+	dimension_filters = get_dimension_filter_map()
+	dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account')))
+	query_filters = []
+
+	meta = frappe.get_meta(doctype)
+	if meta.is_tree:
+		query_filters.append(['is_group', '=', 0])
+
+	if meta.has_field('company'):
+		query_filters.append(['company', '=', filters.get('company')])
+
+	if txt:
+		query_filters.append([searchfield, 'LIKE', "%%%s%%" % txt])
+
+	if dimension_filters:
+		if dimension_filters['allow_or_restrict'] == 'Allow':
+			query_selector = 'in'
+		else:
+			query_selector = 'not in'
+
+		if len(dimension_filters['allowed_dimensions']) == 1:
+			dimensions = tuple(dimension_filters['allowed_dimensions'] * 2)
+		else:
+			dimensions = tuple(dimension_filters['allowed_dimensions'])
+
+		query_filters.append(['name', query_selector, dimensions])
+
+	output = frappe.get_all(doctype, filters=query_filters)
+	result = [d.name for d in output]
+
+	return [(d,) for d in set(result)]
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.js b/erpnext/education/doctype/fee_schedule/fee_schedule.js
index 75dd446..65b5fa6 100644
--- a/erpnext/education/doctype/fee_schedule/fee_schedule.js
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule.js
@@ -1,6 +1,7 @@
 // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
+frappe.provide("erpnext.accounts.dimensions");
 frappe.ui.form.on('Fee Schedule', {
 	setup: function(frm) {
 		frm.add_fetch('fee_structure', 'receivable_account', 'receivable_account');
@@ -8,6 +9,10 @@
 		frm.add_fetch('fee_structure', 'cost_center', 'cost_center');
 	},
 
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+	},
+
 	onload: function(frm) {
 		frm.set_query('receivable_account', function(doc) {
 			return {
@@ -50,6 +55,8 @@
 				}
 			}
 		});
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.js b/erpnext/education/doctype/fee_structure/fee_structure.js
index b331c6d..310c410 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.js
+++ b/erpnext/education/doctype/fee_structure/fee_structure.js
@@ -1,6 +1,8 @@
 // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
+frappe.provide("erpnext.accounts.dimensions");
+
 frappe.ui.form.on('Fee Structure', {
 	setup: function(frm) {
 		frm.add_fetch('company', 'default_receivable_account', 'receivable_account');
@@ -8,6 +10,10 @@
 		frm.add_fetch('company', 'cost_center', 'cost_center');
 	},
 
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+	},
+
 	onload: function(frm) {
 		frm.set_query('academic_term', function() {
 			return {
@@ -35,6 +41,8 @@
 				}
 			};
 		});
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/education/doctype/fees/fees.js b/erpnext/education/doctype/fees/fees.js
index aaf42b4..ac66acd 100644
--- a/erpnext/education/doctype/fees/fees.js
+++ b/erpnext/education/doctype/fees/fees.js
@@ -1,6 +1,7 @@
 // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
+frappe.provide("erpnext.accounts.dimensions");
 
 frappe.ui.form.on("Fees", {
 	setup: function(frm) {
@@ -9,15 +10,19 @@
 		frm.add_fetch("fee_structure", "cost_center", "cost_center");
 	},
 
-	onload: function(frm){
-		frm.set_query("academic_term",function(){
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+	},
+
+	onload: function(frm) {
+		frm.set_query("academic_term", function() {
 			return{
-				"filters":{
+				"filters": {
 					"academic_year": (frm.doc.academic_year)
 				}
 			};
 		});
-		frm.set_query("fee_structure",function(){
+		frm.set_query("fee_structure", function() {
 			return{
 				"filters":{
 					"academic_year": (frm.doc.academic_year)
@@ -45,6 +50,8 @@
 		if (!frm.doc.posting_date) {
 			frm.doc.posting_date = frappe.datetime.get_today();
 		}
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
index 8d4b510..66d0e5f 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py
@@ -29,14 +29,11 @@
 		response = self.client.Item.public_token.exchange(public_token)
 		access_token = response["access_token"]
 		return access_token
-
-	def get_link_token(self):
+	
+	def get_token_request(self, update_mode=False):
 		country_codes = ["US", "CA", "FR", "IE", "NL", "ES", "GB"] if self.settings.enable_european_access else ["US", "CA"]
-		token_request = {
+		args = {
 			"client_name": self.client_name,
-			"client_id": self.settings.plaid_client_id,
-			"secret": self.settings.plaid_secret,
-			"products": self.products,
 			# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
 			"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
 			"country_codes": country_codes,
@@ -45,6 +42,20 @@
 			}
 		}
 
+		if update_mode:
+			args["access_token"] = self.access_token
+		else:
+			args.update({
+				"client_id": self.settings.plaid_client_id,
+				"secret": self.settings.plaid_secret,
+				"products": self.products,
+			})
+		
+		return args
+
+	def get_link_token(self, update_mode=False):
+		token_request = self.get_token_request(update_mode)
+
 		try:
 			response = self.client.LinkToken.create(token_request)
 		except InvalidRequestError:
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
index 22a4004..bbc2ca8 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js
@@ -12,9 +12,25 @@
 
 	refresh: function (frm) {
 		if (frm.doc.enabled) {
-			frm.add_custom_button('Link a new bank account', () => {
+			frm.add_custom_button(__('Link a new bank account'), () => {
 				new erpnext.integrations.plaidLink(frm);
 			});
+
+			frm.add_custom_button(__("Sync Now"), () => {
+				frappe.call({
+					method: "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.enqueue_synchronization",
+					freeze: true,
+					callback: () => {
+						let bank_transaction_link = '<a href="#List/Bank Transaction">Bank Transaction</a>';
+
+						frappe.msgprint({
+							title: __("Sync Started"),
+							message: __("The sync has started in the background, please check the {0} list for new records.", [bank_transaction_link]),
+							alert: 1
+						});
+					}
+				});
+			}).addClass("btn-primary");
 		}
 	}
 });
@@ -30,10 +46,18 @@
 		this.product = ["auth", "transactions"];
 		this.plaid_env = this.frm.doc.plaid_env;
 		this.client_name = frappe.boot.sitename;
-		this.token = await this.frm.call("get_link_token").then(resp => resp.message);
+		this.token = await this.get_link_token();
 		this.init_plaid();
 	}
 
+	async get_link_token() {
+		const token = await this.frm.call("get_link_token").then(resp => resp.message);
+		if (!token) {
+			frappe.throw(__('Cannot retrieve link token. Check Error Log for more information'));
+		}
+		return token;
+	}
+
 	init_plaid() {
 		const me = this;
 		me.loadScript(me.plaidUrl)
@@ -78,8 +102,8 @@
 	}
 
 	onScriptError(error) {
-		frappe.msgprint("There was an issue connecting to Plaid's authentication server");
-		frappe.msgprint(error);
+		frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
+		console.log(error);
 	}
 
 	plaid_success(token, response) {
@@ -107,4 +131,4 @@
 			});
 		}, __("Select a company"), __("Continue"));
 	}
-};
+};
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index e535e81..70c7f3f 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -166,7 +166,6 @@
 		related_bank = frappe.db.get_values("Bank Account", bank_account, ["bank", "integration_id"], as_dict=True)
 		access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token")
 		account_id = related_bank[0].integration_id
-
 	else:
 		access_token = frappe.db.get_value("Bank", bank, "plaid_access_token")
 		account_id = None
@@ -228,13 +227,23 @@
 
 def automatic_synchronization():
 	settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
-
 	if settings.enabled == 1 and settings.automatic_sync == 1:
-		plaid_accounts = frappe.get_all("Bank Account", filters={"integration_id": ["!=", ""]}, fields=["name", "bank"])
+		enqueue_synchronization()
 
-		for plaid_account in plaid_accounts:
-			frappe.enqueue(
-				"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
-				bank=plaid_account.bank,
-				bank_account=plaid_account.name
-			)
+@frappe.whitelist()
+def enqueue_synchronization():
+	plaid_accounts = frappe.get_all("Bank Account",
+		filters={"integration_id": ["!=", ""]},
+		fields=["name", "bank"])
+
+	for plaid_account in plaid_accounts:
+		frappe.enqueue(
+			"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions",
+			bank=plaid_account.bank,
+			bank_account=plaid_account.name
+		)
+
+@frappe.whitelist()
+def get_link_token_for_update(access_token):
+	plaid = PlaidConnector(access_token)
+	return plaid.get_link_token(update_mode=True)
diff --git a/erpnext/exceptions.py b/erpnext/exceptions.py
index d92af5d..04291cd 100644
--- a/erpnext/exceptions.py
+++ b/erpnext/exceptions.py
@@ -6,3 +6,5 @@
 class InvalidAccountCurrency(frappe.ValidationError): pass
 class InvalidCurrency(frappe.ValidationError): pass
 class PartyDisabled(frappe.ValidationError):pass
+class InvalidAccountDimensionError(frappe.ValidationError): pass
+class MandatoryAccountDimensionError(frappe.ValidationError): pass
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 57b0b07..14377e1 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -346,7 +346,8 @@
 		"erpnext.selling.doctype.quotation.quotation.set_expired_status",
 		"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
 		"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
-		"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email"
+		"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
+		"erpnext.non_profit.doctype.membership.membership.set_expired_status"
 	],
 	"daily_long": [
 		"erpnext.setup.doctype.email_digest.email_digest.send",
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index 221300b..629341f 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -2,11 +2,21 @@
 // License: GNU General Public License v3. See license.txt
 
 frappe.provide("erpnext.hr");
+frappe.provide("erpnext.accounts.dimensions");
 
-erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
-	expense_type: function(doc, cdt, cdn) {
+frappe.ui.form.on('Expense Claim', {
+	onload: function(frm) {
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+	},
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+	},
+});
+
+frappe.ui.form.on('Expense Claim Detail', {
+	expense_type: function(frm, cdt, cdn) {
 		var d = locals[cdt][cdn];
-		if(!doc.company) {
+		if (!frm.doc.company) {
 			d.expense_type = "";
 			frappe.msgprint(__("Please set the Company"));
 			this.frm.refresh_fields();
@@ -20,7 +30,7 @@
 			method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account_and_cost_center",
 			args: {
 				"expense_claim_type": d.expense_type,
-				"company": doc.company
+				"company": frm.doc.company
 			},
 			callback: function(r) {
 				if (r.message) {
@@ -32,8 +42,6 @@
 	}
 });
 
-$.extend(cur_frm.cscript, new erpnext.hr.ExpenseClaimController({frm: cur_frm}));
-
 cur_frm.add_fetch('employee', 'company', 'company');
 cur_frm.add_fetch('employee','employee_name','employee_name');
 cur_frm.add_fetch('expense_type','description','description');
@@ -167,15 +175,6 @@
 			};
 		});
 
-		frm.set_query("cost_center", "expenses", function() {
-			return {
-				filters: {
-					"company": frm.doc.company,
-					"is_group": 0
-				}
-			};
-		});
-
 		frm.set_query("payable_account", function() {
 			return {
 				filters: {
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json
index c13548a..1360fd1 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.json
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.json
@@ -11,15 +11,24 @@
  "field_order": [
   "applicant_name",
   "email_id",
+  "phone_number",
+  "country",
   "status",
   "column_break_3",
   "job_title",
   "source",
   "source_name",
+  "applicant_rating",
   "section_break_6",
   "notes",
   "cover_letter",
-  "resume_attachment"
+  "resume_attachment",
+  "resume_link",
+  "section_break_16",
+  "currency",
+  "column_break_18",
+  "lower_range",
+  "upper_range"
  ],
  "fields": [
   {
@@ -91,12 +100,65 @@
    "fieldtype": "Data",
    "label": "Notes",
    "read_only": 1
+  },
+  {
+   "fieldname": "phone_number",
+   "fieldtype": "Data",
+   "label": "Phone Number",
+   "options": "Phone"
+  },
+  {
+   "fieldname": "country",
+   "fieldtype": "Link",
+   "label": "Country",
+   "options": "Country"
+  },
+  {
+   "fieldname": "resume_link",
+   "fieldtype": "Data",
+   "label": "Resume Link"
+  },
+  {
+   "fieldname": "applicant_rating",
+   "fieldtype": "Rating",
+   "in_list_view": 1,
+   "label": "Applicant Rating"
+  },
+  {
+   "fieldname": "section_break_16",
+   "fieldtype": "Section Break",
+   "label": "Salary Expectation"
+  },
+  {
+   "fieldname": "lower_range",
+   "fieldtype": "Currency",
+   "label": "Lower Range",
+   "options": "currency",
+   "precision": "0"
+  },
+  {
+   "fieldname": "upper_range",
+   "fieldtype": "Currency",
+   "label": "Upper Range",
+   "options": "currency",
+   "precision": "0"
+  },
+  {
+   "fieldname": "column_break_18",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency"
   }
  ],
  "icon": "fa fa-user",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-01-13 16:19:39.113330",
+ "modified": "2020-09-18 12:39:02.557563",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Job Applicant",
diff --git a/erpnext/hr/doctype/job_opening/job_opening.json b/erpnext/hr/doctype/job_opening/job_opening.json
index 4437e02..b8f6df6 100644
--- a/erpnext/hr/doctype/job_opening/job_opening.json
+++ b/erpnext/hr/doctype/job_opening/job_opening.json
@@ -1,456 +1,188 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "field:route", 
- "beta": 0, 
- "creation": "2013-01-15 16:13:36", 
- "custom": 0, 
- "description": "Description of a Job Opening", 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Document", 
- "editable_grid": 0, 
- "engine": "InnoDB", 
+ "actions": [],
+ "autoname": "field:route",
+ "creation": "2013-01-15 16:13:36",
+ "description": "Description of a Job Opening",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+  "job_title",
+  "company",
+  "status",
+  "column_break_5",
+  "designation",
+  "department",
+  "staffing_plan",
+  "planned_vacancies",
+  "section_break_6",
+  "publish",
+  "route",
+  "column_break_12",
+  "job_application_route",
+  "section_break_14",
+  "description",
+  "section_break_16",
+  "currency",
+  "lower_range",
+  "upper_range",
+  "column_break_20",
+  "publish_salary_range"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "job_title", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Job Title", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "job_title",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Job Title",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "company", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Company", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Company", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "status", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 1, 
-   "label": "Status", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Open\nClosed", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Status",
+   "options": "Open\nClosed"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_5", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "column_break_5",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "designation", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Designation", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Designation", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "designation",
+   "fieldtype": "Link",
+   "label": "Designation",
+   "options": "Designation",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "department", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Department", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Department", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "department",
+   "fieldtype": "Link",
+   "label": "Department",
+   "options": "Department"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "staffing_plan", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Staffing Plan", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Staffing Plan", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "staffing_plan",
+   "fieldtype": "Link",
+   "label": "Staffing Plan",
+   "options": "Staffing Plan",
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "staffing_plan", 
-   "fieldname": "planned_vacancies", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Planned number of Positions", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "staffing_plan",
+   "fieldname": "planned_vacancies",
+   "fieldtype": "Int",
+   "label": "Planned number of Positions",
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_6", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "publish", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Publish on website", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "default": "0",
+   "fieldname": "publish",
+   "fieldtype": "Check",
+   "label": "Publish on website"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "publish", 
-   "fieldname": "route", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Route", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
+   "depends_on": "publish",
+   "fieldname": "route",
+   "fieldtype": "Data",
+   "label": "Route",
    "unique": 1
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "Job profile, qualifications required etc.", 
-   "fieldname": "description", 
-   "fieldtype": "Text Editor", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Description", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
+   "description": "Job profile, qualifications required etc.",
+   "fieldname": "description",
+   "fieldtype": "Text Editor",
+   "in_list_view": 1,
+   "label": "Description"
+  },
+  {
+   "fieldname": "column_break_12",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_14",
+   "fieldtype": "Section Break"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "section_break_16",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency"
+  },
+  {
+   "fieldname": "lower_range",
+   "fieldtype": "Currency",
+   "label": "Lower Range",
+   "options": "currency",
+   "precision": "0"
+  },
+  {
+   "fieldname": "upper_range",
+   "fieldtype": "Currency",
+   "label": "Upper Range",
+   "options": "currency",
+   "precision": "0"
+  },
+  {
+   "fieldname": "column_break_20",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "publish",
+   "description": "Route to the custom Job Application Webform",
+   "fieldname": "job_application_route",
+   "fieldtype": "Data",
+   "label": "Job Application Route"
+  },
+  {
+   "default": "0",
+   "fieldname": "publish_salary_range",
+   "fieldtype": "Check",
+   "label": "Publish Salary Range"
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "icon": "fa fa-bookmark", 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2018-05-20 15:38:44.705823", 
- "modified_by": "Administrator", 
- "module": "HR", 
- "name": "Job Opening", 
- "owner": "Administrator", 
+ ],
+ "icon": "fa fa-bookmark",
+ "idx": 1,
+ "links": [],
+ "modified": "2020-09-18 11:23:29.488923",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Job Opening",
+ "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "HR User", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR User",
+   "share": 1,
    "write": 1
-  }, 
+  },
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 0, 
-   "delete": 0, 
-   "email": 0, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 0, 
-   "read": 1, 
-   "report": 0, 
-   "role": "Guest", 
-   "set_user_permissions": 0, 
-   "share": 0, 
-   "submit": 0, 
-   "write": 0
+   "read": 1,
+   "role": "Guest"
   }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_order": "ASC", 
- "track_changes": 0, 
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC"
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/job_opening/job_opening.py b/erpnext/hr/doctype/job_opening/job_opening.py
index 00883d7..1e89767 100644
--- a/erpnext/hr/doctype/job_opening/job_opening.py
+++ b/erpnext/hr/doctype/job_opening/job_opening.py
@@ -43,9 +43,8 @@
 			current_count = designation_counts['employee_count'] + designation_counts['job_openings']
 
 			if self.planned_vacancies <= current_count:
-				frappe.throw(_("Job Openings for designation {0} already open \
-					or hiring completed as per Staffing Plan {1}"
-					.format(self.designation, self.staffing_plan)))
+				frappe.throw(_("Job Openings for designation {0} already open or hiring completed as per Staffing Plan {1}").format(
+					self.designation, self.staffing_plan))
 
 	def get_context(self, context):
 		context.parents = [{'route': 'jobs', 'title': _('All Jobs') }]
@@ -56,7 +55,8 @@
 	context.get_list = get_job_openings
 
 def get_job_openings(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None):
-	fields = ['name', 'status', 'job_title', 'description']
+	fields = ['name', 'status', 'job_title', 'description', 'publish_salary_range',
+				'lower_range', 'upper_range', 'currency', 'job_application_route']
 
 	filters = filters or {}
 	filters.update({
diff --git a/erpnext/hr/doctype/job_opening/templates/job_opening_row.html b/erpnext/hr/doctype/job_opening/templates/job_opening_row.html
index 5da8cc8..c015101 100644
--- a/erpnext/hr/doctype/job_opening/templates/job_opening_row.html
+++ b/erpnext/hr/doctype/job_opening/templates/job_opening_row.html
@@ -1,9 +1,18 @@
 <div class="my-5">
 	<h3>{{ doc.job_title }}</h3>
 	<p>{{ doc.description }}</p>
+	{%- if doc.publish_salary_range -%} 
+	<p><b>{{_("Salary range per month")}}: </b>{{ frappe.format_value(frappe.utils.flt(doc.lower_range), currency=doc.currency) }} - {{ frappe.format_value(frappe.utils.flt(doc.upper_range), currency=doc.currency) }}</p>
+	{% endif %}
 	<div>
-		<a class="btn btn-primary"
-		href="/job_application?new=1&job_title={{ doc.name }}">
+		{%- if doc.job_application_route -%}
+		<a class='btn btn-primary' 
+		href='/{{doc.job_application_route}}?new=1&job_title={{ doc.name }}'>
 		{{ _("Apply Now") }}</a>
+		{% else %}
+		<a class='btn btn-primary' 
+		href='/job_application?new=1&job_title={{ doc.name }}'>
+		{{ _("Apply Now") }}</a>
+		{% endif %}
 	</div>
 </div>
diff --git a/erpnext/hr/web_form/job_application/job_application.json b/erpnext/hr/web_form/job_application/job_application.json
index f630570..512ba5c 100644
--- a/erpnext/hr/web_form/job_application/job_application.json
+++ b/erpnext/hr/web_form/job_application/job_application.json
@@ -1,86 +1,200 @@
 {
- "accept_payment": 0, 
- "allow_comments": 1, 
- "allow_delete": 0, 
- "allow_edit": 1, 
- "allow_incomplete": 0, 
- "allow_multiple": 1, 
- "allow_print": 0, 
- "amount": 0.0, 
- "amount_based_on_field": 0, 
- "creation": "2016-09-10 02:53:16.598314", 
- "doc_type": "Job Applicant", 
- "docstatus": 0, 
- "doctype": "Web Form", 
- "idx": 0, 
- "introduction_text": "", 
- "is_standard": 1, 
- "login_required": 0, 
- "max_attachment_size": 0, 
- "modified": "2016-12-20 00:21:44.081622", 
- "modified_by": "Administrator", 
- "module": "HR", 
- "name": "job-application", 
- "owner": "Administrator", 
- "published": 1, 
- "route": "job_application", 
- "show_sidebar": 1, 
- "sidebar_items": [], 
- "success_message": "Thank you for applying.", 
- "success_url": "/jobs", 
- "title": "Job Application", 
+ "accept_payment": 0,
+ "allow_comments": 1,
+ "allow_delete": 0,
+ "allow_edit": 1,
+ "allow_incomplete": 0,
+ "allow_multiple": 1,
+ "allow_print": 0,
+ "amount": 0.0,
+ "amount_based_on_field": 0,
+ "apply_document_permissions": 0,
+ "client_script": "frappe.web_form.on('resume_link', (field, value) => {\n    if (!frappe.utils.is_url(value)) {\n        frappe.msgprint(__('Resume link not valid'));\n    }\n});\n",
+ "creation": "2016-09-10 02:53:16.598314",
+ "doc_type": "Job Applicant",
+ "docstatus": 0,
+ "doctype": "Web Form",
+ "idx": 0,
+ "introduction_text": "",
+ "is_standard": 1,
+ "login_required": 0,
+ "max_attachment_size": 0,
+ "modified": "2020-10-07 19:27:17.143355",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "job-application",
+ "owner": "Administrator",
+ "published": 1,
+ "route": "job_application",
+ "route_to_success_link": 0,
+ "show_attachments": 0,
+ "show_in_grid": 0,
+ "show_sidebar": 1,
+ "sidebar_items": [],
+ "success_message": "Thank you for applying.",
+ "success_url": "/jobs",
+ "title": "Job Application",
  "web_form_fields": [
   {
-   "fieldname": "job_title", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "label": "Job Opening", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "options": "", 
-   "read_only": 1, 
-   "reqd": 0
-  }, 
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "job_title",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "label": "Job Opening",
+   "max_length": 0,
+   "max_value": 0,
+   "options": "",
+   "read_only": 1,
+   "reqd": 0,
+   "show_in_filter": 0
+  },
   {
-   "fieldname": "applicant_name", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "label": "Applicant Name", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 0, 
-   "reqd": 1
-  }, 
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "applicant_name",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "label": "Applicant Name",
+   "max_length": 0,
+   "max_value": 0,
+   "read_only": 0,
+   "reqd": 1,
+   "show_in_filter": 0
+  },
   {
-   "fieldname": "email_id", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "label": "Email Address", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "options": "Email", 
-   "read_only": 0, 
-   "reqd": 1
-  }, 
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "email_id",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "label": "Email Address",
+   "max_length": 0,
+   "max_value": 0,
+   "options": "Email",
+   "read_only": 0,
+   "reqd": 1,
+   "show_in_filter": 0
+  },
   {
-   "fieldname": "cover_letter", 
-   "fieldtype": "Text", 
-   "hidden": 0, 
-   "label": "Cover Letter", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 0, 
-   "reqd": 0
-  }, 
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "phone_number",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "label": "Phone Number",
+   "max_length": 0,
+   "max_value": 0,
+   "options": "Phone",
+   "read_only": 0,
+   "reqd": 0,
+   "show_in_filter": 0
+  },
   {
-   "fieldname": "resume_attachment", 
-   "fieldtype": "Attach", 
-   "hidden": 0, 
-   "label": "Resume Attachment", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 0, 
-   "reqd": 0
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "country",
+   "fieldtype": "Link",
+   "hidden": 0,
+   "label": "Country of Residence",
+   "max_length": 0,
+   "max_value": 0,
+   "options": "Country",
+   "read_only": 0,
+   "reqd": 0,
+   "show_in_filter": 0
+  },
+  {
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "cover_letter",
+   "fieldtype": "Text",
+   "hidden": 0,
+   "label": "Cover Letter",
+   "max_length": 0,
+   "max_value": 0,
+   "read_only": 0,
+   "reqd": 0,
+   "show_in_filter": 0
+  },
+  {
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "resume_link",
+   "fieldtype": "Data",
+   "hidden": 0,
+   "label": "Resume Link",
+   "max_length": 0,
+   "max_value": 0,
+   "read_only": 0,
+   "reqd": 0,
+   "show_in_filter": 0
+  },
+  {
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "",
+   "fieldtype": "Section Break",
+   "hidden": 0,
+   "label": "Expected Salary Range per month",
+   "max_length": 0,
+   "max_value": 0,
+   "read_only": 0,
+   "reqd": 1,
+   "show_in_filter": 0
+  },
+  {
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "hidden": 0,
+   "label": "Currency",
+   "max_length": 0,
+   "max_value": 0,
+   "options": "Currency",
+   "read_only": 0,
+   "reqd": 0,
+   "show_in_filter": 0
+  },
+  {
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "",
+   "fieldtype": "Column Break",
+   "hidden": 0,
+   "max_length": 0,
+   "max_value": 0,
+   "read_only": 0,
+   "reqd": 0,
+   "show_in_filter": 0
+  },
+  {
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "lower_range",
+   "fieldtype": "Currency",
+   "hidden": 0,
+   "label": "Lower Range",
+   "max_length": 0,
+   "max_value": 0,
+   "options": "currency",
+   "read_only": 0,
+   "reqd": 0,
+   "show_in_filter": 0
+  },
+  {
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "",
+   "fieldtype": "Column Break",
+   "hidden": 0,
+   "max_length": 0,
+   "max_value": 0,
+   "read_only": 0,
+   "reqd": 0,
+   "show_in_filter": 0
+  },
+  {
+   "allow_read_on_all_link_options": 0,
+   "fieldname": "upper_range",
+   "fieldtype": "Currency",
+   "hidden": 0,
+   "label": "Upper Range",
+   "max_length": 0,
+   "max_value": 0,
+   "options": "currency",
+   "read_only": 0,
+   "reqd": 0,
+   "show_in_filter": 0
   }
  ]
 }
\ No newline at end of file
diff --git a/erpnext/loan_management/desk_page/loan/loan.json b/erpnext/loan_management/desk_page/loan/loan.json
index fc59c19..75036bd 100644
--- a/erpnext/loan_management/desk_page/loan/loan.json
+++ b/erpnext/loan_management/desk_page/loan/loan.json
@@ -23,7 +23,7 @@
   {
    "hidden": 0,
    "label": "Reports",
-   "links": "[\n    {\n        \"doctype\": \"Loan Repayment\",\n        \"is_query_report\": true,\n        \"label\": \"Loan Repayment and Closure\",\n        \"name\": \"Loan Repayment and Closure\",\n        \"route\": \"#query-report/Loan Repayment and Closure\",\n        \"type\": \"report\"\n    },\n    {\n        \"doctype\": \"Loan Security Pledge\",\n        \"is_query_report\": true,\n        \"label\": \"Loan Security Status\",\n        \"name\": \"Loan Security Status\",\n        \"route\": \"#query-report/Loan Security Status\",\n        \"type\": \"report\"\n    }\n]"
+   "links": "[\n    {\n        \"doctype\": \"Loan Repayment\",\n        \"is_query_report\": true,\n        \"label\": \"Loan Repayment and Closure\",\n        \"name\": \"Loan Repayment and Closure\",\n        \"route\": \"#query-report/Loan Repayment and Closure\",\n        \"type\": \"report\"\n    },\n    {\n        \"doctype\": \"Loan Security Pledge\",\n        \"is_query_report\": true,\n        \"label\": \"Loan Security Status\",\n        \"name\": \"Loan Security Status\",\n        \"route\": \"#query-report/Loan Security Status\",\n        \"type\": \"report\"\n    },\n    {\n        \"doctype\": \"Loan Interest Accrual\",\n        \"is_query_report\": true,\n        \"label\": \"Loan Interest Report\",\n        \"name\": \"Loan Interest Report\",\n        \"route\": \"#query-report/Loan Interest Report\",\n        \"type\": \"report\"\n    },\n    {\n        \"doctype\": \"Loan Security\",\n        \"is_query_report\": true,\n        \"label\": \"Loan Security Exposure\",\n        \"name\": \"Loan Security Exposure\",\n        \"route\": \"#query-report/Loan Security Exposure\",\n        \"type\": \"report\"\n    },\n    {\n        \"doctype\": \"Loan Security\",\n        \"is_query_report\": true,\n        \"label\": \"Applicant-Wise Loan Security Exposure\",\n        \"name\": \"Applicant-Wise Loan Security Exposure\",\n        \"route\": \"#query-report/Applicant-Wise Loan Security Exposure\",\n        \"type\": \"report\"\n    }\n]"
   }
  ],
  "category": "Modules",
@@ -38,7 +38,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Loan",
- "modified": "2020-10-17 12:59:50.336085",
+ "modified": "2021-01-17 07:21:22.092184",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan",
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 2e0a4d1..e607d4f 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -202,7 +202,9 @@
 
 	# checking greater than 0 as there may be some minor precision error
 	if pending_amount < write_off_limit:
-		# update status as loan closure requested
+		# Auto create loan write off and update status as loan closure requested
+		write_off = make_loan_write_off(loan)
+		write_off.submit()
 		frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested')
 	else:
 		frappe.throw(_("Cannot close loan as there is an outstanding of {0}").format(pending_amount))
@@ -336,13 +338,13 @@
 	return unpledge_request
 
 def validate_employee_currency_with_company_currency(applicant, company):
-		from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency
-		if not applicant:
-			frappe.throw(_("Please select Applicant"))
-		if not company:
-			frappe.throw(_("Please select Company"))
-		employee_currency = get_employee_currency(applicant)
-		company_currency = erpnext.get_company_currency(company)
-		if employee_currency != company_currency:
-			frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}")
-				.format(applicant, employee_currency))
+	from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_employee_currency
+	if not applicant:
+		frappe.throw(_("Please select Applicant"))
+	if not company:
+		frappe.throw(_("Please select Company"))
+	employee_currency = get_employee_currency(applicant)
+	company_currency = erpnext.get_company_currency(company)
+	if employee_currency != company_currency:
+		frappe.throw(_("Loan cannot be repayed from salary for Employee {0} because salary is processed in currency {1}")
+			.format(applicant, employee_currency))
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 2abd7d8..f3c9db6 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -321,7 +321,7 @@
 		self.assertEquals(sum(pledged_qty.values()), 0)
 
 		amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5))
-		self.assertTrue(amounts['pending_principal_amount'] < 0)
+		self.assertEqual(amounts['pending_principal_amount'], 0)
 		self.assertEquals(amounts['payable_principal_amount'], 0.0)
 		self.assertEqual(amounts['interest_amount'], 0)
 
@@ -473,7 +473,7 @@
 		self.assertEquals(loan.status, "Loan Closure Requested")
 
 		amounts = calculate_amounts(loan.name, add_days(last_date, 5))
-		self.assertTrue(amounts['pending_principal_amount'] < 0.0)
+		self.assertEqual(amounts['pending_principal_amount'], 0.0)
 
 	def test_partial_unaccrued_interest_payment(self):
 		pledge = [{
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 f157f0d..185bf7a 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
@@ -22,6 +22,7 @@
   "paid_principal_amount",
   "column_break_14",
   "interest_amount",
+  "total_pending_interest_amount",
   "paid_interest_amount",
   "penalty_amount",
   "section_break_15",
@@ -172,13 +173,19 @@
    "hidden": 1,
    "label": "Last Accrual Date",
    "read_only": 1
+  },
+  {
+   "fieldname": "total_pending_interest_amount",
+   "fieldtype": "Currency",
+   "label": "Total Pending Interest Amount",
+   "options": "Company:company:default_currency"
   }
  ],
  "in_create": 1,
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-07 05:49:25.448875",
+ "modified": "2021-01-10 00:15:21.544140",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Interest Accrual",
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 d17f5af..7d7992d 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
@@ -100,6 +100,8 @@
 	interest_per_day = get_per_day_interest(pending_principal_amount, loan.rate_of_interest, posting_date)
 	payable_interest = interest_per_day * no_of_days
 
+	pending_amounts = calculate_amounts(loan.name, posting_date, payment_type='Loan Closure')
+
 	args = frappe._dict({
 		'loan': loan.name,
 		'applicant_type': loan.applicant_type,
@@ -108,7 +110,8 @@
 		'loan_account': loan.loan_account,
 		'pending_principal_amount': pending_principal_amount,
 		'interest_amount': payable_interest,
-		'penalty_amount': calculate_amounts(loan.name, posting_date)['penalty_amount'],
+		'total_pending_interest_amount': pending_amounts['interest_amount'],
+		'penalty_amount': pending_amounts['penalty_amount'],
 		'process_loan_interest': process_loan_interest,
 		'posting_date': posting_date,
 		'accrual_type': accrual_type
@@ -202,6 +205,7 @@
 	loan_interest_accrual.loan_account = args.loan_account
 	loan_interest_accrual.pending_principal_amount = flt(args.pending_principal_amount, precision)
 	loan_interest_accrual.interest_amount = flt(args.interest_amount, precision)
+	loan_interest_accrual.total_pending_interest_amount = flt(args.total_pending_interest_amount, precision)
 	loan_interest_accrual.penalty_amount = flt(args.penalty_amount, precision)
 	loan_interest_accrual.posting_date = args.posting_date or nowdate()
 	loan_interest_accrual.process_loan_interest_accrual = args.process_loan_interest
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
index 46a6440..85e008a 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
@@ -37,10 +37,8 @@
 
 		loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
 		create_pledge(loan_application)
-
 		loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
 			posting_date=get_first_day(nowdate()))
-
 		loan.submit()
 
 		first_date = '2019-10-01'
@@ -50,11 +48,46 @@
 
 		accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
 			/ (days_in_year(get_datetime(first_date).year) * 100)
-
 		make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
-
 		process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
-
 		loan_interest_accural = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
 
 		self.assertEquals(flt(loan_interest_accural.interest_amount, 0), flt(accrued_interest_amount, 0))
+
+	def test_accumulated_amounts(self):
+		pledge = [{
+			"loan_security": "Test Security 1",
+			"qty": 4000.00
+		}]
+
+		loan_application = create_loan_application('_Test Company', self.applicant, 'Demand Loan', pledge)
+		create_pledge(loan_application)
+		loan = create_demand_loan(self.applicant, "Demand Loan", loan_application,
+			posting_date=get_first_day(nowdate()))
+		loan.submit()
+
+		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(first_date).year) * 100)
+		make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
+		process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
+		loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name})
+
+		self.assertEquals(flt(loan_interest_accrual.interest_amount, 0), flt(accrued_interest_amount, 0))
+
+		next_start_date = '2019-10-31'
+		next_end_date = '2019-11-29'
+
+		no_of_days = date_diff(next_end_date, next_start_date) + 1
+		process = process_loan_interest_accrual_for_demand_loans(posting_date=next_end_date)
+		new_accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
+			/ (days_in_year(get_datetime(first_date).year) * 100)
+
+		total_pending_interest_amount = flt(accrued_interest_amount + new_accrued_interest_amount, 0)
+
+		loan_interest_accrual = frappe.get_doc("Loan Interest Accrual", {'loan': loan.name,
+			'process_loan_interest_accrual': process})
+		self.assertEquals(flt(loan_interest_accrual.total_pending_interest_amount, 0), total_pending_interest_amount)
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 415ba99..ac30c91 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -377,7 +377,7 @@
 	amounts["penalty_amount"] = flt(penalty_amount, precision)
 	amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
 	amounts["pending_accrual_entries"] = pending_accrual_entries
-	amounts["unaccrued_interest"] = unaccrued_interest
+	amounts["unaccrued_interest"] = flt(unaccrued_interest, precision)
 
 	if final_due_date:
 		amounts["due_date"] = final_due_date
diff --git a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json
index a55b482..b6e8763 100644
--- a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json
+++ b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.json
@@ -7,6 +7,7 @@
  "engine": "InnoDB",
  "field_order": [
   "loan_security",
+  "loan_security_name",
   "loan_security_type",
   "column_break_2",
   "uom",
@@ -79,10 +80,18 @@
    "label": "Loan Security Type",
    "options": "Loan Security Type",
    "read_only": 1
+  },
+  {
+   "fetch_from": "loan_security.loan_security_name",
+   "fieldname": "loan_security_name",
+   "fieldtype": "Data",
+   "label": "Loan Security Name",
+   "read_only": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-06-11 03:41:33.900340",
+ "modified": "2021-01-17 07:41:49.598086",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Security Price",
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json
index 18a9731..3ef5304 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.json
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.json
@@ -144,17 +144,17 @@
   },
   {
    "allow_on_submit": 1,
-   "description": "Pending amount that will be automatically ignored on loan closure request ",
+   "description": "Loan Write Off will be automatically created on loan closure request if pending amount is below this limit",
    "fieldname": "write_off_amount",
    "fieldtype": "Currency",
-   "label": "Write Off Amount ",
+   "label": "Auto Write Off Amount ",
    "options": "Company:company:default_currency"
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-26 07:13:55.029811",
+ "modified": "2021-01-17 06:51:26.082879",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Type",
diff --git a/erpnext/loan_management/doctype/pledge/pledge.json b/erpnext/loan_management/doctype/pledge/pledge.json
index 801e3a3..c23479c 100644
--- a/erpnext/loan_management/doctype/pledge/pledge.json
+++ b/erpnext/loan_management/doctype/pledge/pledge.json
@@ -6,6 +6,7 @@
  "engine": "InnoDB",
  "field_order": [
   "loan_security",
+  "loan_security_name",
   "loan_security_type",
   "loan_security_code",
   "uom",
@@ -85,11 +86,18 @@
    "label": "Post Haircut Amount",
    "options": "Company:company:default_currency",
    "read_only": 1
+  },
+  {
+   "fetch_from": "loan_security.loan_security_name",
+   "fieldname": "loan_security_name",
+   "fieldtype": "Data",
+   "label": "Loan Security Name",
+   "read_only": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-11-05 10:07:15.424937",
+ "modified": "2021-01-17 07:41:12.452514",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Pledge",
diff --git a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.json b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.json
index ffc3671..3feb305 100644
--- a/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.json
+++ b/erpnext/loan_management/doctype/process_loan_security_shortfall/process_loan_security_shortfall.json
@@ -30,7 +30,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-02-01 08:14:05.845161",
+ "modified": "2021-01-17 03:59:14.494557",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Process Loan Security Shortfall",
@@ -45,7 +45,9 @@
    "read": 1,
    "report": 1,
    "role": "System Manager",
+   "select": 1,
    "share": 1,
+   "submit": 1,
    "write": 1
   },
   {
@@ -57,7 +59,9 @@
    "read": 1,
    "report": 1,
    "role": "Loan Manager",
+   "select": 1,
    "share": 1,
+   "submit": 1,
    "write": 1
   }
  ],
diff --git a/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.json b/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.json
index 3e7e778..a0b3a79 100644
--- a/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.json
+++ b/erpnext/loan_management/doctype/proposed_pledge/proposed_pledge.json
@@ -6,6 +6,7 @@
  "engine": "InnoDB",
  "field_order": [
   "loan_security",
+  "loan_security_name",
   "qty",
   "loan_security_price",
   "amount",
@@ -56,12 +57,19 @@
    "label": "Post Haircut Amount",
    "options": "Company:company:default_currency",
    "read_only": 1
+  },
+  {
+   "fetch_from": "loan_security.loan_security_name",
+   "fieldname": "loan_security_name",
+   "fieldtype": "Data",
+   "label": "Loan Security Name",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-11-05 10:07:37.542344",
+ "modified": "2021-01-17 07:29:01.671722",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Proposed Pledge",
diff --git a/erpnext/loan_management/doctype/unpledge/unpledge.json b/erpnext/loan_management/doctype/unpledge/unpledge.json
index 0035668..0091e6c 100644
--- a/erpnext/loan_management/doctype/unpledge/unpledge.json
+++ b/erpnext/loan_management/doctype/unpledge/unpledge.json
@@ -6,6 +6,7 @@
  "engine": "InnoDB",
  "field_order": [
   "loan_security",
+  "loan_security_name",
   "loan_security_type",
   "loan_security_code",
   "haircut",
@@ -61,12 +62,19 @@
    "fieldtype": "Percent",
    "label": "Haircut",
    "read_only": 1
+  },
+  {
+   "fetch_from": "loan_security.loan_security_name",
+   "fieldname": "loan_security_name",
+   "fieldtype": "Data",
+   "label": "Loan Security Name",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-11-05 10:07:28.106961",
+ "modified": "2021-01-17 07:36:20.212342",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Unpledge",
diff --git a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/__init__.py b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/__init__.py
diff --git a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.js b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.js
new file mode 100644
index 0000000..73d60c4
--- /dev/null
+++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Applicant-Wise Loan Security Exposure"] = {
+	"filters": [
+		{
+			"fieldname":"company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"default": frappe.defaults.get_user_default("Company"),
+			"reqd": 1
+		}
+	]
+};
diff --git a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.json b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.json
new file mode 100644
index 0000000..a778cd7
--- /dev/null
+++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.json
@@ -0,0 +1,29 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-01-15 23:48:38.913514",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-01-15 23:48:38.913514",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Applicant-Wise Loan Security Exposure",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Loan Security",
+ "report_name": "Applicant-Wise Loan Security Exposure",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "System Manager"
+  },
+  {
+   "role": "Loan Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
new file mode 100644
index 0000000..6d7c3b7
--- /dev/null
+++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
@@ -0,0 +1,123 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import erpnext
+from frappe import _
+from frappe.utils import get_datetime, flt
+from six import iteritems
+
+def execute(filters=None):
+	columns = get_columns(filters)
+	data = get_data(filters)
+	return columns, data
+
+
+def get_columns(filters):
+	columns = [
+		{"label": _("Applicant Type"), "fieldname": "applicant_type", "options": "DocType", "width": 100},
+		{"label": _("Applicant Name"), "fieldname": "applicant_name", "fieldtype": "Dynamic Link", "options": "applicant_type", "width": 150},
+		{"label": _("Loan Security"), "fieldname": "loan_security", "fieldtype": "Link", "options": "Loan Security", "width": 160},
+		{"label": _("Loan Security Code"), "fieldname": "loan_security_code", "fieldtype": "Data", "width": 100},
+		{"label": _("Loan Security Name"), "fieldname": "loan_security_name", "fieldtype": "Data", "width": 150},
+		{"label": _("Haircut"), "fieldname": "haircut", "fieldtype": "Percent", "width": 100},
+		{"label": _("Loan Security Type"), "fieldname": "loan_security_type", "fieldtype": "Link", "options": "Loan Security Type", "width": 120},
+		{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
+		{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
+		{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
+		{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
+		{"label": _("% Of Applicant Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
+		{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
+	]
+
+	return columns
+
+def get_data(filters):
+	data = []
+	loan_security_details = get_loan_security_details(filters)
+	pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(filters,
+		loan_security_details)
+
+	currency = erpnext.get_company_currency(filters.get('company'))
+
+	for key, qty in iteritems(pledge_values):
+		row = {}
+		current_value = flt(qty * loan_security_details.get(key[1])['latest_price'])
+		row.update(loan_security_details.get(key[1]))
+		row.update({
+			'applicant_type': applicant_type_map.get(key[0]),
+			'applicant_name': key[0],
+			'total_qty': qty,
+			'current_value': current_value,
+			'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2),
+			'currency': currency
+		})
+
+		data.append(row)
+
+	return data
+
+def get_loan_security_details(filters):
+	security_detail_map = {}
+
+	loan_security_price_map = frappe._dict(frappe.db.sql("""
+		SELECT loan_security, loan_security_price
+		FROM `tabLoan Security Price` t1
+		WHERE valid_from >= (SELECT MAX(valid_from) FROM `tabLoan Security Price` t2
+		WHERE t1.loan_security = t2.loan_security)
+	""", as_list=1))
+
+	loan_security_details = frappe.get_all('Loan Security', fields=['name as loan_security',
+		'loan_security_code', 'loan_security_name', 'haircut', 'loan_security_type',
+		'disabled'])
+
+	for security in loan_security_details:
+		security.update({'latest_price': flt(loan_security_price_map.get(security.loan_security))})
+		security_detail_map.setdefault(security.loan_security, security)
+
+	return security_detail_map
+
+def get_applicant_wise_total_loan_security_qty(filters, loan_security_details):
+	current_pledges = {}
+	total_value_map = {}
+	applicant_type_map = {}
+	applicant_wise_unpledges = {}
+	conditions = ""
+
+	if filters.get('company'):
+		conditions = "AND company = %(company)s"
+
+	unpledges = frappe.db.sql("""
+		SELECT up.applicant, u.loan_security, sum(u.qty) as qty
+		FROM `tabLoan Security Unpledge` up, `tabUnpledge` u
+		WHERE u.parent = up.name
+		AND up.status = 'Approved'
+		{conditions}
+		GROUP BY up.applicant, u.loan_security
+	""".format(conditions=conditions), filters, as_dict=1)
+
+	for unpledge in unpledges:
+		applicant_wise_unpledges.setdefault((unpledge.applicant, unpledge.loan_security), unpledge.qty)
+
+	pledges = frappe.db.sql("""
+		SELECT lp.applicant_type, lp.applicant, p.loan_security, sum(p.qty) as qty
+		FROM `tabLoan Security Pledge` lp, `tabPledge`p
+		WHERE p.parent = lp.name
+		AND lp.status = 'Pledged'
+		{conditions}
+		GROUP BY lp.applicant, p.loan_security
+	""".format(conditions=conditions), filters, as_dict=1)
+
+	for security in pledges:
+		current_pledges.setdefault((security.applicant, security.loan_security), security.qty)
+		total_value_map.setdefault(security.applicant, 0.0)
+		applicant_type_map.setdefault(security.applicant, security.applicant_type)
+
+		current_pledges[(security.applicant, security.loan_security)] -= \
+			applicant_wise_unpledges.get((security.applicant, security.loan_security), 0.0)
+
+		total_value_map[security.applicant] += current_pledges.get((security.applicant, security.loan_security)) \
+			* loan_security_details.get(security.loan_security)['latest_price']
+
+	return current_pledges, total_value_map, applicant_type_map
\ No newline at end of file
diff --git a/erpnext/loan_management/report/loan_interest_report/__init__.py b/erpnext/loan_management/report/loan_interest_report/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/loan_management/report/loan_interest_report/__init__.py
diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js
new file mode 100644
index 0000000..a227b6d
--- /dev/null
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Loan Interest Report"] = {
+	"filters": [
+		{
+			"fieldname":"company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"default": frappe.defaults.get_user_default("Company"),
+			"reqd": 1
+		}
+	]
+};
diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.json b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.json
new file mode 100644
index 0000000..321d606
--- /dev/null
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.json
@@ -0,0 +1,29 @@
+{
+ "add_total_row": 1,
+ "columns": [],
+ "creation": "2021-01-10 02:03:26.742693",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-01-10 02:03:26.742693",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Interest Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Loan Interest Accrual",
+ "report_name": "Loan Interest Report",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "System Manager"
+  },
+  {
+   "role": "Loan Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
new file mode 100644
index 0000000..aa0325e
--- /dev/null
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
@@ -0,0 +1,124 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import erpnext
+from frappe import _
+from frappe.utils import flt, getdate, add_days
+
+
+def execute(filters=None):
+	columns = get_columns(filters)
+	data = get_active_loan_details(filters)
+	return columns, data
+
+def get_columns(filters):
+	columns = [
+		{"label": _("Loan"), "fieldname": "loan", "fieldtype": "Link", "options": "Loan", "width": 160},
+		{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 160},
+		{"label": _("Applicant Type"), "fieldname": "applicant_type", "options": "DocType", "width": 100},
+		{"label": _("Applicant Name"), "fieldname": "applicant_name", "fieldtype": "Dynamic Link", "options": "applicant_type", "width": 150},
+		{"label": _("Loan Type"), "fieldname": "loan_type", "fieldtype": "Link", "options": "Loan Type", "width": 100},
+		{"label": _("Sanctioned Amount"), "fieldname": "sanctioned_amount", "fieldtype": "Currency", "options": "currency", "width": 120},
+		{"label": _("Disbursed Amount"), "fieldname": "disbursed_amount", "fieldtype": "Currency", "options": "currency", "width": 120},
+		{"label": _("Penalty Amount"), "fieldname": "penalty", "fieldtype": "Currency", "options": "currency", "width": 120},
+		{"label": _("Accrued Interest"), "fieldname": "accrued_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
+		{"label": _("Total Repayment"), "fieldname": "total_repayment", "fieldtype": "Currency", "options": "currency", "width": 120},
+		{"label": _("Principal Outstanding"), "fieldname": "principal_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
+		{"label": _("Interest Outstanding"), "fieldname": "interest_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
+		{"label": _("Total Outstanding"), "fieldname": "total_outstanding", "fieldtype": "Currency", "options": "currency", "width": 120},
+		{"label": _("Undue Booked Interest"), "fieldname": "undue_interest", "fieldtype": "Currency", "options": "currency", "width": 120},
+		{"label": _("Interest %"), "fieldname": "rate_of_interest", "fieldtype": "Percent", "width": 100},
+		{"label": _("Penalty Interest %"), "fieldname": "penalty_interest", "fieldtype": "Percent", "width": 100},
+		{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
+	]
+
+	return columns
+
+def get_active_loan_details(filters):
+
+	filter_obj = {"status": ("!=", "Closed")}
+	if filters.get('company'):
+		filter_obj.update({'company': filters.get('company')})
+
+	loan_details = frappe.get_all("Loan",
+		fields=["name as loan", "applicant_type", "applicant as applicant_name", "loan_type",
+		"disbursed_amount", "rate_of_interest", "total_payment", "total_principal_paid",
+		"total_interest_payable", "written_off_amount", "status"],
+		filters=filter_obj)
+
+	loan_list = [d.loan for d in loan_details]
+
+	sanctioned_amount_map = get_sanctioned_amount_map()
+	penal_interest_rate_map = get_penal_interest_rate_map()
+	payments = get_payments(loan_list)
+	accrual_map = get_interest_accruals(loan_list)
+	currency = erpnext.get_company_currency(filters.get('company'))
+
+	for loan in loan_details:
+		loan.update({
+			"sanctioned_amount": flt(sanctioned_amount_map.get(loan.applicant_name)),
+			"principal_outstanding": flt(loan.total_payment) - flt(loan.total_principal_paid) \
+				- flt(loan.total_interest_payable) - flt(loan.written_off_amount),
+			"total_repayment": flt(payments.get(loan.loan)),
+			"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
+			"interest_outstanding": flt(accrual_map.get(loan.loan, {}).get("interest_outstanding")),
+			"penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
+			"penalty_interest": penal_interest_rate_map.get(loan.loan_type),
+			"undue_interest": flt(accrual_map.get(loan.loan, {}).get("undue_interest")),
+			"currency": currency
+		})
+
+		loan['total_outstanding'] = loan['principal_outstanding'] + loan['interest_outstanding'] \
+			+ loan['penalty']
+
+	return loan_details
+
+def get_sanctioned_amount_map():
+	return frappe._dict(frappe.get_all("Sanctioned Loan Amount", fields=["applicant", "sanctioned_amount_limit"],
+		as_list=1))
+
+def get_payments(loans):
+	return frappe._dict(frappe.get_all("Loan Repayment", fields=["against_loan", "sum(amount_paid)"],
+		filters={"against_loan": ("in", loans)}, group_by="against_loan", as_list=1))
+
+def get_interest_accruals(loans):
+	accrual_map = {}
+
+	interest_accruals = frappe.get_all("Loan Interest Accrual",
+		fields=["loan", "interest_amount", "posting_date", "penalty_amount",
+		"paid_interest_amount", "accrual_type"], filters={"loan": ("in", loans)}, order_by="posting_date desc")
+
+	for entry in interest_accruals:
+		accrual_map.setdefault(entry.loan, {
+			"accrued_interest": 0.0,
+			"undue_interest": 0.0,
+			"interest_outstanding": 0.0,
+			"last_accrual_date": '',
+			"due_date": ''
+		})
+
+		if entry.accrual_type == 'Regular':
+			if not accrual_map[entry.loan]['due_date']:
+				accrual_map[entry.loan]['due_date'] = add_days(entry.posting_date, 1)
+			if not accrual_map[entry.loan]['last_accrual_date']:
+				accrual_map[entry.loan]['last_accrual_date'] = entry.posting_date
+
+		due_date = accrual_map[entry.loan]['due_date']
+		last_accrual_date = accrual_map[entry.loan]['last_accrual_date']
+
+		if due_date and getdate(entry.posting_date) < getdate(due_date):
+			accrual_map[entry.loan]["interest_outstanding"] += entry.interest_amount - entry.paid_interest_amount
+		else:
+			accrual_map[entry.loan]['undue_interest'] += entry.interest_amount - entry.paid_interest_amount
+
+		accrual_map[entry.loan]["accrued_interest"] += entry.interest_amount
+
+		if last_accrual_date and getdate(entry.posting_date) == last_accrual_date:
+			accrual_map[entry.loan]["penalty"] = entry.penalty_amount
+
+	return accrual_map
+
+def get_penal_interest_rate_map():
+	return frappe._dict(frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1))
\ No newline at end of file
diff --git a/erpnext/loan_management/report/loan_security_exposure/__init__.py b/erpnext/loan_management/report/loan_security_exposure/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/loan_management/report/loan_security_exposure/__init__.py
diff --git a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.js b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.js
new file mode 100644
index 0000000..777f296
--- /dev/null
+++ b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.js
@@ -0,0 +1,16 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Loan Security Exposure"] = {
+	"filters": [
+		{
+			"fieldname":"company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"default": frappe.defaults.get_user_default("Company"),
+			"reqd": 1
+		}
+	]
+};
diff --git a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.json b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.json
new file mode 100644
index 0000000..d4dca08
--- /dev/null
+++ b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.json
@@ -0,0 +1,29 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-01-16 08:08:01.694583",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-01-16 08:08:01.694583",
+ "modified_by": "Administrator",
+ "module": "Loan Management",
+ "name": "Loan Security Exposure",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Loan Security",
+ "report_name": "Loan Security Exposure",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "System Manager"
+  },
+  {
+   "role": "Loan Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py
new file mode 100644
index 0000000..3ef10c0
--- /dev/null
+++ b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py
@@ -0,0 +1,79 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import erpnext
+from frappe import _
+from frappe.utils import flt
+from six import iteritems
+from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \
+	 import get_loan_security_details, get_applicant_wise_total_loan_security_qty
+
+def execute(filters=None):
+	columns = get_columns(filters)
+	data = get_data(filters)
+	return columns, data
+
+def get_columns(filters):
+	columns = [
+		{"label": _("Loan Security"), "fieldname": "loan_security", "fieldtype": "Link", "options": "Loan Security", "width": 160},
+		{"label": _("Loan Security Code"), "fieldname": "loan_security_code", "fieldtype": "Data", "width": 100},
+		{"label": _("Loan Security Name"), "fieldname": "loan_security_name", "fieldtype": "Data", "width": 150},
+		{"label": _("Haircut"), "fieldname": "haircut", "fieldtype": "Percent", "width": 100},
+		{"label": _("Loan Security Type"), "fieldname": "loan_security_type", "fieldtype": "Link", "options": "Loan Security Type", "width": 120},
+		{"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80},
+		{"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100},
+		{"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100},
+		{"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100},
+		{"label": _("% Of Total Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100},
+		{"label": _("Pledged Applicant Count"), "fieldname": "pledged_applicant_count", "fieldtype": "Percentage", "width": 100},
+		{"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100},
+	]
+
+	return columns
+
+def get_data(filters):
+	data = []
+	loan_security_details = get_loan_security_details(filters)
+	current_pledges, total_portfolio_value = get_company_wise_loan_security_details(filters, loan_security_details)
+	currency = erpnext.get_company_currency(filters.get('company'))
+
+	for security, value in iteritems(current_pledges):
+		row = {}
+		current_value = flt(value['qty'] * loan_security_details.get(security)['latest_price'])
+		row.update(loan_security_details.get(security))
+		row.update({
+			'total_qty': value['qty'],
+			'current_value': current_value,
+			'portfolio_percent': flt(current_value * 100 / total_portfolio_value, 2),
+			'pledged_applicant_count': value['applicant_count'],
+			'currency': currency
+		})
+
+		data.append(row)
+
+	return data
+
+
+def get_company_wise_loan_security_details(filters, loan_security_details):
+	pledge_values, total_value_map, applicant_type_map = get_applicant_wise_total_loan_security_qty(filters,
+		loan_security_details)
+
+	total_portfolio_value = 0
+	security_wise_map = {}
+	for key, qty in iteritems(pledge_values):
+		security_wise_map.setdefault(key[1], {
+			'qty': 0.0,
+			'applicant_count': 0.0
+		})
+
+		security_wise_map[key[1]]['qty'] += qty
+		if qty:
+			security_wise_map[key[1]]['applicant_count'] += 1
+
+		total_portfolio_value += flt(qty * loan_security_details.get(key[1])['latest_price'])
+
+	return security_wise_map, total_portfolio_value
+
+
+
diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json
index 992ef16..f190cfa 100644
--- a/erpnext/non_profit/doctype/member/member.json
+++ b/erpnext/non_profit/doctype/member/member.json
@@ -12,7 +12,6 @@
   "membership_expiry_date",
   "column_break_5",
   "membership_type",
-  "email",
   "email_id",
   "image",
   "customer_section",
@@ -65,13 +64,6 @@
    "reqd": 1
   },
   {
-   "fieldname": "email",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "User",
-   "options": "User"
-  },
-  {
    "fieldname": "image",
    "fieldtype": "Attach Image",
    "hidden": 1,
@@ -178,7 +170,7 @@
  ],
  "image_field": "image",
  "links": [],
- "modified": "2020-09-16 23:44:13.596948",
+ "modified": "2020-11-09 12:12:10.174647",
  "modified_by": "Administrator",
  "module": "Non Profit",
  "name": "Member",
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index 25d6b53..04b99f9 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -18,8 +18,6 @@
 
 
 	def validate(self):
-		if self.email:
-			self.validate_email_type(self.email)
 		if self.email_id:
 			self.validate_email_type(self.email_id)
 
@@ -57,14 +55,16 @@
 	def make_customer_and_link(self):
 		if self.customer:
 			frappe.msgprint(_("A customer is already linked to this Member"))
-		cust = create_customer(frappe._dict({
+
+		customer = create_customer(frappe._dict({
 			'fullname': self.member_name,
-			'email': self.email_id or self.email,
+			'email': self.email_id,
 			'phone': None
 		}))
 
-		self.customer = cust
+		self.customer = customer
 		self.save()
+		frappe.msgprint(_("Customer {0} has been created succesfully.").format(self.customer))
 
 
 def get_or_create_member(user_details):
diff --git a/erpnext/non_profit/doctype/membership/membership.js b/erpnext/non_profit/doctype/membership/membership.js
index ee8a8c0..573ac33 100644
--- a/erpnext/non_profit/doctype/membership/membership.js
+++ b/erpnext/non_profit/doctype/membership/membership.js
@@ -4,16 +4,25 @@
 frappe.ui.form.on('Membership', {
 	setup: function(frm) {
 		frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
-			if (val) frm.set_df_property('razorpay_details_section', 'hidden', false);
+			if (val) frm.set_df_property("razorpay_details_section", "hidden", false);
 		})
 	},
 
 	refresh: function(frm) {
+		if (frm.doc.__islocal)
+			return;
+
 		!frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => {
-			frm.call("generate_invoice", {
-				save: true
-			}).then(() => {
-				frm.reload_doc();
+			frm.call({
+				doc: frm.doc,
+				method: "generate_invoice",
+				args: {save: true},
+				freeze: true,
+				freeze_message: __("Creating Membership Invoice"),
+				callback: function(r) {
+					if (r.invoice)
+						frm.reload_doc();
+				}
 			});
 		});
 
@@ -27,6 +36,6 @@
 	},
 
 	onload: function(frm) {
-		frm.add_fetch('membership_type', 'amount', 'amount');
+		frm.add_fetch("membership_type", "amount", "amount");
 	}
 });
diff --git a/erpnext/non_profit/doctype/membership/membership.json b/erpnext/non_profit/doctype/membership/membership.json
index 7f21896..6da053f 100644
--- a/erpnext/non_profit/doctype/membership/membership.json
+++ b/erpnext/non_profit/doctype/membership/membership.json
@@ -7,6 +7,7 @@
  "engine": "InnoDB",
  "field_order": [
   "member",
+  "member_name",
   "membership_type",
   "column_break_3",
   "membership_status",
@@ -46,6 +47,8 @@
   {
    "fieldname": "membership_status",
    "fieldtype": "Select",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
    "label": "Membership Status",
    "options": "New\nCurrent\nExpired\nPending\nCancelled"
   },
@@ -122,11 +125,18 @@
    "fieldtype": "Link",
    "label": "Invoice",
    "options": "Sales Invoice"
+  },
+  {
+   "fetch_from": "member.member_name",
+   "fieldname": "member_name",
+   "fieldtype": "Data",
+   "label": "Member Name",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-09-19 14:28:11.532696",
+ "modified": "2021-01-21 16:31:20.032656",
  "modified_by": "Administrator",
  "module": "Non Profit",
  "name": "Membership",
@@ -158,7 +168,9 @@
   }
  ],
  "restrict_to_domain": "Non Profit",
+ "search_fields": "member, member_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "title_field": "member_name",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index 7d15aba..c113b80 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -14,33 +14,43 @@
 from frappe import _
 import erpnext
 
-
 class Membership(Document):
 	def validate(self):
 		if not self.member or not frappe.db.exists("Member", self.member):
-			member_name = frappe.get_value('Member', dict(email=frappe.session.user))
+			# for web forms
+			user_type = frappe.db.get_value("User", frappe.session.user, "user_type")
+			if user_type == "Website User":
+				self.create_member_from_website_user()
+			else:
+				frappe.throw(_("Please select a Member"))
 
-			if not member_name:
-				user = frappe.get_doc('User', frappe.session.user)
-				member = frappe.get_doc(dict(
-					doctype='Member',
-					email=frappe.session.user,
-					membership_type=self.membership_type,
-					member_name=user.get_fullname()
-				)).insert(ignore_permissions=True)
-				member_name = member.name
+		self.validate_membership_period()
 
-			if self.get("__islocal"):
-				self.member = member_name
+	def create_member_from_website_user(self):
+		member_name = frappe.get_value("Member", dict(email_id=frappe.session.user))
 
+		if not member_name:
+			user = frappe.get_doc("User", frappe.session.user)
+			member = frappe.get_doc(dict(
+				doctype="Member",
+				email_id=frappe.session.user,
+				membership_type=self.membership_type,
+				member_name=user.get_fullname()
+			)).insert(ignore_permissions=True)
+			member_name = member.name
+
+		if self.get("__islocal"):
+			self.member = member_name
+
+	def validate_membership_period(self):
 		# get last membership (if active)
-		last_membership = erpnext.get_last_membership()
+		last_membership = erpnext.get_last_membership(self.member)
 
 		# if person applied for offline membership
 		if last_membership and not frappe.session.user == "Administrator":
 			# if last membership does not expire in 30 days, then do not allow to renew
 			if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
-				frappe.throw(_('You can only renew if your membership expires within 30 days'))
+				frappe.throw(_("You can only renew if your membership expires within 30 days"))
 
 			self.from_date = add_days(last_membership.to_date, 1)
 		elif frappe.session.user == "Administrator":
@@ -54,11 +64,16 @@
 			self.to_date = add_months(self.from_date, 1)
 
 	def on_payment_authorized(self, status_changed_to=None):
-		if status_changed_to in ("Completed", "Authorized"):
-			self.load_from_db()
-			self.db_set('paid', 1)
+		if status_changed_to not in ("Completed", "Authorized"):
+			return
+		self.load_from_db()
+		self.db_set("paid", 1)
+		settings = frappe.get_doc("Membership Settings")
+		if settings.enable_invoicing and settings.create_for_web_forms:
+			self.generate_invoice(with_payment_entry=settings.make_payment_entry, save=True)
 
-	def generate_invoice(self, save=True):
+
+	def generate_invoice(self, save=True, with_payment_entry=False):
 		if not (self.paid or self.currency or self.amount):
 			frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details"))
 
@@ -66,34 +81,64 @@
 			frappe.throw(_("An invoice is already linked to this document"))
 
 		member = frappe.get_doc("Member", self.member)
-		plan = frappe.get_doc("Membership Type", self.membership_type)
-		settings = frappe.get_doc("Membership Settings")
-
 		if not member.customer:
 			frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
 
-		if not settings.debit_account:
-			frappe.throw(_("You need to set <b>Debit Account</b> in Membership Settings"))
-
-		if not settings.company:
-			frappe.throw(_("You need to set <b>Default Company</b> for invoicing in Membership Settings"))
+		plan = frappe.get_doc("Membership Type", self.membership_type)
+		settings = frappe.get_doc("Membership Settings")
+		self.validate_membership_type_and_settings(plan, settings)
 
 		invoice = make_invoice(self, member, plan, settings)
 		self.invoice = invoice.name
 
+		if with_payment_entry:
+			self.make_payment_entry(settings, invoice)
+
 		if save:
 			self.save()
 
 		return invoice
 
+	def validate_membership_type_and_settings(self, plan, settings):
+		settings_link = get_link_to_form("Membership Type", self.membership_type)
+
+		if not settings.debit_account:
+			frappe.throw(_("You need to set <b>Debit Account</b> in {0}").format(settings_link))
+
+		if not settings.company:
+			frappe.throw(_("You need to set <b>Default Company</b> for invoicing in {0}").format(settings_link))
+
+		if not plan.linked_item:
+			frappe.throw(_("Please set a Linked Item for the Membership Type {0}").format(
+				get_link_to_form("Membership Type", self.membership_type)))
+
+	def make_payment_entry(self, settings, invoice):
+		if not settings.payment_account:
+			frappe.throw(_("You need to set <b>Payment Account</b> in {0}").format(
+				get_link_to_form("Membership Type", self.membership_type)))
+
+		from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+		frappe.flags.ignore_account_permission = True
+		pe = get_payment_entry(dt="Sales Invoice", dn=invoice.name, bank_amount=invoice.grand_total)
+		frappe.flags.ignore_account_permission=False
+		pe.paid_to = settings.payment_account
+		pe.reference_no = self.name
+		pe.reference_date = getdate()
+		pe.save(ignore_permissions=True)
+		pe.submit()
+
 	def send_acknowlement(self):
 		settings = frappe.get_doc("Membership Settings")
 		if not settings.send_email:
-			frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in Membership Settings"))
+			frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in {0}").format(
+				get_link_to_form("Membership Settings", "Membership Settings")))
 
 		member = frappe.get_doc("Member", self.member)
+		if not member.email_id:
+			frappe.throw(_("Email address of member {0} is missing").format(frappe.utils.get_link_to_form("Member", self.member)))
+
 		plan = frappe.get_doc("Membership Type", self.membership_type)
-		email = member.email_id if member.email_id else member.email
+		email = member.email_id
 		attachments = [frappe.attach_print("Membership", self.name, print_format=settings.membership_print_format)]
 
 		if self.invoice and settings.send_invoice:
@@ -112,48 +157,56 @@
 		}
 
 		if not frappe.flags.in_test:
-			frappe.enqueue(method=frappe.sendmail, queue='short', timeout=300, is_async=True, **email_args)
+			frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args)
 		else:
 			frappe.sendmail(**email_args)
 
 	def generate_and_send_invoice(self):
-		invoice = self.generate_invoice(False)
+		self.generate_invoice(save=False)
 		self.send_acknowlement()
 
+
 def make_invoice(membership, member, plan, settings):
 	invoice = frappe.get_doc({
-		'doctype': 'Sales Invoice',
-		'customer': member.customer,
-		'debit_to': settings.debit_account,
-		'currency': membership.currency,
-		'is_pos': 0,
-		'items': [
+		"doctype": "Sales Invoice",
+		"customer": member.customer,
+		"debit_to": settings.debit_account,
+		"currency": membership.currency,
+		"company": settings.company,
+		"is_pos": 0,
+		"items": [
 			{
-				'item_code': plan.linked_item,
-				'rate': membership.amount,
-				'qty': 1
+				"item_code": plan.linked_item,
+				"rate": membership.amount,
+				"qty": 1
 			}
 		]
 	})
-
+	invoice.set_missing_values()
 	invoice.insert(ignore_permissions=True)
 	invoice.submit()
 
+	frappe.msgprint(_("Sales Invoice created successfully"))
+
 	return invoice
 
+
 def get_member_based_on_subscription(subscription_id, email):
 	members = frappe.get_all("Member", filters={
-					'subscription_id': subscription_id,
-					'email_id': email
+					"subscription_id": subscription_id,
+					"email_id": email
 				}, order_by="creation desc")
 
 	try:
-		return frappe.get_doc("Member", members[0]['name'])
+		return frappe.get_doc("Member", members[0]["name"])
 	except:
 		return None
 
+
 def verify_signature(data):
-	signature = frappe.request.headers.get('X-Razorpay-Signature')
+	if frappe.flags.in_test:
+		return True
+	signature = frappe.request.headers.get("X-Razorpay-Signature")
 
 	settings = frappe.get_doc("Membership Settings")
 	key = settings.get_webhook_secret()
@@ -162,6 +215,7 @@
 
 	controller.verify_signature(data, signature, key)
 
+
 @frappe.whitelist(allow_guest=True)
 def trigger_razorpay_subscription(*args, **kwargs):
 	data = frappe.request.get_data(as_text=True)
@@ -170,16 +224,16 @@
 	except Exception as e:
 		log = frappe.log_error(e, "Webhook Verification Error")
 		notify_failure(log)
-		return { 'status': 'Failed', 'reason': e}
+		return { "status": "Failed", "reason": e}
 
 	if isinstance(data, six.string_types):
 		data = json.loads(data)
 	data = frappe._dict(data)
 
-	subscription = data.payload.get("subscription", {}).get('entity', {})
+	subscription = data.payload.get("subscription", {}).get("entity", {})
 	subscription = frappe._dict(subscription)
 
-	payment = data.payload.get("payment", {}).get('entity', {})
+	payment = data.payload.get("payment", {}).get("entity", {})
 	payment = frappe._dict(payment)
 
 	try:
@@ -189,15 +243,15 @@
 		member = get_member_based_on_subscription(subscription.id, payment.email)
 		if not member:
 			member = create_member(frappe._dict({
-				'fullname': payment.email,
-				'email': payment.email,
-				'plan_id': get_plan_from_razorpay_id(subscription.plan_id)
+				"fullname": payment.email,
+				"email": payment.email,
+				"plan_id": get_plan_from_razorpay_id(subscription.plan_id)
 			}))
 
 			member.subscription_id = subscription.id
 			member.customer_id = payment.customer_id
 			if subscription.notes and type(subscription.notes) == dict:
-				notes = '\n'.join("{}: {}".format(k, v) for k, v in subscription.notes.items())
+				notes = "\n".join("{}: {}".format(k, v) for k, v in subscription.notes.items())
 				member.add_comment("Comment", notes)
 			elif subscription.notes and type(subscription.notes) == str:
 				member.add_comment("Comment", subscription.notes)
@@ -227,28 +281,39 @@
 		message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id)
 		log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
 		notify_failure(log)
-		return { 'status': 'Failed', 'reason': e}
+		return { "status": "Failed", "reason": e}
 
-	return { 'status': 'Success' }
+	return { "status": "Success" }
 
 
 def notify_failure(log):
 	try:
-		content = """Dear System Manager,
-Razorpay webhook for creating renewing membership subscription failed due to some reason. Please check the following error log linked below
+		content = """
+			Dear System Manager,
+			Razorpay webhook for creating renewing membership subscription failed due to some reason.
+			Please check the following error log linked below
+			Error Log: {0}
+			Regards, Administrator
+		""".format(get_link_to_form("Error Log", log.name))
 
-Error Log: {0}
-
-Regards,
-Administrator""".format(get_link_to_form("Error Log", log.name))
 		sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content)
 	except:
 		pass
 
+
 def get_plan_from_razorpay_id(plan_id):
-	plan = frappe.get_all("Membership Type", filters={'razorpay_plan_id': plan_id}, order_by="creation desc")
+	plan = frappe.get_all("Membership Type", filters={"razorpay_plan_id": plan_id}, order_by="creation desc")
 
 	try:
-		return plan[0]['name']
+		return plan[0]["name"]
 	except:
 		return None
+
+
+def set_expired_status():
+	frappe.db.sql("""
+		UPDATE
+			`tabMembership` SET `status` = 'Expired'
+		WHERE
+			`status` not in ('Cancelled') AND `to_date` < %s
+		""", (nowdate()))
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/membership/membership_list.js b/erpnext/non_profit/doctype/membership/membership_list.js
new file mode 100644
index 0000000..a959159
--- /dev/null
+++ b/erpnext/non_profit/doctype/membership/membership_list.js
@@ -0,0 +1,15 @@
+frappe.listview_settings['Membership'] = {
+	get_indicator: function(doc) {
+		if (doc.membership_status == 'New') {
+			return [__('New'), 'blue', 'membership_status,=,New'];
+		} else if (doc.membership_status === 'Current') {
+			return [__('Current'), 'green', 'membership_status,=,Current'];
+		} else if (doc.membership_status === 'Pending') {
+			return [__('Pending'), 'yellow', 'membership_status,=,Pending'];
+		} else if (doc.membership_status === 'Expired') {
+			return [__('Expired'), 'grey', 'membership_status,=,Expired'];
+		} else {
+			return [__('Cancelled'), 'red', 'membership_status,=,Cancelled'];
+		}
+	}
+};
diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py
index b23f406..ff7e6c4 100644
--- a/erpnext/non_profit/doctype/membership/test_membership.py
+++ b/erpnext/non_profit/doctype/membership/test_membership.py
@@ -2,8 +2,110 @@
 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 from __future__ import unicode_literals
-
 import unittest
+import frappe
+import erpnext
+from erpnext.non_profit.doctype.member.member import create_member
+from frappe.utils import nowdate, add_months
 
 class TestMembership(unittest.TestCase):
-	pass
+	def setUp(self):
+		# Get default company
+		company = frappe.get_doc("Company", erpnext.get_default_company())
+
+		# update membership settings
+		settings = frappe.get_doc("Membership Settings")
+		# Enable razorpay
+		settings.enable_razorpay = 1
+		settings.billing_cycle = "Monthly"
+		settings.billing_frequency = 24
+		# Enable invoicing
+		settings.enable_invoicing = 1
+		settings.make_payment_entry = 1
+		settings.company = company.name
+		settings.payment_account = company.default_cash_account
+		settings.debit_account = company.default_receivable_account
+		settings.save()
+
+		# make test plan
+		if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"):
+			plan = frappe.new_doc("Membership Type")
+			plan.membership_type = "_rzpy_test_milythm"
+			plan.amount = 100
+			plan.razorpay_plan_id = "_rzpy_test_milythm"
+			plan.linked_item = create_item("_Test Item for Non Profit Membership").name
+			plan.insert()
+		else:
+			plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
+
+		# make test member
+		self.member_doc = create_member(frappe._dict({
+				'fullname': "_Test_Member",
+				'email': "_test_member_erpnext@example.com",
+				'plan_id': plan.name
+		}))
+		self.member_doc.make_customer_and_link()
+		self.member = self.member_doc.name
+
+	def test_auto_generate_invoice_and_payment_entry(self):
+		entry = make_membership(self.member)
+
+		# Naive test to see if at all invoice was generated and attached to member
+		# In any case if details were missing, the invoicing would throw an error
+		invoice = entry.generate_invoice(save=True)
+		self.assertEqual(invoice.name, entry.invoice)
+
+	def test_renew_within_30_days(self):
+		# create a membership for two months
+		# Should work fine
+		make_membership(self.member, { "from_date": nowdate() })
+		make_membership(self.member, { "from_date": add_months(nowdate(), 1) })
+
+		from frappe.utils.user import add_role
+		add_role("test@example.com", "Non Profit Manager")
+		frappe.set_user("test@example.com")
+
+		# create next membership with expiry not within 30 days
+		self.assertRaises(frappe.ValidationError, make_membership, self.member, {
+			"from_date": add_months(nowdate(), 2),
+		})
+
+		frappe.set_user("Administrator")
+		# create the same membership but as administrator
+		make_membership(self.member, {
+			"from_date": add_months(nowdate(), 2),
+			"to_date": add_months(nowdate(), 3),
+		})
+
+def set_config(key, value):
+	frappe.db.set_value("Membership Settings", None, key, value)
+
+def make_membership(member, payload={}):
+	data = {
+		"doctype": "Membership",
+		"member": member,
+		"membership_status": "Current",
+		"membership_type": "_rzpy_test_milythm",
+		"currency": "INR",
+		"paid": 1,
+		"from_date": nowdate(),
+		"amount": 100
+	}
+	data.update(payload)
+	membership = frappe.get_doc(data)
+	membership.insert(ignore_permissions=True, ignore_if_duplicate=True)
+	return membership
+
+def create_item(item_code):
+	if not frappe.db.exists("Item", item_code):
+		item = frappe.new_doc("Item")
+		item.item_code = item_code
+		item.item_name = item_code
+		item.stock_uom = "Nos"
+		item.description = item_code
+		item.item_group = "All Item Groups"
+		item.is_stock_item = 0
+		item.save()
+	else:
+		item = frappe.get_doc("Item", item_code)
+	return item
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
index 1d89402..c95aab2 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.js
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.js
@@ -11,7 +11,7 @@
 			});
 		}
 
-		frm.set_query('inv_print_format', function(doc) {
+		frm.set_query("inv_print_format", function() {
 			return {
 				filters: {
 					"doc_type": "Sales Invoice"
@@ -19,7 +19,7 @@
 			};
 		});
 
-		frm.set_query('membership_print_format', function(doc) {
+		frm.set_query("membership_print_format", function() {
 			return {
 				filters: {
 					"doc_type": "Membership"
@@ -27,12 +27,23 @@
 			};
 		});
 
-		frm.set_query('debit_account', function(doc) {
+		frm.set_query("debit_account", function() {
 			return {
 				filters: {
-					'account_type': 'Receivable',
-					'is_group': 0,
-					'company': frm.doc.company
+					"account_type": "Receivable",
+					"is_group": 0,
+					"company": frm.doc.company
+				}
+			};
+		});
+
+		frm.set_query("payment_account", function () {
+			var account_types = ["Bank", "Cash"];
+			return {
+				filters: {
+					"account_type": ["in", account_types],
+					"is_group": 0,
+					"company": frm.doc.company
 				}
 			};
 		});
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
index 5b6bab5..3887b0a 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
@@ -11,9 +11,12 @@
   "billing_frequency",
   "webhook_secret",
   "column_break_6",
-  "enable_auto_invoicing",
+  "enable_invoicing",
+  "create_for_web_forms",
+  "make_payment_entry",
   "company",
   "debit_account",
+  "payment_account",
   "column_break_9",
   "send_email",
   "send_invoice",
@@ -58,14 +61,7 @@
    "label": "Invoicing"
   },
   {
-   "default": "0",
-   "fieldname": "enable_auto_invoicing",
-   "fieldtype": "Check",
-   "label": "Enable Auto Invoicing",
-   "mandatory_depends_on": "eval:doc.send_invoice"
-  },
-  {
-   "depends_on": "eval:doc.enable_auto_invoicing",
+   "depends_on": "eval:doc.enable_invoicing",
    "fieldname": "debit_account",
    "fieldtype": "Link",
    "label": "Debit Account",
@@ -77,7 +73,7 @@
    "fieldtype": "Column Break"
   },
   {
-   "depends_on": "eval:doc.enable_auto_invoicing",
+   "depends_on": "eval:doc.enable_invoicing",
    "fieldname": "company",
    "fieldtype": "Link",
    "label": "Company",
@@ -86,7 +82,7 @@
   },
   {
    "default": "0",
-   "depends_on": "eval:doc.enable_auto_invoicing && doc.send_email",
+   "depends_on": "eval:doc.enable_invoicing && doc.send_email",
    "fieldname": "send_invoice",
    "fieldtype": "Check",
    "label": "Send Invoice with Email"
@@ -119,11 +115,43 @@
    "label": "Email Template",
    "mandatory_depends_on": "eval:doc.send_email",
    "options": "Email Template"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_invoicing",
+   "fieldtype": "Check",
+   "label": "Enable Invoicing",
+   "mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.enable_invoicing",
+   "description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.",
+   "fieldname": "make_payment_entry",
+   "fieldtype": "Check",
+   "label": "Make Payment Entry"
+  },
+  {
+   "depends_on": "eval:doc.make_payment_entry",
+   "fieldname": "payment_account",
+   "fieldtype": "Link",
+   "label": "Payment To",
+   "mandatory_depends_on": "eval:doc.make_payment_entry",
+   "options": "Account"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.enable_invoicing",
+   "description": "Automatically create an invoice when payment is authorized from a web form entry",
+   "fieldname": "create_for_web_forms",
+   "fieldtype": "Check",
+   "label": "Auto Create Invoice for Web Forms"
   }
  ],
+ "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-08-05 17:26:37.287395",
+ "modified": "2021-01-21 19:57:53.213286",
  "modified_by": "Administrator",
  "module": "Non Profit",
  "name": "Membership Settings",
diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.js b/erpnext/non_profit/doctype/membership_type/membership_type.js
index 43311a2..91a5cb7 100644
--- a/erpnext/non_profit/doctype/membership_type/membership_type.js
+++ b/erpnext/non_profit/doctype/membership_type/membership_type.js
@@ -2,13 +2,21 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Membership Type', {
-	refresh: function(frm) {
-		frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
+	refresh: function (frm) {
+		frappe.db.get_single_value('Membership Settings', 'enable_razorpay').then(val => {
 			if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false);
 		});
 
-		frappe.db.get_single_value("Membership Settings", "enable_auto_invoicing").then(val => {
+		frappe.db.get_single_value('Membership Settings', 'enable_invoicing').then(val => {
 			if (val) frm.set_df_property('linked_item', 'hidden', false);
 		});
+
+		frm.set_query('linked_item', () => {
+			return {
+				filters: {
+					is_stock_item: 0
+				}
+			};
+		});
 	}
 });
diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.py b/erpnext/non_profit/doctype/membership_type/membership_type.py
index b95b043..022829b 100644
--- a/erpnext/non_profit/doctype/membership_type/membership_type.py
+++ b/erpnext/non_profit/doctype/membership_type/membership_type.py
@@ -5,9 +5,14 @@
 from __future__ import unicode_literals
 from frappe.model.document import Document
 import frappe
+from frappe import _
 
 class MembershipType(Document):
-	pass
+	def validate(self):
+		if self.linked_item:
+			is_stock_item = frappe.db.get_value("Item", self.linked_item, "is_stock_item")
+			if is_stock_item:
+				frappe.throw(_("The Linked Item should be a service item"))
 
 def get_membership_type(razorpay_id):
 	return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id})
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index cfac31b..da52ae9 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -736,8 +736,9 @@
 erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
 erpnext.patches.v13_0.updates_for_multi_currency_payroll
 erpnext.patches.v13_0.update_reason_for_resignation_in_employee
-erpnext.patches.v13_0.update_custom_fields_for_shopify
 execute:frappe.delete_doc("Report", "Quoted Item Comparison")
+erpnext.patches.v13_0.update_member_email_address
+erpnext.patches.v13_0.update_custom_fields_for_shopify
 erpnext.patches.v13_0.updates_for_multi_currency_payroll
 erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
 erpnext.patches.v13_0.add_po_to_global_search
diff --git a/erpnext/patches/v13_0/update_member_email_address.py b/erpnext/patches/v13_0/update_member_email_address.py
new file mode 100644
index 0000000..4056f84
--- /dev/null
+++ b/erpnext/patches/v13_0/update_member_email_address.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+	"""add value to email_id column from email"""
+
+	if frappe.db.has_column("Member", "email"):
+		# Get all members
+		for member in frappe.db.get_all("Member", pluck="name"):
+			# Check if email_id already exists
+			if not frappe.db.get_value("Member", member, "email_id"):
+				# fetch email id from the user linked field email
+				email = frappe.db.get_value("Member", member, "email")
+
+				# Set the value for it
+				frappe.db.set_value("Member", member, "email_id", email)
+
+	if frappe.db.exists("DocType", "Membership Settings"):
+		rename_field("Membership Settings", "enable_auto_invoicing", "enable_invoicing")
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index 61c593d..45f9aa1 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -3,6 +3,8 @@
 
 var in_progress = false;
 
+frappe.provide("erpnext.accounts.dimensions");
+
 frappe.ui.form.on('Payroll Entry', {
 	onload: function (frm) {
 		if (!frm.doc.posting_date) {
@@ -10,6 +12,7 @@
 		}
 		frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet);
 
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 		frm.events.department_filters(frm);
 		frm.events.payroll_payable_account_filters(frm);
 	},
@@ -129,21 +132,6 @@
 					"company": frm.doc.company
 				}
 			};
-		}),
-		frm.set_query("cost_center", function () {
-			return {
-				filters: {
-					"is_group": 0,
-					company: frm.doc.company
-				}
-			};
-		}),
-		frm.set_query("project", function () {
-			return {
-				filters: {
-					company: frm.doc.company
-				}
-			};
 		});
 	},
 
@@ -183,6 +171,7 @@
 
 	company: function (frm) {
 		frm.events.clear_employee_table(frm);
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	currency: function (frm) {
diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json
index 5c1eb61..393f647 100644
--- a/erpnext/payroll/doctype/salary_detail/salary_detail.json
+++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json
@@ -9,6 +9,7 @@
   "abbr",
   "column_break_3",
   "amount",
+  "year_to_date",
   "section_break_5",
   "additional_salary",
   "statistical_component",
@@ -226,11 +227,19 @@
   {
    "fieldname": "column_break_24",
    "fieldtype": "Column Break"
+  },
+  {
+   "description": "Total salary booked against this component for this employee from the beginning of the year (payroll period or fiscal year) up to the current salary slip's end date.",
+   "fieldname": "year_to_date",
+   "fieldtype": "Currency",
+   "label": "Year To Date",
+   "options": "currency",
+   "read_only": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-11-25 13:12:41.081106",
+ "modified": "2021-01-14 13:39:15.847158",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Detail",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js
index 51fb359..b50c774 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.js
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js
@@ -138,11 +138,11 @@
 	},
 
 	change_grid_labels: function(frm) {
-		frm.set_currency_labels(["amount", "default_amount", "additional_amount", "tax_on_flexible_benefit",
-			"tax_on_additional_salary"], frm.doc.currency, "earnings");
+		let fields = ["amount", "year_to_date", "default_amount", "additional_amount", "tax_on_flexible_benefit",
+			"tax_on_additional_salary"];
 
-		frm.set_currency_labels(["amount", "default_amount", "additional_amount", "tax_on_flexible_benefit",
-			"tax_on_additional_salary"], frm.doc.currency, "deductions");
+		frm.set_currency_labels(fields, frm.doc.currency, "earnings");
+		frm.set_currency_labels(fields, frm.doc.currency, "deductions");
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 43deee4..9f9691b 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -584,6 +584,7 @@
    "fieldtype": "Column Break"
   },
   {
+   "description": "Total salary booked for this employee from the beginning of the year (payroll period or fiscal year) up to the current salary slip's end date.",
    "fieldname": "year_to_date",
    "fieldtype": "Currency",
    "label": "Year To Date",
@@ -591,6 +592,7 @@
    "read_only": 1
   },
   {
+   "description": "Total salary booked for this employee from the beginning of the month up to the current salary slip's end date.",
    "fieldname": "month_to_date",
    "fieldtype": "Currency",
    "label": "Month To Date",
@@ -616,7 +618,7 @@
  "idx": 9,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-21 23:43:44.959840",
+ "modified": "2021-01-14 13:37:38.180920",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Slip",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 183ad13..2d3bc57 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -52,6 +52,7 @@
 		self.calculate_net_pay()
 		self.compute_year_to_date()
 		self.compute_month_to_date()
+		self.compute_component_wise_year_to_date()
 
 		if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"):
 			max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet")
@@ -1138,16 +1139,7 @@
 
 	def compute_year_to_date(self):
 		year_to_date = 0
-		payroll_period = get_payroll_period(self.start_date, self.end_date, self.company)
-
-		if payroll_period:
-			period_start_date = payroll_period.start_date
-			period_end_date = payroll_period.end_date
-		else:
-			# get dates based on fiscal year if no payroll period exists
-			fiscal_year = get_fiscal_year(date=self.start_date, company=self.company, as_dict=1)
-			period_start_date = fiscal_year.year_start_date
-			period_end_date = fiscal_year.year_end_date
+		period_start_date, period_end_date = self.get_year_to_date_period()
 
 		salary_slip_sum = frappe.get_list('Salary Slip',
 			fields = ['sum(net_pay) as sum'],
@@ -1180,6 +1172,47 @@
 		month_to_date += self.net_pay
 		self.month_to_date = month_to_date
 
+	def compute_component_wise_year_to_date(self):
+		period_start_date, period_end_date = self.get_year_to_date_period()
+
+		for key in ('earnings', 'deductions'):
+			for component in self.get(key):
+				year_to_date = 0
+				component_sum = frappe.db.sql("""
+					SELECT sum(detail.amount) as sum
+					FROM `tabSalary Detail` as detail
+					INNER JOIN `tabSalary Slip` as salary_slip
+					ON detail.parent = salary_slip.name
+					WHERE
+						salary_slip.employee_name = %(employee_name)s
+						AND detail.salary_component = %(component)s
+						AND salary_slip.start_date >= %(period_start_date)s
+						AND salary_slip.end_date < %(period_end_date)s
+						AND salary_slip.name != %(docname)s
+						AND salary_slip.docstatus = 1""",
+						{'employee_name': self.employee_name, 'component': component.salary_component, 'period_start_date': period_start_date,
+							'period_end_date': period_end_date, 'docname': self.name}
+				)
+
+				year_to_date = flt(component_sum[0][0]) if component_sum else 0.0
+				year_to_date += component.amount
+				component.year_to_date = year_to_date
+
+	def get_year_to_date_period(self):
+		payroll_period = get_payroll_period(self.start_date, self.end_date, self.company)
+
+		if payroll_period:
+			period_start_date = payroll_period.start_date
+			period_end_date = payroll_period.end_date
+		else:
+			# get dates based on fiscal year if no payroll period exists
+			fiscal_year = get_fiscal_year(date=self.start_date, company=self.company, as_dict=1)
+			period_start_date = fiscal_year.year_start_date
+			period_end_date = fiscal_year.year_end_date
+
+		return period_start_date, period_end_date
+
+
 def unlink_ref_doc_from_salary_slip(ref_no):
 	linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip`
 	where journal_entry=%s and docstatus < 2""", (ref_no))
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 4368c03..f58a8e5 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -321,6 +321,38 @@
 			year_to_date += flt(slip.net_pay)
 			self.assertEqual(slip.year_to_date, year_to_date)
 
+	def test_component_wise_year_to_date_computation(self):
+		from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+
+		applicant = make_employee("test_ytd@salary.com", company="_Test Company")
+
+		payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company")
+
+		create_tax_slab(payroll_period, allow_tax_exemption=True, currency="INR", effective_date=getdate("2019-04-01"),
+			company="_Test Company")
+
+		salary_structure = make_salary_structure("Monthly Salary Structure Test for Salary Slip YTD",
+			"Monthly", employee=applicant, company="_Test Company", currency="INR", payroll_period=payroll_period)
+
+		# clear salary slip for this employee
+		frappe.db.sql("DELETE FROM `tabSalary Slip` where employee_name = 'test_ytd@salary.com'")
+
+		create_salary_slips_for_payroll_period(applicant, salary_structure.name,
+			payroll_period, deduct_random=False, num=3)
+
+		salary_slips = frappe.get_all("Salary Slip", fields=["name"], filters={"employee_name":
+			"test_ytd@salary.com"}, order_by = "posting_date")
+
+		year_to_date = dict()
+		for slip in salary_slips:
+			doc = frappe.get_doc("Salary Slip", slip.name)
+			for entry in doc.get("earnings"):
+				if not year_to_date.get(entry.salary_component):
+					year_to_date[entry.salary_component] = 0
+
+				year_to_date[entry.salary_component] += entry.amount
+				self.assertEqual(year_to_date[entry.salary_component], entry.year_to_date)
+
 	def test_tax_for_payroll_period(self):
 		data = {}
 		# test the impact of tax exemption declaration, tax exemption proof submission
@@ -714,10 +746,10 @@
 	else:
 		return income_tax_slab_name
 
-def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True):
+def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True, num=12):
 	deducted_dates = []
 	i = 0
-	while i < 12:
+	while i < num:
 		slip = frappe.get_doc({"doctype": "Salary Slip", "employee": employee,
 				"salary_structure": salary_structure, "frequency": "Monthly"})
 		if i == 0:
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js
index ba824c5..6c7b382 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -70,6 +70,9 @@
 		});
 	},
 
+	company: function(frm) {
+		frm.trigger('set_earning_deduction_component');
+	},
 
 	currency: function(frm) {
 		calculate_totals(frm.doc);
@@ -117,6 +120,7 @@
 		fields_read_only.forEach(function(field) {
 			frappe.meta.get_docfield("Salary Detail", field, frm.doc.name).read_only = 1;
 		});
+		frm.trigger('set_earning_deduction_component');
 	},
 
 	assign_to_employees:function (frm) {
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py
index 77914bb..e718031 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py
@@ -216,8 +216,13 @@
 	return frappe.db.sql("""
 		select t1.salary_component
 		from `tabSalary Component` t1, `tabSalary Component Account` t2
-		where t1.salary_component = t2.parent
-		and t1.type = %s 
-		and t2.company = %s
+		where (t1.name = t2.parent
+		and t1.type = %(type)s
+		and t2.company = %(company)s)
+		or (t1.type = %(type)s
+		and t1.statistical_component = 1)
 		order by salary_component
-	""", (filters['type'], filters['company']) )
+	""",{
+		"type": filters['type'],
+		"company": filters['company']
+	})
diff --git a/erpnext/payroll/print_format/__init__.py b/erpnext/payroll/print_format/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/payroll/print_format/__init__.py
diff --git a/erpnext/payroll/print_format/salary_slip_with_year_to_date/__init__.py b/erpnext/payroll/print_format/salary_slip_with_year_to_date/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/payroll/print_format/salary_slip_with_year_to_date/__init__.py
diff --git a/erpnext/payroll/print_format/salary_slip_with_year_to_date/salary_slip_with_year_to_date.json b/erpnext/payroll/print_format/salary_slip_with_year_to_date/salary_slip_with_year_to_date.json
new file mode 100644
index 0000000..71ba37f
--- /dev/null
+++ b/erpnext/payroll/print_format/salary_slip_with_year_to_date/salary_slip_with_year_to_date.json
@@ -0,0 +1,25 @@
+{
+ "absolute_value": 0,
+ "align_labels_right": 0,
+ "creation": "2021-01-14 09:56:42.393623",
+ "custom_format": 0,
+ "default_print_language": "en",
+ "disabled": 0,
+ "doc_type": "Salary Slip",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"   <h3 style=\\\"text-align: right;\\\"><span style=\\\"line-height: 1.42857;\\\">{{doc.name}}</span></h3>\\n<div>\\n    <hr style=\\\"text-align: center;\\\">\\n</div>   \"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"employee\", \"print_hide\": 0, \"label\": \"Employee\"}, {\"fieldname\": \"company\", \"print_hide\": 0, \"label\": \"Company\"}, {\"fieldname\": \"employee_name\", \"print_hide\": 0, \"label\": \"Employee Name\"}, {\"fieldname\": \"department\", \"print_hide\": 0, \"label\": \"Department\"}, {\"fieldname\": \"designation\", \"print_hide\": 0, \"label\": \"Designation\"}, {\"fieldname\": \"branch\", \"print_hide\": 0, \"label\": \"Branch\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"start_date\", \"print_hide\": 0, \"label\": \"Start Date\"}, {\"fieldname\": \"end_date\", \"print_hide\": 0, \"label\": \"End Date\"}, {\"fieldname\": \"total_working_days\", \"print_hide\": 0, \"label\": \"Working Days\"}, {\"fieldname\": \"leave_without_pay\", \"print_hide\": 0, \"label\": \"Leave Without Pay\"}, {\"fieldname\": \"payment_days\", \"print_hide\": 0, \"label\": \"Payment Days\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"earnings\", \"print_hide\": 0, \"label\": \"Earnings\", \"visible_columns\": [{\"fieldname\": \"salary_component\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"amount\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"year_to_date\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"deductions\", \"print_hide\": 0, \"label\": \"Deductions\", \"visible_columns\": [{\"fieldname\": \"salary_component\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"amount\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"year_to_date\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"depends_on_payment_days\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"gross_pay\", \"print_hide\": 0, \"label\": \"Gross Pay\"}, {\"fieldname\": \"total_deduction\", \"print_hide\": 0, \"label\": \"Total Deduction\"}, {\"fieldname\": \"net_pay\", \"print_hide\": 0, \"label\": \"Net Pay\"}, {\"fieldname\": \"rounded_total\", \"print_hide\": 0, \"label\": \"Rounded Total\"}, {\"fieldname\": \"total_in_words\", \"print_hide\": 0, \"label\": \"Total in words\"}, {\"fieldtype\": \"Section Break\", \"label\": \"net pay info\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"year_to_date\", \"print_hide\": 0, \"label\": \"Year To Date\"}, {\"fieldname\": \"month_to_date\", \"print_hide\": 0, \"label\": \"Month To Date\"}]",
+ "idx": 0,
+ "line_breaks": 0,
+ "modified": "2021-01-14 10:03:45.283725",
+ "modified_by": "Administrator",
+ "module": "Payroll",
+ "name": "Salary Slip with Year to Date",
+ "owner": "Administrator",
+ "print_format_builder": 0,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 0,
+ "standard": "Yes"
+}
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index 29f3595..649eb45 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -31,15 +31,6 @@
 					}
 				}
 			});
-
-			frm.set_query("cost_center", "taxes", function(doc) {
-				return {
-					filters: {
-						'company': doc.company,
-						"is_group": 0
-					}
-				}
-			});
 		}
 	},
 	validate: function(frm) {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index bed9c14..f144c29 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1,6 +1,8 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
+frappe.provide('erpnext.accounts.dimensions');
+
 erpnext.TransactionController = erpnext.taxes_and_totals.extend({
 	setup: function() {
 		this._super();
@@ -106,6 +108,8 @@
 				if(!item.warehouse && frm.doc.set_warehouse) {
 					item.warehouse = frm.doc.set_warehouse;
 				}
+
+				erpnext.accounts.dimensions.copy_dimension_from_first_row(frm, cdt, cdn, 'items');
 			}
 		});
 
@@ -159,16 +163,6 @@
 				};
 			});
 		}
-		if (this.frm.fields_dict["items"].grid.get_field("cost_center")) {
-			this.frm.set_query("cost_center", "items", function(doc) {
-				return {
-					filters: {
-						"company": doc.company,
-						"is_group": 0
-					}
-				};
-			});
-		}
 
 		if (this.frm.fields_dict["items"].grid.get_field("expense_account")) {
 			this.frm.set_query("expense_account", "items", function(doc) {
diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js
index 560a561..b635adc 100644
--- a/erpnext/public/js/queries.js
+++ b/erpnext/public/js/queries.js
@@ -115,7 +115,26 @@
 				["Warehouse", "is_group", "=",0]
 
 			]
-		}
+		};
+	},
+
+	get_filtered_dimensions: function(doc, child_fields, dimension, company) {
+		let account = '';
+
+		child_fields.forEach((field) => {
+			if (!account) {
+				account = doc[field];
+			}
+		});
+
+		return {
+			query: "erpnext.controllers.queries.get_filtered_dimensions",
+			filters: {
+				'dimension': dimension,
+				'account': account,
+				'company': company
+			}
+		};
 	}
 });
 
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 891bbe5..c39609b 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -194,15 +194,21 @@
 	add_dimensions: function(report_name, index) {
 		let filters = frappe.query_reports[report_name].filters;
 
-		erpnext.dimension_filters.forEach((dimension) => {
-			let found = filters.some(el => el.fieldname === dimension['fieldname']);
+		frappe.call({
+			method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimensions",
+			callback: function(r) {
+				let accounting_dimensions = r.message[0];
+				accounting_dimensions.forEach((dimension) => {
+					let found = filters.some(el => el.fieldname === dimension['fieldname']);
 
-			if (!found) {
-				filters.splice(index, 0 ,{
-					"fieldname": dimension["fieldname"],
-					"label": __(dimension["label"]),
-					"fieldtype": "Link",
-					"options": dimension["document_type"]
+					if (!found) {
+						filters.splice(index, 0, {
+							"fieldname": dimension["fieldname"],
+							"label": __(dimension["label"]),
+							"fieldtype": "Link",
+							"options": dimension["document_type"]
+						});
+					}
 				});
 			}
 		});
diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js
index b6720c0..96e1817 100644
--- a/erpnext/public/js/utils/dimension_tree_filter.js
+++ b/erpnext/public/js/utils/dimension_tree_filter.js
@@ -1,54 +1,83 @@
-frappe.provide('frappe.ui.form');
+frappe.provide('erpnext.accounts');
 
-let default_dimensions = {};
+erpnext.accounts.dimensions = {
+	setup_dimension_filters(frm, doctype) {
+		this.accounting_dimensions = [];
+		this.default_dimensions = {};
+		this.fetch_custom_dimensions(frm, doctype);
+	},
 
-let doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
-	"Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Shipping Rule", "Loyalty Program",
-	"Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool",
-	"Subscription", "Purchase Order", "Journal Entry", "Material Request", "Purchase Receipt", "Landed Cost Item", "Asset"];
+	fetch_custom_dimensions(frm, doctype) {
+		let me = this;
+		frappe.call({
+			method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimensions",
+			args: {
+				'with_cost_center_and_project': true
+			},
+			callback: function(r) {
+				me.accounting_dimensions = r.message[0];
+				me.default_dimensions = r.message[1];
+				me.setup_filters(frm, doctype);
+			}
+		});
+	},
 
-let child_docs = ["Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account",
-	"Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction",
-	"Landed Cost Item", "Asset Value Adjustment", "Opening Invoice Creation Tool Item", "Subscription Plan"];
-
-frappe.call({
-	method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimension_filters",
-	callback: function(r) {
-		erpnext.dimension_filters = r.message[0];
-		default_dimensions = r.message[1];
-	}
-});
-
-doctypes_with_dimensions.forEach((doctype) => {
-	frappe.ui.form.on(doctype, {
-		onload: function(frm) {
-			erpnext.dimension_filters.forEach((dimension) => {
+	setup_filters(frm, doctype) {
+		if (this.accounting_dimensions) {
+			this.accounting_dimensions.forEach((dimension) => {
 				frappe.model.with_doctype(dimension['document_type'], () => {
-					if(frappe.meta.has_field(dimension['document_type'], 'is_group')) {
-						frm.set_query(dimension['fieldname'], {
-							"is_group": 0
-						});
-					}
+					let parent_fields = [];
+					frappe.meta.get_docfields(doctype).forEach((df) => {
+						if (df.fieldtype === 'Link' && df.options === 'Account') {
+							parent_fields.push(df.fieldname);
+						} else if (df.fieldtype === 'Table') {
+							this.setup_child_filters(frm, df.options, df.fieldname, dimension['fieldname']);
+						}
+
+						if (frappe.meta.has_field(doctype, dimension['fieldname'])) {
+							this.setup_account_filters(frm, dimension['fieldname'], parent_fields);
+						}
+					});
 				});
 			});
-		},
+		}
+	},
 
-		company: function(frm) {
-			if(frm.doc.company && (Object.keys(default_dimensions || {}).length > 0)
-				&& default_dimensions[frm.doc.company]) {
-				frm.trigger('update_dimension');
-			}
-		},
+	setup_child_filters(frm, doctype, parentfield, dimension) {
+		let fields = [];
 
-		update_dimension: function(frm) {
-			erpnext.dimension_filters.forEach((dimension) => {
-				if(frm.is_new()) {
-					if(frm.doc.company && Object.keys(default_dimensions || {}).length > 0
-						&& default_dimensions[frm.doc.company]) {
+		if (frappe.meta.has_field(doctype, dimension)) {
+			frappe.model.with_doctype(doctype, () => {
+				frappe.meta.get_docfields(doctype).forEach((df) => {
+					if (df.fieldtype === 'Link' && df.options === 'Account') {
+						fields.push(df.fieldname);
+					}
+				});
 
-						let default_dimension = default_dimensions[frm.doc.company][dimension['fieldname']];
+				frm.set_query(dimension, parentfield, function(doc, cdt, cdn) {
+					let row = locals[cdt][cdn];
+					return erpnext.queries.get_filtered_dimensions(row, fields, dimension, doc.company);
+				});
+			});
+		}
+	},
 
-						if(default_dimension) {
+	setup_account_filters(frm, dimension, fields) {
+		frm.set_query(dimension, function(doc) {
+			return erpnext.queries.get_filtered_dimensions(doc, fields, dimension, doc.company);
+		});
+	},
+
+	update_dimension(frm, doctype) {
+		if (this.accounting_dimensions) {
+			this.accounting_dimensions.forEach((dimension) => {
+				if (frm.is_new()) {
+					if (frm.doc.company && Object.keys(this.default_dimensions || {}).length > 0
+						&& this.default_dimensions[frm.doc.company]) {
+
+						let default_dimension = this.default_dimensions[frm.doc.company][dimension['fieldname']];
+
+						if (default_dimension) {
 							if (frappe.meta.has_field(doctype, dimension['fieldname'])) {
 								frm.set_value(dimension['fieldname'], default_dimension);
 							}
@@ -61,23 +90,14 @@
 				}
 			});
 		}
-	});
-});
+	},
 
-child_docs.forEach((doctype) => {
-	frappe.ui.form.on(doctype, {
-		items_add: function(frm, cdt, cdn) {
-			erpnext.dimension_filters.forEach((dimension) => {
-				var row = frappe.get_doc(cdt, cdn);
-				frm.script_manager.copy_from_first_row("items", row, [dimension['fieldname']]);
-			});
-		},
-
-		accounts_add: function(frm, cdt, cdn) {
-			erpnext.dimension_filters.forEach((dimension) => {
-				var row = frappe.get_doc(cdt, cdn);
-				frm.script_manager.copy_from_first_row("accounts", row, [dimension['fieldname']]);
+	copy_dimension_from_first_row(frm, cdt, cdn, fieldname) {
+		if (frappe.meta.has_field(frm.doctype, fieldname) && this.accounting_dimensions) {
+			this.accounting_dimensions.forEach((dimension) => {
+				let row = frappe.get_doc(cdt, cdn);
+				frm.script_manager.copy_from_first_row(fieldname, row, [dimension['fieldname']]);
 			});
 		}
-	});
-});
\ No newline at end of file
+	}
+};
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index d0cac90..eb210be 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -161,9 +161,9 @@
 
 		item.qty = abs(item.qty)
 		item.discount_amount = abs(item.discount_amount * item.qty)
-		item.unit_rate = abs(item.base_amount / item.qty)
-		item.gross_amount = abs(item.base_amount)
-		item.taxable_value = abs(item.base_amount)
+		item.unit_rate = abs(item.base_net_amount / item.qty)
+		item.gross_amount = abs(item.base_net_amount)
+		item.taxable_value = abs(item.base_net_amount)
 
 		item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
 		item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
@@ -198,7 +198,7 @@
 		if t.account_head in gst_accounts_list:
 			item_tax_rate = item_tax_detail[0]
 			# item tax amount excluding discount amount
-			item_tax_amount = (item_tax_rate / 100) * item.base_amount
+			item_tax_amount = (item_tax_rate / 100) * item.base_net_amount
 
 			if t.account_head in gst_accounts.cess_account:
 				item_tax_amount_after_discount = item_tax_detail[1]
@@ -217,8 +217,14 @@
 
 def get_invoice_value_details(invoice):
 	invoice_value_details = frappe._dict(dict())
-	invoice_value_details.base_total = abs(invoice.base_total)
-	invoice_value_details.invoice_discount_amt = invoice.base_discount_amount
+
+	if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
+		invoice_value_details.base_total = abs(invoice.base_total)
+	else:
+		invoice_value_details.base_total = abs(invoice.base_net_total)
+
+	# since tax already considers discount amount
+	invoice_value_details.invoice_discount_amt = 0 # invoice.base_discount_amount
 	invoice_value_details.round_off = invoice.base_rounding_adjustment
 	invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
 	invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
@@ -244,9 +250,9 @@
 			
 			for tax_type in ['igst', 'cgst', 'sgst']:
 				if t.account_head in gst_accounts[f'{tax_type}_account']:
-					invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount)
+					invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount_after_discount_amount)
 		else:
-			invoice_value_details.total_other_charges += abs(t.base_tax_amount)
+			invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount)
 	
 	return invoice_value_details
 
@@ -473,7 +479,7 @@
 			"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
 			"response": json.dumps(res, indent=4) if res else None
 		})
-		request_log.insert(ignore_permissions=True)
+		request_log.save(ignore_permissions=True)
 		frappe.db.commit()
 
 	def fetch_auth_token(self):
@@ -486,7 +492,8 @@
 			res = self.make_request('post', self.authenticate_url, headers)
 			self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
 			self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
-			self.e_invoice_settings.save()
+			self.e_invoice_settings.save(ignore_permissions=True)
+			self.e_invoice_settings.reload()
 
 		except Exception:
 			self.log_error(res)
@@ -757,7 +764,7 @@
 			'label': _('IRN Generated')
 		}
 		self.update_invoice()
-	
+
 	def attach_qrcode_image(self):
 		qrcode = self.invoice.signed_qr_code
 		doctype = self.invoice.doctype
@@ -768,7 +775,7 @@
 			'file_name': 'QRCode_{}.png'.format(docname.replace('/', '-')),
 			'attached_to_doctype': doctype,
 			'attached_to_name': docname,
-			'content': 'qrcode',
+			'content': str(base64.b64encode(os.urandom(64))),
 			'is_private': 1
 		})
 		_file.insert()
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index cb1e31b..ee18042 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -7,6 +7,7 @@
 
 frappe.provide("erpnext.stock");
 frappe.provide("erpnext.stock.delivery_note");
+frappe.provide("erpnext.accounts.dimensions");
 
 frappe.ui.form.on("Delivery Note", {
 	setup: function(frm) {
@@ -76,7 +77,7 @@
 			}
 		});
 
-
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	print_without_amount: function(frm) {
@@ -318,6 +319,7 @@
 
 	company: function(frm) {
 		frm.trigger("unhide_account_head");
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	unhide_account_head: function(frm) {
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 01edd99..527b0d3 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -2,6 +2,7 @@
 // License: GNU General Public License v3. See license.txt
 
 // eslint-disable-next-line
+frappe.provide("erpnext.accounts.dimensions");
 {% include 'erpnext/public/js/controllers/buying.js' %};
 
 frappe.ui.form.on('Material Request', {
@@ -66,6 +67,12 @@
 				filters: {'company': doc.company}
 			};
 		});
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+	},
+
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	onload_post_render: function(frm) {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index bc1d81d..d998729 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -46,6 +46,8 @@
 		erpnext.queries.setup_queries(frm, "Warehouse", function() {
 			return erpnext.queries.warehouse(frm.doc);
 		});
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	refresh: function(frm) {
@@ -75,6 +77,7 @@
 
 	company: function(frm) {
 		frm.trigger("toggle_display_account_head");
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	toggle_display_account_head: function(frm) {
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index f75e8b7..357fa8d 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -1,6 +1,7 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt
 
 frappe.provide("erpnext.stock");
+frappe.provide("erpnext.accounts.dimensions");
 
 frappe.ui.form.on('Stock Entry', {
 	setup: function(frm) {
@@ -97,6 +98,7 @@
 		});
 
 		frm.add_fetch("bom_no", "inspection_required", "inspection_required");
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	setup_quality_inspection: function(frm) {
@@ -312,6 +314,8 @@
 				frm.set_value("letter_head", company_doc.default_letter_head);
 			}
 			frm.trigger("toggle_display_account_head");
+
+			erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 		}
 	},
 
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index e2121fc..ac4ed5e 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -2,6 +2,7 @@
 // License: GNU General Public License v3. See license.txt
 
 frappe.provide("erpnext.stock");
+frappe.provide("erpnext.accounts.dimensions");
 
 frappe.ui.form.on("Stock Reconciliation", {
 	onload: function(frm) {
@@ -26,6 +27,12 @@
 		if (!frm.doc.expense_account) {
 			frm.trigger("set_expense_account");
 		}
+
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+	},
+
+	company: function(frm) {
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index 8aaf7ab..ff603fc 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -233,7 +233,8 @@
 				from `tabItem` {item_conditions}) item
 		where item_code = item.name and
 			company = %(company)s and
-			posting_date <= %(to_date)s
+			posting_date <= %(to_date)s and
+			is_cancelled != 1
 			{sle_conditions}
 			order by posting_date, posting_time, sle.creation, actual_qty""" #nosec
 		.format(item_conditions=get_item_conditions(filters),
diff --git a/erpnext/support/desk_page/support/support.json b/erpnext/support/desk_page/support/support.json
index 18cf87a..dba2b14 100644
--- a/erpnext/support/desk_page/support/support.json
+++ b/erpnext/support/desk_page/support/support.json
@@ -28,7 +28,7 @@
   {
    "hidden": 0,
    "label": "Reports",
-   "links": "[\n    {\n        \"dependencies\": [\n            \"Issue\"\n        ],\n        \"doctype\": \"Issue\",\n        \"is_query_report\": true,\n        \"label\": \"First Response Time for Issues\",\n        \"name\": \"First Response Time for Issues\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Issue\"\n        ],\n        \"doctype\": \"Issue\",\n        \"is_query_report\": true,\n        \"label\": \"Issue Summary\",\n        \"name\": \"Issue Summary\",\n        \"type\": \"report\"\n    }\n]"
+   "links": "[\n    {\n        \"dependencies\": [\n            \"Issue\"\n        ],\n        \"doctype\": \"Issue\",\n        \"is_query_report\": true,\n        \"label\": \"First Response Time for Issues\",\n        \"name\": \"First Response Time for Issues\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Issue\"\n        ],\n        \"doctype\": \"Issue\",\n        \"is_query_report\": true,\n        \"label\": \"Issue Analytics\",\n        \"name\": \"Issue Analytics\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Issue\"\n        ],\n        \"doctype\": \"Issue\",\n        \"is_query_report\": true,\n        \"label\": \"Issue Summary\",\n        \"name\": \"Issue Summary\",\n        \"type\": \"report\"\n    }\n]"
   }
  ],
  "category": "Modules",
@@ -43,7 +43,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Support",
- "modified": "2020-10-12 18:40:22.252915",
+ "modified": "2021-01-13 20:15:03.064256",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Support",
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 62b39cc..02d10a4 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -214,7 +214,7 @@
 
 	def before_insert(self):
 		if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
-			self.set_response_and_resolution_time()
+			self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
 
 	def set_response_and_resolution_time(self, priority=None, service_level_agreement=None):
 		service_level_agreement = get_active_service_level_agreement_for(priority=priority,
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index c962dc6..483bb15 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -135,15 +135,19 @@
 		self.assertEqual(flt(issue.total_hold_time, 2), 2700)
 
 
-def make_issue(creation=None, customer=None, index=0):
+def make_issue(creation=None, customer=None, index=0, priority=None, issue_type=None):
 	issue = frappe.get_doc({
 		"doctype": "Issue",
 		"subject": "Service Level Agreement Issue {0}".format(index),
 		"customer": customer,
 		"raised_by": "test@example.com",
 		"description": "Service Level Agreement Issue",
+		"issue_type": issue_type,
+		"priority": priority,
 		"creation": creation,
-		"service_level_agreement_creation": creation
+		"opening_date": creation,
+		"service_level_agreement_creation": creation,
+		"company": "_Test Company"
 	}).insert(ignore_permissions=True)
 
 	return issue
diff --git a/erpnext/support/report/issue_analytics/__init__.py b/erpnext/support/report/issue_analytics/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/support/report/issue_analytics/__init__.py
diff --git a/erpnext/support/report/issue_analytics/issue_analytics.js b/erpnext/support/report/issue_analytics/issue_analytics.js
new file mode 100644
index 0000000..f87b2c2
--- /dev/null
+++ b/erpnext/support/report/issue_analytics/issue_analytics.js
@@ -0,0 +1,141 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Issue Analytics"] = {
+	"filters": [
+		{
+			fieldname: "company",
+			label: __("Company"),
+			fieldtype: "Link",
+			options: "Company",
+			default: frappe.defaults.get_user_default("Company"),
+			reqd: 1
+		},
+		{
+			fieldname: "based_on",
+			label: __("Based On"),
+			fieldtype: "Select",
+			options: ["Customer", "Issue Type", "Issue Priority", "Assigned To"],
+			default: "Customer",
+			reqd: 1
+		},
+		{
+			fieldname: "from_date",
+			label: __("From Date"),
+			fieldtype: "Date",
+			default: frappe.defaults.get_global_default("year_start_date"),
+			reqd: 1
+		},
+		{
+			fieldname:"to_date",
+			label: __("To Date"),
+			fieldtype: "Date",
+			default: frappe.defaults.get_global_default("year_end_date"),
+			reqd: 1
+		},
+		{
+			fieldname: "range",
+			label: __("Range"),
+			fieldtype: "Select",
+			options: [
+				{ "value": "Weekly", "label": __("Weekly") },
+				{ "value": "Monthly", "label": __("Monthly") },
+				{ "value": "Quarterly", "label": __("Quarterly") },
+				{ "value": "Yearly", "label": __("Yearly") }
+			],
+			default: "Monthly",
+			reqd: 1
+		},
+		{
+			fieldname: "status",
+			label: __("Status"),
+			fieldtype: "Select",
+			options:[
+				{label: __('Open'), value: 'Open'},
+				{label: __('Replied'), value: 'Replied'},
+				{label: __('Resolved'), value: 'Resolved'},
+				{label: __('Closed'), value: 'Closed'}
+			]
+		},
+		{
+			fieldname: "priority",
+			label: __("Issue Priority"),
+			fieldtype: "Link",
+			options: "Issue Priority"
+		},
+		{
+			fieldname: "customer",
+			label: __("Customer"),
+			fieldtype: "Link",
+			options: "Customer"
+		},
+		{
+			fieldname: "project",
+			label: __("Project"),
+			fieldtype: "Link",
+			options: "Project"
+		},
+		{
+			fieldname: "assigned_to",
+			label: __("Assigned To"),
+			fieldtype: "Link",
+			options: "User"
+		}
+	],
+	after_datatable_render: function(datatable_obj) {
+		$(datatable_obj.wrapper).find(".dt-row-0").find('input[type=checkbox]').click();
+	},
+	get_datatable_options(options) {
+		return Object.assign(options, {
+			checkboxColumn: true,
+			events: {
+				onCheckRow: function(data) {
+					if (data && data.length) {
+						row_name = data[2].content;
+						row_values = data.slice(3).map(function(column) {
+							return column.content;
+						})
+						entry  = {
+							'name': row_name,
+							'values': row_values
+						}
+
+						let raw_data = frappe.query_report.chart.data;
+						let new_datasets = raw_data.datasets;
+
+						var found = false;
+
+						for(var i=0; i < new_datasets.length; i++){
+							if (new_datasets[i].name == row_name){
+								found = true;
+								new_datasets.splice(i,1);
+								break;
+							}
+						}
+
+						if (!found){
+							new_datasets.push(entry);
+						}
+
+						let new_data = {
+							labels: raw_data.labels,
+							datasets: new_datasets
+						}
+
+						setTimeout(() => {
+							frappe.query_report.chart.update(new_data)
+						},500)
+
+
+						setTimeout(() => {
+							frappe.query_report.chart.draw(true);
+						}, 1000)
+
+						frappe.query_report.raw_chart_data = new_data;
+					}
+				},
+			}
+		});
+	}
+};
\ No newline at end of file
diff --git a/erpnext/support/report/issue_analytics/issue_analytics.json b/erpnext/support/report/issue_analytics/issue_analytics.json
new file mode 100644
index 0000000..dd18498
--- /dev/null
+++ b/erpnext/support/report/issue_analytics/issue_analytics.json
@@ -0,0 +1,26 @@
+{
+ "add_total_row": 1,
+ "columns": [],
+ "creation": "2020-10-09 19:52:10.227317",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-10-11 19:43:19.358625",
+ "modified_by": "Administrator",
+ "module": "Support",
+ "name": "Issue Analytics",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Issue",
+ "report_name": "Issue Analytics",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Support Team"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/support/report/issue_analytics/issue_analytics.py b/erpnext/support/report/issue_analytics/issue_analytics.py
new file mode 100644
index 0000000..0b62915
--- /dev/null
+++ b/erpnext/support/report/issue_analytics/issue_analytics.py
@@ -0,0 +1,222 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import json
+from six import iteritems
+from frappe import _, scrub
+from frappe.utils import getdate, flt, add_to_date, add_days
+from erpnext.accounts.utils import get_fiscal_year
+
+def execute(filters=None):
+	return IssueAnalytics(filters).run()
+
+class IssueAnalytics(object):
+	def __init__(self, filters=None):
+		"""Issue Analytics Report"""
+		self.filters = frappe._dict(filters or {})
+		self.get_period_date_ranges()
+
+	def run(self):
+		self.get_columns()
+		self.get_data()
+		self.get_chart_data()
+
+		return self.columns, self.data, None, self.chart
+
+	def get_columns(self):
+		self.columns = []
+
+		if self.filters.based_on == 'Customer':
+			self.columns.append({
+				'label': _('Customer'),
+				'options': 'Customer',
+				'fieldname': 'customer',
+				'fieldtype': 'Link',
+				'width': 200
+			})
+
+		elif self.filters.based_on == 'Assigned To':
+			self.columns.append({
+				'label': _('User'),
+				'fieldname': 'user',
+				'fieldtype': 'Link',
+				'options': 'User',
+				'width': 200
+			})
+
+		elif self.filters.based_on == 'Issue Type':
+			self.columns.append({
+				'label': _('Issue Type'),
+				'fieldname': 'issue_type',
+				'fieldtype': 'Link',
+				'options': 'Issue Type',
+				'width': 200
+			})
+
+		elif self.filters.based_on == 'Issue Priority':
+			self.columns.append({
+				'label': _('Issue Priority'),
+				'fieldname': 'priority',
+				'fieldtype': 'Link',
+				'options': 'Issue Priority',
+				'width': 200
+			})
+
+		for end_date in self.periodic_daterange:
+			period = self.get_period(end_date)
+			self.columns.append({
+				'label': _(period),
+				'fieldname': scrub(period),
+				'fieldtype': 'Int',
+				'width': 120
+			})
+
+		self.columns.append({
+			'label': _('Total'),
+			'fieldname': 'total',
+			'fieldtype': 'Int',
+			'width': 120
+		})
+
+	def get_data(self):
+		self.get_issues()
+		self.get_rows()
+
+	def get_period(self, date):
+		months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+		if self.filters.range == 'Weekly':
+			period = 'Week ' + str(date.isocalendar()[1])
+		elif self.filters.range == 'Monthly':
+			period = str(months[date.month - 1])
+		elif self.filters.range == 'Quarterly':
+			period = 'Quarter ' + str(((date.month - 1) // 3) + 1)
+		else:
+			year = get_fiscal_year(date, self.filters.company)
+			period = str(year[0])
+
+		if getdate(self.filters.from_date).year != getdate(self.filters.to_date).year and self.filters.range != 'Yearly':
+			period += ' ' + str(date.year)
+
+		return period
+
+	def get_period_date_ranges(self):
+		from dateutil.relativedelta import relativedelta, MO
+		from_date, to_date = getdate(self.filters.from_date), getdate(self.filters.to_date)
+
+		increment = {
+			'Monthly': 1,
+			'Quarterly': 3,
+			'Half-Yearly': 6,
+			'Yearly': 12
+		}.get(self.filters.range, 1)
+
+		if self.filters.range in ['Monthly', 'Quarterly']:
+			from_date = from_date.replace(day=1)
+		elif self.filters.range == 'Yearly':
+			from_date = get_fiscal_year(from_date)[1]
+		else:
+			from_date = from_date + relativedelta(from_date, weekday=MO(-1))
+
+		self.periodic_daterange = []
+		for dummy in range(1, 53):
+			if self.filters.range == 'Weekly':
+				period_end_date = add_days(from_date, 6)
+			else:
+				period_end_date = add_to_date(from_date, months=increment, days=-1)
+
+			if period_end_date > to_date:
+				period_end_date = to_date
+
+			self.periodic_daterange.append(period_end_date)
+
+			from_date = add_days(period_end_date, 1)
+			if period_end_date == to_date:
+				break
+
+	def get_issues(self):
+		filters = self.get_common_filters()
+		self.field_map = {
+			'Customer': 'customer',
+			'Issue Type': 'issue_type',
+			'Issue Priority': 'priority',
+			'Assigned To': '_assign'
+		}
+
+		self.entries = frappe.db.get_all('Issue',
+			fields=[self.field_map.get(self.filters.based_on), 'name', 'opening_date'],
+			filters=filters,
+			debug=1
+		)
+
+	def get_common_filters(self):
+		filters = {}
+		filters['opening_date'] = ('between', [self.filters.from_date, self.filters.to_date])
+
+		if self.filters.get('assigned_to'):
+			filters['_assign'] = ('like', '%' + self.filters.get('assigned_to') + '%')
+
+		for entry in ['company', 'status', 'priority', 'customer', 'project']:
+			if self.filters.get(entry):
+				filters[entry] = self.filters.get(entry)
+
+		return filters
+
+	def get_rows(self):
+		self.data = []
+		self.get_periodic_data()
+
+		for entity, period_data in iteritems(self.issue_periodic_data):
+			if self.filters.based_on == 'Customer':
+				row = {'customer': entity}
+			elif self.filters.based_on == 'Assigned To':
+				row = {'user': entity}
+			elif self.filters.based_on == 'Issue Type':
+				row = {'issue_type': entity}
+			elif self.filters.based_on == 'Issue Priority':
+				row = {'priority': entity}
+
+			total = 0
+			for end_date in self.periodic_daterange:
+				period = self.get_period(end_date)
+				amount = flt(period_data.get(period, 0.0))
+				row[scrub(period)] = amount
+				total += amount
+
+			row['total'] = total
+
+			self.data.append(row)
+
+	def get_periodic_data(self):
+		self.issue_periodic_data = frappe._dict()
+
+		for d in self.entries:
+			period = self.get_period(d.get('opening_date'))
+
+			if self.filters.based_on == 'Assigned To':
+				if d._assign:
+					for entry in json.loads(d._assign):
+						self.issue_periodic_data.setdefault(entry, frappe._dict()).setdefault(period, 0.0)
+						self.issue_periodic_data[entry][period] += 1
+
+			else:
+				field = self.field_map.get(self.filters.based_on)
+				value = d.get(field)
+				if not value:
+					value = _('Not Specified')
+
+				self.issue_periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0.0)
+				self.issue_periodic_data[value][period] += 1
+
+	def get_chart_data(self):
+		length = len(self.columns)
+		labels = [d.get('label') for d in self.columns[1:length-1]]
+		self.chart = {
+			'data': {
+				'labels': labels,
+				'datasets': []
+			},
+			'type': 'line'
+		}
\ No newline at end of file
diff --git a/erpnext/support/report/issue_analytics/test_issue_analytics.py b/erpnext/support/report/issue_analytics/test_issue_analytics.py
new file mode 100644
index 0000000..432906d
--- /dev/null
+++ b/erpnext/support/report/issue_analytics/test_issue_analytics.py
@@ -0,0 +1,211 @@
+from __future__ import unicode_literals
+import unittest
+import frappe
+from frappe.utils import getdate, add_months
+from erpnext.support.report.issue_analytics.issue_analytics import execute
+from erpnext.support.doctype.issue.test_issue import make_issue, create_customer
+from erpnext.support.doctype.service_level_agreement.test_service_level_agreement import create_service_level_agreements_for_issues
+from frappe.desk.form.assign_to import add as add_assignment
+
+months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+class TestIssueAnalytics(unittest.TestCase):
+	@classmethod
+	def setUpClass(self):
+		frappe.db.sql("delete from `tabIssue` where company='_Test Company'")
+		frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
+
+		current_month_date = getdate()
+		last_month_date = add_months(current_month_date, -1)
+		self.current_month = str(months[current_month_date.month - 1]).lower() + '_' + str(current_month_date.year)
+		self.last_month = str(months[last_month_date.month - 1]).lower() + '_' + str(last_month_date.year)
+
+	def test_issue_analytics(self):
+		create_service_level_agreements_for_issues()
+		create_issue_types()
+		create_records()
+
+		self.compare_result_for_customer()
+		self.compare_result_for_issue_type()
+		self.compare_result_for_issue_priority()
+		self.compare_result_for_assignment()
+
+	def compare_result_for_customer(self):
+		filters = {
+			'company': '_Test Company',
+			'based_on': 'Customer',
+			'from_date': add_months(getdate(), -1),
+			'to_date': getdate(),
+			'range': 'Monthly'
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'customer': '__Test Customer 2',
+				self.last_month: 1.0,
+				self.current_month: 0.0,
+				'total': 1.0
+			},
+			{
+				'customer': '__Test Customer 1',
+				self.last_month: 0.0,
+				self.current_month: 1.0,
+				'total': 1.0
+			},
+			{
+				'customer': '__Test Customer',
+				self.last_month: 1.0,
+				self.current_month: 1.0,
+				'total': 2.0
+			}
+		]
+
+		self.assertEqual(expected_data, report[1]) # rows
+		self.assertEqual(len(report[0]), 4) # cols
+
+	def compare_result_for_issue_type(self):
+		filters = {
+			'company': '_Test Company',
+			'based_on': 'Issue Type',
+			'from_date': add_months(getdate(), -1),
+			'to_date': getdate(),
+			'range': 'Monthly'
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'issue_type': 'Discomfort',
+				self.last_month: 1.0,
+				self.current_month: 0.0,
+				'total': 1.0
+			},
+			{
+				'issue_type': 'Service Request',
+				self.last_month: 0.0,
+				self.current_month: 1.0,
+				'total': 1.0
+			},
+			{
+				'issue_type': 'Bug',
+				self.last_month: 1.0,
+				self.current_month: 1.0,
+				'total': 2.0
+			}
+		]
+
+		self.assertEqual(expected_data, report[1]) # rows
+		self.assertEqual(len(report[0]), 4) # cols
+
+	def compare_result_for_issue_priority(self):
+		filters = {
+			'company': '_Test Company',
+			'based_on': 'Issue Priority',
+			'from_date': add_months(getdate(), -1),
+			'to_date': getdate(),
+			'range': 'Monthly'
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'priority': 'Medium',
+				self.last_month: 1.0,
+				self.current_month: 1.0,
+				'total': 2.0
+			},
+			{
+				'priority': 'Low',
+				self.last_month: 1.0,
+				self.current_month: 0.0,
+				'total': 1.0
+			},
+			{
+				'priority': 'High',
+				self.last_month: 0.0,
+				self.current_month: 1.0,
+				'total': 1.0
+			}
+		]
+
+		self.assertEqual(expected_data, report[1]) # rows
+		self.assertEqual(len(report[0]), 4) # cols
+
+	def compare_result_for_assignment(self):
+		filters = {
+			'company': '_Test Company',
+			'based_on': 'Assigned To',
+			'from_date': add_months(getdate(), -1),
+			'to_date': getdate(),
+			'range': 'Monthly'
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'user': 'test@example.com',
+				self.last_month: 1.0,
+				self.current_month: 1.0,
+				'total': 2.0
+			},
+			{
+				'user': 'test1@example.com',
+				self.last_month: 2.0,
+				self.current_month: 1.0,
+				'total': 3.0
+			}
+		]
+
+		self.assertEqual(expected_data, report[1]) # rows
+		self.assertEqual(len(report[0]), 4) # cols
+
+
+def create_issue_types():
+	for entry in ['Bug', 'Service Request', 'Discomfort']:
+		if not frappe.db.exists('Issue Type', entry):
+			frappe.get_doc({
+				'doctype': 'Issue Type',
+				'__newname': entry
+			}).insert()
+
+
+def create_records():
+	create_customer("__Test Customer", "_Test SLA Customer Group", "__Test SLA Territory")
+	create_customer("__Test Customer 1", "_Test SLA Customer Group", "__Test SLA Territory")
+	create_customer("__Test Customer 2", "_Test SLA Customer Group", "__Test SLA Territory")
+
+	current_month_date = getdate()
+	last_month_date = add_months(current_month_date, -1)
+
+	issue = make_issue(current_month_date, "__Test Customer", 2, "High", "Bug")
+	add_assignment({
+		"assign_to": ["test@example.com"],
+		"doctype": "Issue",
+		"name": issue.name
+	})
+
+	issue = make_issue(last_month_date, "__Test Customer", 2, "Low", "Bug")
+	add_assignment({
+		"assign_to": ["test1@example.com"],
+		"doctype": "Issue",
+		"name": issue.name
+	})
+
+	issue = make_issue(current_month_date, "__Test Customer 1", 2, "Medium", "Service Request")
+	add_assignment({
+		"assign_to": ["test1@example.com"],
+		"doctype": "Issue",
+		"name": issue.name
+	})
+
+	issue = make_issue(last_month_date, "__Test Customer 2", 2, "Medium", "Discomfort")
+	add_assignment({
+		"assign_to": ["test@example.com", "test1@example.com"],
+		"doctype": "Issue",
+		"name": issue.name
+	})
\ No newline at end of file
diff --git a/erpnext/templates/generators/job_opening.html b/erpnext/templates/generators/job_opening.html
index f92e72e..c562db3 100644
--- a/erpnext/templates/generators/job_opening.html
+++ b/erpnext/templates/generators/job_opening.html
@@ -13,10 +13,21 @@
 {%- if description -%}
 <div>{{ description }}</div>
 {% endif %}
+
+{%- if publish_salary_range -%} 
+<div><b>{{_("Salary range per month")}}: </b>{{ frappe.format_value(frappe.utils.flt(lower_range), currency=currency) }} - {{ frappe.format_value(frappe.utils.flt(upper_range), currency=currency) }}</div>
+{% endif %}
+
 <p style='margin-top: 30px'>
-	<a class='btn btn-primary'
+	{%- if job_application_route -%}
+	<a class='btn btn-primary' 
+	href='/{{job_application_route}}?new=1&job_title={{ doc.name }}'>
+	{{ _("Apply Now") }}</a>
+	{% else %}
+	<a class='btn btn-primary' 
 	href='/job_application?new=1&job_title={{ doc.name }}'>
 	{{ _("Apply Now") }}</a>
+	{% endif %}
 </p>
 
 {% endblock %}