Merge branch 'version-13-beta-pre-release' into version-13-beta
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 5fbf06d..a754e13 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
 from erpnext.hooks import regional_overrides
 from frappe.utils import getdate
 
-__version__ = '13.0.0-beta.9'
+__version__ = '13.0.0-beta.11'
 
 def get_default_company(user=None):
 	'''Get default company for user'''
@@ -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/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json
index 3fc109b..849df18 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json
@@ -910,98 +910,8 @@
             },
             "is_group": 1
         },
-        "Passiva": {
+        "Passiva - Verbindlichkeiten": {
             "root_type": "Liability",
-            "A - Eigenkapital": {
-                "account_type": "Equity",
-                "is_group": 1,
-                "I - Gezeichnetes Kapital": {
-                    "account_type": "Equity",
-					"is_group": 1,
-					"Gezeichnetes Kapital": {
-						"account_type": "Equity",
-						"account_number": "2900"
-					},
-					"Ausstehende Einlagen auf das gezeichnete Kapital": {
-						"account_number": "2910",
-						"is_group": 1
-					}
-                },
-                "II - Kapitalr\u00fccklage": {
-                    "account_type": "Equity",
-					"is_group": 1,
-					"Kapitalr\u00fccklage": {
-						"account_number": "2920"
-					}
-                },
-                "III - Gewinnr\u00fccklagen": {
-                    "account_type": "Equity",
-                    "1 - gesetzliche R\u00fccklage": {
-                        "account_type": "Equity",
-						"is_group": 1,
-						"Gesetzliche R\u00fccklage": {
-							"account_number": "2930"
-						}
-                    },
-                    "2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
-                        "account_type": "Equity",
-                        "is_group": 1
-                    },
-                    "3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
-                        "account_type": "Equity",
-						"is_group": 1,
-						"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
-							"account_number": "2950"
-						}
-                    },
-                    "4 - andere Gewinnr\u00fccklagen": {
-                        "account_type": "Equity",
-                        "is_group": 1,
-                        "Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
-                            "is_group": 1,
-                            "Gewinnr\u00fccklagen (BilMoG)": {
-                                "account_number": "2963"
-                            },
-                            "Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
-                                "account_number": "2964"
-                            },
-                            "Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
-                                "account_number": "2965"
-                            },
-                            "Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
-                                "account_number": "2966"
-                            }
-                        },
-                        "Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
-                            "account_number": "2967"
-                        },
-                        "Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
-                            "account_number": "2968"
-                        },
-                        "Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
-                            "account_number": "2969"
-                        }
-                    },
-                    "is_group": 1
-                },
-                "IV - Gewinnvortrag/Verlustvortrag": {
-                    "account_type": "Equity",
-                    "is_group": 1,
-					"Gewinnvortrag vor Verwendung": {
-						"account_number": "2970"
-					},
-					"Verlustvortrag vor Verwendung": {
-						"account_number": "2978"
-					}
-                },
-                "V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
-                    "account_type": "Equity",
-                    "is_group": 1
-                },
-                "Einlagen stiller Gesellschafter": {
-                    "account_number": "9295"
-                }
-            },
             "B - R\u00fcckstellungen": {
                 "is_group": 1,
                 "1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
@@ -1618,6 +1528,143 @@
             },
             "is_group": 1
         },
+        "Passiva - Eigenkapital": {
+            "root_type": "Equity",
+            "A - Eigenkapital": {
+                "account_type": "Equity",
+                "is_group": 1,
+                "I - Gezeichnetes Kapital": {
+                    "account_type": "Equity",
+                    "is_group": 1,
+                    "Gezeichnetes Kapital": {
+                        "account_number": "2900",
+                        "account_type": "Equity"
+                    },
+                    "Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
+                        "account_number": "2901"
+                    },
+                    "Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
+                        "account_number": "2902"
+                    },
+                    "Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
+                        "account_number": "2903"
+                    },
+                    "R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
+                        "account_number": "2906"
+                    },
+                    "Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
+                        "account_number": "2907"
+                    },
+                    "Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
+                        "account_number": "2908"
+                    },
+                    "Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
+                        "account_number": "2910"
+                    }
+                },
+                "II - Kapitalr\u00fccklage": {
+                    "account_type": "Equity",
+                    "is_group": 1,
+                    "Kapitalr\u00fccklage": {
+                        "account_number": "2920"
+                    },
+                    "Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
+                        "account_number": "2925"
+                    },
+                    "Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
+                        "account_number": "2926"
+                    },
+                    "Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
+                        "account_number": "2927"
+                    },
+                    "Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
+                        "account_number": "2928"
+                    },
+                    "Nachschusskapital (Gegenkonto 1299)": {
+                        "account_number": "2929"
+                    }
+                },
+                "III - Gewinnr\u00fccklagen": {
+                    "account_type": "Equity",
+                    "1 - gesetzliche R\u00fccklage": {
+                        "account_type": "Equity",
+                        "is_group": 1,
+                        "Gesetzliche R\u00fccklage": {
+                            "account_number": "2930"
+                        }
+                    },
+                    "2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
+                        "account_type": "Equity",
+                        "is_group": 1,
+                        "R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
+                            "account_number": "2935"
+                        }
+                    },
+                    "3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
+                        "account_type": "Equity",
+                        "is_group": 1,
+                        "Satzungsm\u00e4\u00dfige R\u00fccklagen": {
+                            "account_number": "2950"
+                        }
+                    },
+                    "4 - andere Gewinnr\u00fccklagen": {
+                        "account_type": "Equity",
+                        "is_group": 1,
+                        "Andere Gewinnr\u00fccklagen": {
+                            "account_number": "2960"
+                        },
+                        "Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
+                            "account_number": "2961"
+                        },
+                        "Eigenkapitalanteil von Wertaufholungen": {
+                            "account_number": "2962"
+                        },
+                        "Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
+                            "is_group": 1,
+                            "Gewinnr\u00fccklagen (BilMoG)": {
+                                "account_number": "2963"
+                            },
+                            "Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
+                                "account_number": "2964"
+                            },
+                            "Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
+                                "account_number": "2965"
+                            },
+                            "Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
+                                "account_number": "2966"
+                            }
+                        },
+                        "Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
+                            "account_number": "2967"
+                        },
+                        "Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
+                            "account_number": "2968"
+                        },
+                        "Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
+                            "account_number": "2969"
+                        }
+                    },
+                    "is_group": 1
+                },
+                "IV - Gewinnvortrag/Verlustvortrag": {
+                    "account_type": "Equity",
+                    "is_group": 1,
+                    "Gewinnvortrag vor Verwendung": {
+                        "account_number": "2970"
+                    },
+                    "Verlustvortrag vor Verwendung": {
+                        "account_number": "2978"
+                    }
+                },
+                "V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
+                    "account_type": "Equity",
+                    "is_group": 1
+                },
+                "Einlagen stiller Gesellschafter": {
+                    "account_number": "9295"
+                }
+            }
+        },
         "1 - Umsatzerl\u00f6se": {
             "root_type": "Income",
             "is_group": 1,
diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py
index 113bea0..533eda3 100644
--- a/erpnext/accounts/doctype/account/test_account.py
+++ b/erpnext/accounts/doctype/account/test_account.py
@@ -254,7 +254,8 @@
 			account_name = kwargs.get('account_name'),
 			account_type = kwargs.get('account_type'),
 			parent_account = kwargs.get('parent_account'),
-			company = kwargs.get('company')
+			company = kwargs.get('company'),
+			account_currency = kwargs.get('account_currency')
 		))
 
 		account.save()
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/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 41f9ce0..a3c29b6 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -21,6 +21,7 @@
   "book_asset_depreciation_entry_automatically",
   "add_taxes_from_item_tax_template",
   "automatically_fetch_payment_terms",
+  "delete_linked_ledger_entries",
   "deferred_accounting_settings_section",
   "automatically_process_deferred_accounting_entry",
   "book_deferred_entries_based_on",
@@ -219,6 +220,12 @@
    "fieldtype": "Select",
    "label": "Book Deferred Entries Based On",
    "options": "Days\nMonths"
+  },
+  {
+   "default": "0",
+   "fieldname": "delete_linked_ledger_entries",
+   "fieldtype": "Check",
+   "label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
   }
  ],
  "icon": "icon-cog",
@@ -226,7 +233,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-10-13 11:32:52.268826",
+ "modified": "2021-01-05 13:04:00.118892",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
@@ -254,4 +261,4 @@
  "sort_field": "modified",
  "sort_order": "ASC",
  "track_changes": 1
-}
+}
\ No newline at end of file
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/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py
index 0f115f9..c5ec23c 100644
--- a/erpnext/accounts/doctype/budget/test_budget.py
+++ b/erpnext/accounts/doctype/budget/test_budget.py
@@ -122,8 +122,10 @@
 
 		frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
 
+		project = frappe.get_value("Project", {"project_name": "_Test Project"})
+
 		jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
-			"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
+			"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project=project, posting_date=nowdate())
 
 		self.assertRaises(BudgetError, jv.submit)
 
@@ -147,8 +149,11 @@
 
 		budget = make_budget(budget_against="Project")
 
+		project = frappe.get_value("Project", {"project_name": "_Test Project"})
+
 		jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
-			"_Test Bank - _TC", 250000, "_Test Cost Center - _TC", project="_Test Project", posting_date=nowdate())
+			"_Test Bank - _TC", 250000, "_Test Cost Center - _TC",
+			project=project, posting_date=nowdate())
 
 		self.assertRaises(BudgetError, jv.submit)
 
@@ -159,10 +164,10 @@
 
 		budget = make_budget(budget_against="Cost Center")
 		month = now_datetime().month
-		if month > 10:
-			month = 10
+		if month > 9:
+			month = 9
 
-		for i in range(month):
+		for i in range(month+1):
 			jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
 				"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
 
@@ -181,12 +186,14 @@
 
 		budget = make_budget(budget_against="Project")
 		month = now_datetime().month
-		if month > 10:
-			month = 10
+		if month > 9:
+			month = 9
 
-		for i in range(month):
+		project = frappe.get_value("Project", {"project_name": "_Test Project"})
+		for i in range(month + 1):
 			jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
-				"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True, project="_Test Project")
+				"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True,
+				project=project)
 
 			self.assertTrue(frappe.db.get_value("GL Entry",
 				{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
@@ -289,7 +296,7 @@
 	budget = frappe.new_doc("Budget")
 
 	if budget_against == "Project":
-		budget.project = "_Test Project"
+		budget.project = frappe.get_value("Project", {"project_name": "_Test Project"})
 	else:
 		budget.cost_center =cost_center or "_Test Cost Center - _TC"
 
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index c441274..b0a864f 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 \
@@ -137,9 +157,10 @@
 			frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
 				.format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
 
-		if self.cost_center and _check_is_group():
-			frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""")
-				.format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
+		if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
+			and self.cost_center and _check_is_group():
+			frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(
+				self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
 
 	def validate_party(self):
 		validate_party_frozen_disabled(self.party_type, self.party)
@@ -149,7 +170,7 @@
 		account_currency = get_account_currency(self.account)
 
 		if not self.account_currency:
-			self.account_currency = company_currency
+			self.account_currency = account_currency or company_currency
 
 		if account_currency != self.account_currency:
 			frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}")
diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py b/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py
index acc308e..3d80a97 100644
--- a/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py
+++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py
@@ -20,7 +20,8 @@
 				'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt']
 			},
 			{
-				'items': ['Item']
+				'label': _('Stock'),
+				'items': ['Item Groups', 'Item']
 			}
 		]
 	}
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/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index b56f8e5..5f003e0 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -160,7 +160,7 @@
 		self.assertFalse(gle)
 
 	def test_reverse_journal_entry(self):
-		from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry 
+		from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
 		jv = make_journal_entry("_Test Bank USD - _TC",
 			"Sales - _TC", 100, exchange_rate=50, save=False)
 
@@ -299,15 +299,20 @@
 
 	def test_jv_with_project(self):
 		from erpnext.projects.doctype.project.test_project import make_project
-		project = make_project({
-			'project_name': 'Journal Entry Project',
-			'project_template_name': 'Test Project Template',
-			'start_date': '2020-01-01'
-		})
+
+		if not frappe.db.exists("Project", {"project_name": "Journal Entry Project"}):
+			project = make_project({
+				'project_name': 'Journal Entry Project',
+				'project_template_name': 'Test Project Template',
+				'start_date': '2020-01-01'
+			})
+			project_name = project.name
+		else:
+			project_name = frappe.get_value("Project", {"project_name": "_Test Project"})
 
 		jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
 		for d in jv.accounts:
-			d.project = project.project_name
+			d.project = project_name
 		jv.voucher_type = "Bank Entry"
 		jv.multi_currency = 0
 		jv.cheque_no = "112233"
@@ -317,10 +322,10 @@
 
 		expected_values = {
 			"_Test Cash - _TC": {
-				"project": project.project_name
+				"project": project_name
 			},
 			"_Test Bank - _TC": {
-				"project": project.project_name
+				"project": project_name
 			}
 		}
 
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 e117471..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) {
@@ -401,6 +396,8 @@
 
 	set_account_currency_and_balance: function(frm, account, currency_field,
 			balance_field, callback_function) {
+
+		var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
 		if (frm.doc.posting_date && account) {
 			frappe.call({
 				method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
@@ -427,6 +424,14 @@
 
 									if(!frm.doc.paid_amount && frm.doc.received_amount)
 										frm.events.received_amount(frm);
+
+									if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
+										&& frm.doc.paid_amount != frm.doc.received_amount) {
+											if (company_currency != frm.doc.paid_from_account_currency &&
+												frm.doc.payment_type == "Pay") {
+													frm.doc.paid_amount = frm.doc.received_amount;
+												}
+										}
 								}
 							},
 							() => {
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_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index a3fcd0c..bd664c5 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -266,6 +266,8 @@
 		from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
 		if not self.pos_profile:
 			pos_profile = get_pos_profile(self.company) or {}
+			if not pos_profile:
+				frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
 			self.pos_profile = pos_profile.get('name')
 
 		profile = {}
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/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index af8d21d..f28cee7 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -56,6 +56,7 @@
 		self.assertEqual(details.get("discount_percentage"), 10)
 
 		prule = frappe.get_doc(test_record.copy())
+		prule.priority = 1
 		prule.applicable_for = "Customer"
 		prule.title = "_Test Pricing Rule for Customer"
 		self.assertRaises(MandatoryError, prule.insert)
@@ -261,6 +262,7 @@
 			"rate_or_discount": "Discount Percentage",
 			"rate": 0,
 			"discount_percentage": 17.5,
+			"priority": 1,
 			"company": "_Test Company"
 		}).insert()
 
@@ -557,6 +559,7 @@
 		"rate": args.rate or 0.0,
 		"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
 		"condition": args.condition or '',
+		"priority": 1,
 		"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
 	})
 
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index fb1fbe4..d163335 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -41,10 +41,11 @@
 	if not pricing_rules: return []
 
 	if apply_multiple_pricing_rules(pricing_rules):
-		pricing_rules = sorted_by_priority(pricing_rules)
+		pricing_rules = sorted_by_priority(pricing_rules, args, doc)
 		for pricing_rule in pricing_rules:
-			pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
-			if pricing_rule:
+			if isinstance(pricing_rule, list):
+				rules.extend(pricing_rule)
+			else:
 				rules.append(pricing_rule)
 	else:
 		pricing_rule = filter_pricing_rules(args, pricing_rules, doc)
@@ -53,17 +54,22 @@
 
 	return rules
 
-def sorted_by_priority(pricing_rules):
+def sorted_by_priority(pricing_rules, args, doc=None):
 	# If more than one pricing rules, then sort by priority
 	pricing_rules_list = []
 	pricing_rule_dict = {}
-	for pricing_rule in pricing_rules:
-		if not pricing_rule.get("priority"): continue
 
-		pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
+	for pricing_rule in pricing_rules:
+		pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
+		if pricing_rule:
+			if not pricing_rule.get('priority'):
+				pricing_rule['priority'] = 1
+
+			if pricing_rule.get('apply_multiple_pricing_rules'):
+				pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
 
 	for key in sorted(pricing_rule_dict):
-		pricing_rules_list.append(pricing_rule_dict.get(key))
+		pricing_rules_list.extend(pricing_rule_dict.get(key))
 
 	return pricing_rules_list or pricing_rules
 
@@ -144,9 +150,7 @@
 
 	if not apply_multiple_rule: return False
 
-	if (apply_multiple_rule
-		and len(apply_multiple_rule) == len(pricing_rules)):
-		return True
+	return True
 
 def _get_tree_conditions(args, parenttype, table, allow_blank=True):
 	field = frappe.scrub(parenttype)
@@ -264,18 +268,6 @@
 		if max_priority:
 			pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
 
-	# apply internal priority
-	all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory",
-		"supplier", "supplier_group", "campaign", "sales_partner", "variant_of"]
-
-	if len(pricing_rules) > 1:
-		for field_set in [["item_code", "variant_of", "item_group", "brand"],
-			["customer", "customer_group", "territory"], ["supplier", "supplier_group"]]:
-				remaining_fields = list(set(all_fields) - set(field_set))
-				if if_all_rules_same(pricing_rules, remaining_fields):
-					pricing_rules = apply_internal_priority(pricing_rules, field_set, args)
-					break
-
 	if pricing_rules and not isinstance(pricing_rules, list):
 		pricing_rules = list(pricing_rules)
 
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 7830cfd..06aa20b 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) {
@@ -268,8 +275,11 @@
 
 	supplier: function() {
 		var me = this;
-		if(this.frm.updating_party_details)
+
+		// Do not update if inter company reference is there as the details will already be updated
+		if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference)
 			return;
+
 		erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
 			{
 				posting_date: this.frm.doc.posting_date,
@@ -498,7 +508,7 @@
 frappe.ui.form.on("Purchase Invoice", {
 	setup: function(frm) {
 		frm.custom_make_buttons = {
-			'Purchase Invoice': 'Debit Note',
+			'Purchase Invoice': 'Return / Debit Note',
 			'Payment Entry': 'Payment'
 		}
 
@@ -511,15 +521,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/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index c64ffd8..451c936 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -57,8 +57,8 @@
   "set_warehouse",
   "rejected_warehouse",
   "col_break_warehouse",
+  "set_from_warehouse",
   "is_subcontracted",
-  "supplier_warehouse",
   "items_section",
   "update_stock",
   "scan_barcode",
@@ -515,6 +515,7 @@
   },
   {
    "depends_on": "update_stock",
+   "description": "Sets 'Accepted Warehouse' in each row of the items table.",
    "fieldname": "set_warehouse",
    "fieldtype": "Link",
    "label": "Set Accepted Warehouse",
@@ -544,17 +545,6 @@
    "print_hide": 1
   },
   {
-   "depends_on": "eval:doc.is_subcontracted==\"Yes\"",
-   "fieldname": "supplier_warehouse",
-   "fieldtype": "Link",
-   "label": "Supplier Warehouse",
-   "no_copy": 1,
-   "options": "Warehouse",
-   "print_hide": 1,
-   "print_width": "50px",
-   "width": "50px"
-  },
-  {
    "fieldname": "items_section",
    "fieldtype": "Section Break",
    "oldfieldtype": "Section Break",
@@ -1232,7 +1222,9 @@
    "fieldname": "inter_company_invoice_reference",
    "fieldtype": "Link",
    "label": "Inter Company Invoice Reference",
+   "no_copy": 1,
    "options": "Sales Invoice",
+   "print_hide": 1,
    "read_only": 1
   },
   {
@@ -1356,13 +1348,25 @@
    "fieldtype": "Link",
    "label": "Represents Company",
    "options": "Company"
+  },
+  {
+   "depends_on": "eval:doc.update_stock && (doc.is_subcontracted==\"Yes\" || doc.is_internal_supplier)",
+   "description": "Sets 'From Warehouse' in each row of the items table.",
+   "fieldname": "set_from_warehouse",
+   "fieldtype": "Link",
+   "label": "Set From Warehouse",
+   "no_copy": 1,
+   "options": "Warehouse",
+   "print_hide": 1,
+   "print_width": "50px",
+   "width": "50px"
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 204,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-11 12:46:12.796378",
+ "modified": "2020-12-26 20:49:03.305063",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index b52678e..dacd50a 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -443,7 +443,7 @@
 		else:
 			self.stock_received_but_not_billed = None
 			self.expenses_included_in_valuation = None
-		
+
 		self.negative_expense_to_be_booked = 0.0
 		gl_entries = []
 
@@ -457,7 +457,7 @@
 		self.make_internal_transfer_gl_entries(gl_entries)
 
 		gl_entries = make_regional_gl_entries(gl_entries, self)
-		
+
 		gl_entries = merge_similar_entries(gl_entries)
 
 		self.make_payment_gl_entries(gl_entries)
@@ -480,7 +480,7 @@
 		grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
 
 		if grand_total and not self.is_internal_transfer():
-				# Didnot use base_grand_total to book rounding loss gle
+				# Did not use base_grand_total to book rounding loss gle
 				grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
 					self.precision("grand_total"))
 				gl_entries.append(
@@ -511,8 +511,8 @@
 		voucher_wise_stock_value = {}
 		if self.update_stock:
 			for d in frappe.get_all('Stock Ledger Entry',
-				fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}):
-				voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference)
+				fields = ["voucher_detail_no", "stock_value_difference", "warehouse"], filters={'voucher_no': self.name}):
+				voucher_wise_stock_value.setdefault((d.voucher_detail_no, d.warehouse), d.stock_value_difference)
 
 		valuation_tax_accounts = [d.account_head for d in self.get("taxes")
 			if d.category in ('Valuation', 'Total and Valuation')
@@ -563,16 +563,17 @@
 							)
 
 					else:
-						gl_entries.append(
-							self.get_gl_dict({
-								"account": item.expense_account,
-								"against": self.supplier,
-								"debit": warehouse_debit_amount,
-								"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
-								"cost_center": item.cost_center,
-								"project": item.project or self.project
-							}, account_currency, item=item)
-						)
+						if not self.is_internal_transfer():
+							gl_entries.append(
+								self.get_gl_dict({
+									"account": item.expense_account,
+									"against": self.supplier,
+									"debit": warehouse_debit_amount,
+									"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+									"cost_center": item.cost_center,
+									"project": item.project or self.project
+								}, account_currency, item=item)
+							)
 
 					# Amount added through landed-cost-voucher
 					if landed_cost_entries:
@@ -582,7 +583,8 @@
 								"against": item.expense_account,
 								"cost_center": item.cost_center,
 								"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
-								"credit": flt(amount),
+								"credit": flt(amount["base_amount"]),
+								"credit_in_account_currency": flt(amount["amount"]),
 								"project": item.project or self.project
 							}, item=item))
 
@@ -624,13 +626,14 @@
 							if expense_booked_in_pr:
 								expense_account = service_received_but_not_billed_account
 
-					gl_entries.append(self.get_gl_dict({
-							"account": expense_account,
-							"against": self.supplier,
-							"debit": amount,
-							"cost_center": item.cost_center,
-							"project": item.project or self.project
-						}, account_currency, item=item))
+					if not self.is_internal_transfer():
+						gl_entries.append(self.get_gl_dict({
+								"account": expense_account,
+								"against": self.supplier,
+								"debit": amount,
+								"cost_center": item.cost_center,
+								"project": item.project or self.project
+							}, account_currency, item=item))
 
 					# If asset is bought through this document and not linked to PR
 					if self.update_stock and item.landed_cost_voucher_amount:
@@ -795,10 +798,10 @@
 
 		# Stock ledger value is not matching with the warehouse amount
 		if (self.update_stock and voucher_wise_stock_value.get(item.name) and
-			warehouse_debit_amount != flt(voucher_wise_stock_value.get(item.name), net_amt_precision)):
+			warehouse_debit_amount != flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)):
 
 			cost_of_goods_sold_account = self.get_company_default("default_expense_account")
-			stock_amount = flt(voucher_wise_stock_value.get(item.name), net_amt_precision)
+			stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
 			stock_adjustment_amt = warehouse_debit_amount - stock_amount
 
 			gl_entries.append(
@@ -999,10 +1002,10 @@
 			self.delete_auto_created_batches()
 
 		self.make_gl_entries_on_cancel()
-		
+
 		if self.update_stock == 1:
 			self.repost_future_sle_and_gle()
-		
+
 		self.update_project()
 		frappe.db.set(self, 'status', 'Cancelled')
 
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index c0506ba..ded293b 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -426,32 +426,37 @@
 		)
 
 	def test_total_purchase_cost_for_project(self):
-		make_project({'project_name':'_Test Project'})
+		if not frappe.db.exists("Project", {"project_name": "_Test Project for Purchase"}):
+			project = make_project({'project_name':'_Test Project for Purchase'})
+		else:
+			project = frappe.get_doc("Project", {"project_name": "_Test Project for Purchase"})
 
 		existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
-			from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""")
+			from `tabPurchase Invoice Item`
+			where project = '{0}'
+			and docstatus=1""".format(project.name))
 		existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
 
-		pi = make_purchase_invoice(currency="USD", conversion_rate=60, project="_Test Project")
-		self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
+		pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name)
+		self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
 			existing_purchase_cost + 15000)
 
-		pi1 = make_purchase_invoice(qty=10, project="_Test Project")
-		self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
+		pi1 = make_purchase_invoice(qty=10, project=project.name)
+		self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
 			existing_purchase_cost + 15500)
 
 		pi1.cancel()
-		self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
+		self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"),
 			existing_purchase_cost + 15000)
 
 		pi.cancel()
-		self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
+		self.assertEqual(frappe.db.get_value("Project", project.name, "total_purchase_cost"), existing_purchase_cost)
 
 	def test_return_purchase_invoice_with_perpetual_inventory(self):
 		pi = make_purchase_invoice(company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
 			cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
 
-		return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, 
+		return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2,
 			company = "_Test Company with perpetual inventory", warehouse= "Stores - TCP1",
 			cost_center = "Main - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1")
 
@@ -860,17 +865,17 @@
 		})
 
 		pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
-		pi.items[0].project = item_project.project_name
-		pi.project = project.project_name
+		pi.items[0].project = item_project.name
+		pi.project = project.name
 
 		pi.submit()
 
 		expected_values = {
 			"Creditors - _TC": {
-				"project": project.project_name
+				"project": project.name
 			},
 			"_Test Account Cost for Goods Sold - _TC": {
-				"project": item_project.project_name
+				"project": item_project.name
 			}
 		}
 
@@ -1026,7 +1031,7 @@
 	pi.is_return = args.is_return
 	pi.credit_to = args.return_against or "Creditors - _TC"
 	pi.is_subcontracted = args.is_subcontracted or "No"
-	if args.supplier_warehouse: 
+	if args.supplier_warehouse:
 		pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
 
 	pi.append("items", {
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index f6d76e5..1f7853d 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "hash",
  "creation": "2013-05-22 12:43:10",
  "doctype": "DocType",
@@ -87,6 +88,7 @@
   "po_detail",
   "purchase_receipt",
   "pr_detail",
+  "sales_invoice_item",
   "item_weight_details",
   "weight_per_unit",
   "total_weight",
@@ -553,8 +555,8 @@
    "fieldtype": "Link",
    "hidden": 1,
    "label": "Brand",
-   "print_hide": 1,
-   "options": "Brand"
+   "options": "Brand",
+   "print_hide": 1
   },
   {
    "fetch_from": "item_code.item_group",
@@ -562,9 +564,9 @@
    "fieldname": "item_group",
    "fieldtype": "Link",
    "label": "Item Group",
+   "options": "Item Group",
    "print_hide": 1,
-   "read_only": 1,
-   "options": "Item Group"
+   "read_only": 1
   },
   {
    "description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges",
@@ -759,10 +761,11 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:parent.is_internal_supplier && parent.update_stock",
    "fieldname": "from_warehouse",
    "fieldtype": "Link",
    "ignore_user_permissions": 1,
-   "label": "Supplier Warehouse",
+   "label": "From Warehouse",
    "options": "Warehouse"
   },
   {
@@ -779,11 +782,20 @@
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "fieldname": "sales_invoice_item",
+   "fieldtype": "Data",
+   "label": "Sales Invoice Item",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
- "modified": "2020-08-20 11:48:01.398356",
+ "links": [],
+ "modified": "2020-12-26 17:20:36.415791",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice Item",
@@ -791,4 +803,4 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC"
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 5efc32e..f2a62cd 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) {
@@ -126,16 +130,15 @@
 
 		this.set_default_print_format();
 		if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
-			frappe.model.with_doc("Customer", me.frm.doc.customer, function() {
-				var customer = frappe.model.get_doc("Customer", me.frm.doc.customer);
-				var internal = customer.is_internal_customer;
-				var disabled = customer.disabled;
-				if (internal == 1 && disabled == 0) {
-					me.frm.add_custom_button("Inter Company Invoice", function() {
-						me.make_inter_company_invoice();
-					}, __('Create'));
-				}
-			});
+			let internal = me.frm.doc.is_internal_customer;
+			if (internal) {
+				let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Invoice" :
+					"Inter Company Purchase Invoice";
+
+				me.frm.add_custom_button(button_label, function() {
+					me.make_inter_company_invoice();
+				}, __('Create'));
+			}
 		}
 	},
 
@@ -571,15 +574,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: {
@@ -592,7 +586,7 @@
 
 		frm.custom_make_buttons = {
 			'Delivery Note': 'Delivery',
-			'Sales Invoice': 'Sales Return',
+			'Sales Invoice': 'Return / Credit Note',
 			'Payment Request': 'Payment Request',
 			'Payment Entry': 'Payment'
 		},
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 6799fb9..447cee4 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -60,6 +60,8 @@
   "ignore_pricing_rule",
   "sec_warehouse",
   "set_warehouse",
+  "column_break_55",
+  "set_target_warehouse",
   "items_section",
   "update_stock",
   "scan_barcode",
@@ -1969,13 +1971,24 @@
    "label": "Represents Company",
    "options": "Company",
    "read_only": 1
+  },
+  {
+   "fieldname": "column_break_55",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "eval: doc.is_internal_customer && doc.update_stock",
+   "fieldname": "set_target_warehouse",
+   "fieldtype": "Link",
+   "label": "Set Target Warehouse",
+   "options": "Warehouse"
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 181,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-11 12:48:31.769958",
+ "modified": "2020-12-25 22:57:32.555067",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 40009ac..f45e076 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -4,9 +4,9 @@
 from __future__ import unicode_literals
 import frappe, erpnext
 import frappe.defaults
-from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, get_link_to_form
+from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate
 from frappe import _, msgprint, throw
-from erpnext.accounts.party import get_party_account, get_due_date
+from erpnext.accounts.party import get_party_account, get_due_date, get_party_details
 from frappe.model.mapper import get_mapped_doc
 from erpnext.controllers.selling_controller import SellingController
 from erpnext.accounts.utils import get_account_currency
@@ -21,6 +21,8 @@
 from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
 	get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
 from erpnext.accounts.deferred_revenue import validate_service_stop_date
+from frappe.model.utils import get_fetch_values
+from frappe.contacts.doctype.address.address import get_address_display
 
 from erpnext.healthcare.utils import manage_invoice_submit_cancel
 
@@ -179,7 +181,10 @@
 
 		# this sequence because outstanding may get -ve
 		self.make_gl_entries()
-		
+
+		if self.update_stock == 1:
+			self.repost_future_sle_and_gle()
+
 		if self.update_stock == 1:
 			self.repost_future_sle_and_gle()
 
@@ -261,10 +266,10 @@
 			self.update_stock_ledger()
 
 		self.make_gl_entries_on_cancel()
-		
+
 		if self.update_stock == 1:
 			self.repost_future_sle_and_gle()
-		
+
 		frappe.db.set(self, 'status', 'Cancelled')
 
 		if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction":
@@ -549,7 +554,12 @@
 		self.against_income_account = ','.join(against_acc)
 
 	def add_remarks(self):
-		if not self.remarks: self.remarks = 'No Remarks'
+		if not self.remarks:
+			if self.po_no and self.po_date:
+				self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no,
+					formatdate(self.po_date))
+			else:
+				self.remarks = _("No Remarks")
 
 	def validate_auto_set_posting_time(self):
 		# Don't auto set the posting date and time if invoice is amended
@@ -1529,7 +1539,7 @@
 	details = get_inter_company_details(doc, doctype)
 	price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"] else doc.buying_price_list
 	valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1})
-	if not valid_price_list:
+	if not valid_price_list and not doc.is_internal_transfer():
 		frappe.throw(_("Selected Price List should have buying and selling fields checked."))
 
 	party = details.get("party")
@@ -1552,6 +1562,7 @@
 	if doctype in ["Sales Invoice", "Sales Order"]:
 		source_doc = frappe.get_doc(doctype, source_name)
 		target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
+		target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
 		source_document_warehouse_field = 'target_warehouse'
 		target_document_warehouse_field = 'from_warehouse'
 	else:
@@ -1565,6 +1576,7 @@
 
 	def set_missing_values(source, target):
 		target.run_method("set_missing_values")
+		set_purchase_references(target)
 
 	def update_details(source_doc, target_doc, source_parent):
 		target_doc.inter_company_invoice_reference = source_doc.name
@@ -1572,19 +1584,38 @@
 			currency = frappe.db.get_value('Supplier', details.get('party'), 'default_currency')
 			target_doc.company = details.get("company")
 			target_doc.supplier = details.get("party")
+			target_doc.is_internal_supplier = 1
+			target_doc.ignore_pricing_rule = 1
 			target_doc.buying_price_list = source_doc.selling_price_list
 
+			# Invert Addresses
+			update_address(target_doc, 'supplier_address', 'address_display', source_doc.company_address)
+			update_address(target_doc, 'shipping_address', 'shipping_address_display', source_doc.customer_address)
+
 			if currency:
 				target_doc.currency = currency
+
+			update_taxes(target_doc, party=target_doc.supplier, party_type='Supplier', company=target_doc.company,
+				doctype=target_doc.doctype, party_address=target_doc.supplier_address,
+				company_address=target_doc.shipping_address)
+
 		else:
 			currency = frappe.db.get_value('Customer', details.get('party'), 'default_currency')
 			target_doc.company = details.get("company")
 			target_doc.customer = details.get("party")
 			target_doc.selling_price_list = source_doc.buying_price_list
 
+			update_address(target_doc, 'company_address', 'company_address_display', source_doc.supplier_address)
+			update_address(target_doc, 'shipping_address_name', 'shipping_address', source_doc.shipping_address)
+			update_address(target_doc, 'customer_address', 'address_display', source_doc.shipping_address)
+
 			if currency:
 				target_doc.currency = currency
 
+			update_taxes(target_doc, party=target_doc.customer, party_type='Customer', company=target_doc.company,
+				doctype=target_doc.doctype, party_address=target_doc.customer_address,
+				company_address=target_doc.company_address, shipping_address_name=target_doc.shipping_address_name)
+
 	item_field_map = {
 		"doctype": target_doctype + " Item",
 		"field_no_map": [
@@ -1592,25 +1623,33 @@
 			"expense_account",
 			"cost_center",
 			"warehouse"
-		]
+		],
+		"field_map": {
+			'rate': 'rate',
+		}
 	}
 
-	if source_doc.get('update_stock'):
-		item_field_map.update({
-			'field_map': {
-				source_document_warehouse_field: target_document_warehouse_field,
-				'batch_no': 'batch_no',
-				'serial_no': 'serial_no'
-			}
+	if doctype in ["Sales Invoice", "Sales Order"]:
+		item_field_map["field_map"].update({
+			"name": target_detail_field,
 		})
 
+	if source_doc.get('update_stock'):
+		item_field_map["field_map"].update({
+			source_document_warehouse_field: target_document_warehouse_field,
+			'batch_no': 'batch_no',
+			'serial_no': 'serial_no'
+		})
 
 	doclist = get_mapped_doc(doctype, source_name,	{
 		doctype: {
 			"doctype": target_doctype,
 			"postprocess": update_details,
+			"set_target_warehouse": "set_from_warehouse",
 			"field_no_map": [
-				"taxes_and_charges"
+				"taxes_and_charges",
+				"set_warehouse",
+				"shipping_address"
 			]
 		},
 		doctype +" Item": item_field_map
@@ -1619,6 +1658,110 @@
 
 	return doclist
 
+def set_purchase_references(doc):
+	# add internal PO or PR links if any
+	if doc.is_internal_transfer():
+		if doc.doctype == 'Purchase Receipt':
+			so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference)
+
+			if so_item_map:
+				pd_item_map, parent_child_map, warehouse_map = \
+					get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
+
+				update_pr_items(doc, so_item_map, pd_item_map, parent_child_map, warehouse_map)
+
+		elif doc.doctype == 'Purchase Invoice':
+			dn_item_map, so_item_map = get_sales_invoice_details(doc.inter_company_invoice_reference)
+			# First check for Purchase receipt
+			if list(dn_item_map.values()):
+				pd_item_map, parent_child_map, warehouse_map = \
+					get_pd_details('Purchase Receipt Item', dn_item_map, 'delivery_note_item')
+
+				update_pi_items(doc, 'pr_detail', 'purchase_receipt',
+					dn_item_map, pd_item_map, parent_child_map, warehouse_map)
+
+			if list(so_item_map.values()):
+				pd_item_map, parent_child_map, warehouse_map = \
+					get_pd_details('Purchase Order Item', so_item_map, 'sales_order_item')
+
+				update_pi_items(doc, 'po_detail', 'purchase_order',
+					so_item_map, pd_item_map, parent_child_map, warehouse_map)
+
+def update_pi_items(doc, detail_field, parent_field, sales_item_map,
+	purchase_item_map, parent_child_map, warehouse_map):
+	for item in doc.get('items'):
+		item.set(detail_field, purchase_item_map.get(sales_item_map.get(item.sales_invoice_item)))
+		item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item)))
+		if doc.update_stock:
+			item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item))
+
+def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map):
+	for item in doc.get('items'):
+		item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item))
+		item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item))
+		item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
+
+def get_delivery_note_details(internal_reference):
+	so_item_map = {}
+
+	si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
+		filters={'parent': internal_reference})
+
+	for d in si_item_details:
+		so_item_map.setdefault(d.name, d.so_detail)
+
+	return so_item_map
+
+def get_sales_invoice_details(internal_reference):
+	dn_item_map = {}
+	so_item_map = {}
+
+	si_item_details = frappe.get_all('Sales Invoice Item', fields=['name', 'so_detail',
+		'dn_detail'], filters={'parent': internal_reference})
+
+	for d in si_item_details:
+		if d.dn_detail:
+			dn_item_map.setdefault(d.name, d.dn_detail)
+		if d.so_detail:
+			so_item_map.setdefault(d.name, d.so_detail)
+
+	return dn_item_map, so_item_map
+
+def get_pd_details(doctype, sd_detail_map, sd_detail_field):
+	pd_item_map = {}
+	accepted_warehouse_map = {}
+	parent_child_map = {}
+
+	pd_item_details = frappe.get_all(doctype,
+		fields=[sd_detail_field, 'name', 'warehouse', 'parent'], filters={sd_detail_field: ('in', list(sd_detail_map.values()))})
+
+	for d in pd_item_details:
+		pd_item_map.setdefault(d.get(sd_detail_field), d.name)
+		parent_child_map.setdefault(d.get(sd_detail_field), d.parent)
+		accepted_warehouse_map.setdefault(d.get(sd_detail_field), d.warehouse)
+
+	return pd_item_map, parent_child_map, accepted_warehouse_map
+
+def update_taxes(doc, party=None, party_type=None, company=None, doctype=None, party_address=None,
+	company_address=None, shipping_address_name=None, master_doctype=None):
+	# Update Party Details
+	party_details = get_party_details(party=party, party_type=party_type, company=company,
+		doctype=doctype, party_address=party_address, company_address=company_address,
+		shipping_address=shipping_address_name)
+
+	# Update taxes and charges if any
+	doc.taxes_and_charges = party_details.get('taxes_and_charges')
+	doc.set('taxes', party_details.get('taxes'))
+
+def update_address(doc, address_field, address_display_field, address_name):
+	doc.set(address_field, address_name)
+	fetch_values = get_fetch_values(doc.doctype, address_field, address_name)
+
+	for key, value in fetch_values.items():
+		doc.set(key, value)
+
+	doc.set(address_display_field, get_address_display(doc.get(address_field)))
+
 @frappe.whitelist()
 def get_loyalty_programs(customer):
 	''' sets applicable loyalty program to the customer or returns a list of applicable programs '''
@@ -1694,6 +1837,7 @@
 		where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
 	(company, mode_of_payment), as_dict=1)
 
+@frappe.whitelist()
 def create_dunning(source_name, target_doc=None):
 	from frappe.model.mapper import get_mapped_doc
 	from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index eb223ee..7cd1828 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -22,6 +22,7 @@
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
+from erpnext.stock.utils import get_incoming_rate
 
 class TestSalesInvoice(unittest.TestCase):
 	def make(self):
@@ -688,7 +689,7 @@
 		self.assertTrue(gle)
 
 	def test_pos_gl_entry_with_perpetual_inventory(self):
-		make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", 
+		make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
 			expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
 
 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
@@ -745,7 +746,7 @@
 		self.assertEqual(pos_return.get('payments')[0].amount, -1000)
 
 	def test_pos_change_amount(self):
-		make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1", 
+		make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
 			expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
 
 		pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
@@ -1573,17 +1574,17 @@
 		})
 
 		sales_invoice = create_sales_invoice(do_not_save=1)
-		sales_invoice.items[0].project = item_project.project_name
-		sales_invoice.project = project.project_name
+		sales_invoice.items[0].project = item_project.name
+		sales_invoice.project = project.name
 
 		sales_invoice.submit()
 
 		expected_values = {
 			"Debtors - _TC": {
-				"project": project.project_name
+				"project": project.name
 			},
 			"Sales - _TC": {
-				"project": item_project.project_name
+				"project": item_project.name
 			}
 		}
 
@@ -1770,59 +1771,82 @@
 		self.assertEqual(target_doc.company, "_Test Company 1")
 		self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
 
-	# def test_internal_transfer_gl_entry(self):
-	# 	## Create internal transfer account
-	# 	account = create_account(account_name="Unrealized Profit",
-	# 		parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
+	def test_internal_transfer_gl_entry(self):
+		## Create internal transfer account
+		account = create_account(account_name="Unrealized Profit",
+			parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
 
-	# 	frappe.db.set_value('Company', '_Test Company with perpetual inventory',
-	# 		'unrealized_profit_loss_account', account)
+		frappe.db.set_value('Company', '_Test Company with perpetual inventory',
+			'unrealized_profit_loss_account', account)
 
-	# 	customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
-	# 		"_Test Company with perpetual inventory")
+		customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
+			"_Test Company with perpetual inventory")
 
-	# 	create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
-	# 		"_Test Company with perpetual inventory")
+		create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
+			"_Test Company with perpetual inventory")
 
-	# 	si = create_sales_invoice(
-	# 		company = "_Test Company with perpetual inventory",
-	# 		customer = customer,
-	# 		debit_to = "Debtors - TCP1",
-	# 		warehouse = "Stores - TCP1",
-	# 		income_account = "Sales - TCP1",
-	# 		expense_account = "Cost of Goods Sold - TCP1",
-	# 		cost_center = "Main - TCP1",
-	# 		currency = "INR",
-	# 		do_not_save = 1
-	# 	)
+		si = create_sales_invoice(
+			company = "_Test Company with perpetual inventory",
+			customer = customer,
+			debit_to = "Debtors - TCP1",
+			warehouse = "Stores - TCP1",
+			income_account = "Sales - TCP1",
+			expense_account = "Cost of Goods Sold - TCP1",
+			cost_center = "Main - TCP1",
+			currency = "INR",
+			do_not_save = 1
+		)
 
-	# 	si.selling_price_list = "_Test Price List Rest of the World"
-	# 	si.update_stock = 1
-	# 	si.items[0].target_warehouse = 'Work In Progress - TCP1'
-	# 	add_taxes(si)
-	# 	si.save()
-	# 	si.submit()
+		si.selling_price_list = "_Test Price List Rest of the World"
+		si.update_stock = 1
+		si.items[0].target_warehouse = 'Work In Progress - TCP1'
+		add_taxes(si)
+		si.save()
 
-	# 	target_doc = make_inter_company_transaction("Sales Invoice", si.name)
-	# 	target_doc.company = '_Test Company with perpetual inventory'
-	# 	target_doc.items[0].warehouse = 'Finished Goods - TCP1'
-	# 	add_taxes(target_doc)
-	# 	target_doc.save()
-	# 	target_doc.submit()
+		rate = 0.0
+		for d in si.get('items'):
+			rate = get_incoming_rate({
+				"item_code": d.item_code,
+				"warehouse": d.warehouse,
+				"posting_date": si.posting_date,
+				"posting_time": si.posting_time,
+				"qty": -1 * flt(d.get('stock_qty')),
+				"serial_no": d.serial_no,
+				"company": si.company,
+				"voucher_type": 'Sales Invoice',
+				"voucher_no": si.name,
+				"allow_zero_valuation": d.get("allow_zero_valuation")
+			}, raise_error_if_no_rate=False)
 
-	# 	si_gl_entries = [
-	# 		["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
-	# 		["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
-	# 	]
+			rate = flt(rate, 2)
 
-	# 	check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
+		si.submit()
 
-	# 	pi_gl_entries = [
-	# 		["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
-	# 		["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
-	# 	]
+		target_doc = make_inter_company_transaction("Sales Invoice", si.name)
+		target_doc.company = '_Test Company with perpetual inventory'
+		target_doc.items[0].warehouse = 'Finished Goods - TCP1'
+		add_taxes(target_doc)
+		target_doc.save()
+		target_doc.submit()
 
-	# 	check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
+		tax_amount = flt(rate * (12/100), 2)
+		si_gl_entries = [
+			["_Test Account Excise Duty - TCP1", 0.0, tax_amount, nowdate()],
+			["Unrealized Profit - TCP1", tax_amount, 0.0, nowdate()]
+		]
+
+		check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
+
+		pi_gl_entries = [
+			["_Test Account Excise Duty - TCP1", tax_amount , 0.0, nowdate()],
+			["Unrealized Profit - TCP1", 0.0, tax_amount, nowdate()]
+		]
+
+		# Sale and Purchase both should be at valuation rate
+		self.assertEqual(si.items[0].rate, rate)
+		self.assertEqual(target_doc.items[0].rate, rate)
+
+		check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
 
 	def test_eway_bill_json(self):
 		si = make_sales_invoice_for_ewaybill()
@@ -1841,7 +1865,7 @@
 		self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
 		self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
 		self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
-	
+
 	def test_einvoice_submission_without_irn(self):
 		# init
 		frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
@@ -1857,27 +1881,10 @@
 		# reset
 		frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
 		frappe.flags.country = country
-	
+
 	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 +1937,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/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index 3695075..7a98aff 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -565,11 +565,12 @@
    "print_hide": 1
   },
   {
+   "depends_on": "eval: parent.is_internal_customer && parent.update_stock",
    "fieldname": "target_warehouse",
    "fieldtype": "Link",
    "hidden": 1,
    "ignore_user_permissions": 1,
-   "label": "Customer Warehouse (Optional)",
+   "label": "Target Warehouse",
    "no_copy": 1,
    "options": "Warehouse",
    "print_hide": 1
@@ -815,7 +816,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-23 19:59:04.879322",
+ "modified": "2020-12-26 17:25:04.090630",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
index b46de6c..429a9f3 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
@@ -34,6 +34,9 @@
 
 	validate_disabled(doc)
 
+	# Validate with existing taxes and charges template for unique tax category
+	validate_for_tax_category(doc)
+
 	for tax in doc.get("taxes"):
 		validate_taxes_and_charges(tax)
 		validate_inclusive_tax(tax, doc)
@@ -41,3 +44,7 @@
 def validate_disabled(doc):
 	if doc.is_default and doc.disabled:
 		frappe.throw(_("Disabled template must not be default template"))
+
+def validate_for_tax_category(doc):
+	if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}):
+		frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
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/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 552a5d4..e023b47 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -446,7 +446,7 @@
 		if not self.generate_invoice_at_period_start:
 			return False
 
-		if self.is_new_subscription():
+		if self.is_new_subscription() and getdate() >= getdate(self.current_invoice_start):
 			return True
 
 		# Check invoice dates and make sure it doesn't have outstanding invoices
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 64268b8..38b2284 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -39,7 +39,7 @@
 	party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
 	party = party_details[party_type.lower()]
 
-	if not ignore_permissions and not frappe.has_permission(party_type, "read", party):
+	if not ignore_permissions and not (frappe.has_permission(party_type, "read", party) or frappe.has_permission(party_type, "select", party)):
 		frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
 
 	party = frappe.get_doc(party_type, party)
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
index 9827e00..8eef2ad 100644
--- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
@@ -152,7 +152,7 @@
 					<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
 					<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
 					<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
-					<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
 					<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
 					<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
 				</tr>
diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
index 16bef56..2162a02 100644
--- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
+++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
@@ -47,21 +47,22 @@
 
 	for d in gl_entries:
 		asset_data = assets_details.get(d.against_voucher)
-		if not asset_data.get("accumulated_depreciation_amount"):
-			asset_data.accumulated_depreciation_amount = d.debit
-		else:
-			asset_data.accumulated_depreciation_amount += d.debit
+		if asset_data:
+			if not asset_data.get("accumulated_depreciation_amount"):
+				asset_data.accumulated_depreciation_amount = d.debit
+			else:
+				asset_data.accumulated_depreciation_amount += d.debit
 
-		row = frappe._dict(asset_data)
-		row.update({
-			"depreciation_amount": d.debit,
-			"depreciation_date": d.posting_date,
-			"amount_after_depreciation": (flt(row.gross_purchase_amount) -
-				flt(row.accumulated_depreciation_amount)),
-			"depreciation_entry": d.voucher_no
-		})
+			row = frappe._dict(asset_data)
+			row.update({
+				"depreciation_amount": d.debit,
+				"depreciation_date": d.posting_date,
+				"amount_after_depreciation": (flt(row.gross_purchase_amount) -
+					flt(row.accumulated_depreciation_amount)),
+				"depreciation_entry": d.voucher_no
+			})
 
-		data.append(row)
+			data.append(row)
 
 	return data
 
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index a36e7f8..cb4d9b4 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -49,12 +49,13 @@
 		elif d.po_detail:
 			purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, []))
 
-		expense_account = d.expense_account or aii_account_map.get(d.company)
+		expense_account = d.unrealized_profit_loss_account or d.expense_account \
+			or aii_account_map.get(d.company)
 
 		row = {
 			'item_code': d.item_code,
-			'item_name': item_record.item_name,
-			'item_group': item_record.item_group,
+			'item_name': item_record.item_name if item_record else d.item_name,
+			'item_group': item_record.item_group if item_record else d.item_group,
 			'description': d.description,
 			'invoice': d.parent,
 			'posting_date': d.posting_date,
@@ -315,7 +316,9 @@
 			`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
 			`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
 			`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
+			`tabPurchase Invoice`.unrealized_profit_loss_account,
 			`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
+			`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
 			`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
 			`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
 			`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index f54ceb0..998003a 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -76,7 +76,7 @@
 			'company': d.company,
 			'sales_order': d.sales_order,
 			'delivery_note': d.delivery_note,
-			'income_account': d.income_account,
+			'income_account': d.unrealized_profit_loss_account or d.income_account,
 			'cost_center': d.cost_center,
 			'stock_qty': d.stock_qty,
 			'stock_uom': d.stock_uom
@@ -379,6 +379,7 @@
 		select
 			`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
 			`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
+			`tabSales Invoice`.unrealized_profit_loss_account,
 			`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
 			`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
 			`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
index 57a1231..7195c7e 100644
--- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
+++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py
@@ -59,23 +59,111 @@
 
 def get_columns(filters):
 	return [
-		_("Payment Document") + ":: 100",
-		_("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":140",
-		_("Party Type") + "::100",
-		_("Party") + ":Dynamic Link/Party Type:140",
-		_("Posting Date") + ":Date:100",
-		_("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"),
-		_("Invoice Posting Date") + ":Date:130",
-		_("Payment Due Date") + ":Date:130",
-		_("Debit") + ":Currency:120",
-		_("Credit") + ":Currency:120",
-		_("Remarks") + "::150",
-		_("Age") +":Int:40",
-		"0-30:Currency:100",
-		"30-60:Currency:100",
-		"60-90:Currency:100",
-		_("90-Above") + ":Currency:100",
-		_("Delay in payment (Days)") + "::150"
+		{
+			"fieldname": "payment_document",
+			"label": _("Payment Document Type"),
+			"fieldtype": "Data",
+			"width": 100
+		},
+		{
+			"fieldname": "payment_entry",
+			"label": _("Payment Document"),
+			"fieldtype": "Dynamic Link",
+			"options": "payment_document",
+			"width": 160
+		},
+		{
+			"fieldname": "party_type",
+			"label": _("Party Type"),
+			"fieldtype": "Data",
+			"width": 100
+		},
+		{
+			"fieldname": "party",
+			"label": _("Party"),
+			"fieldtype": "Dynamic Link",
+			"options": "party_type",
+			"width": 160
+		},
+		{
+			"fieldname": "posting_date",
+			"label": _("Posting Date"),
+			"fieldtype": "Date",
+			"width": 100
+		},
+		{
+			"fieldname": "invoice",
+			"label": _("Invoice"),
+			"fieldtype": "Link",
+			"options": "Purchase Invoice" if filters.get("payment_type") == _("Outgoing") else "Sales Invoice",
+			"width": 160
+		},
+		{
+			"fieldname": "invoice_posting_date",
+			"label": _("Invoice Posting Date"),
+			"fieldtype": "Date",
+			"width": 100
+		},
+		{
+			"fieldname": "due_date",
+			"label": _("Payment Due Date"),
+			"fieldtype": "Date",
+			"width": 100
+		},
+		{
+			"fieldname": "debit",
+			"label": _("Debit"),
+			"fieldtype": "Currency",
+			"width": 140
+		},
+		{
+			"fieldname": "credit",
+			"label": _("Credit"),
+			"fieldtype": "Currency",
+			"width": 140
+		},
+		{
+			"fieldname": "remarks",
+			"label": _("Remarks"),
+			"fieldtype": "Data",
+			"width": 200
+		},
+		{
+			"fieldname": "age",
+			"label": _("Age"),
+			"fieldtype": "Int",
+			"width": 50
+		},
+		{
+			"fieldname": "range1",
+			"label": "0-30",
+			"fieldtype": "Currency",
+			"width": 140
+		},
+		{
+			"fieldname": "range2",
+			"label": "30-60",
+			"fieldtype": "Currency",
+			"width": 140
+		},
+		{
+			"fieldname": "range3",
+			"label": "60-90",
+			"fieldtype": "Currency",
+			"width": 140
+		},
+		{
+			"fieldname": "range4",
+			"label": _("90 Above"),
+			"fieldtype": "Currency",
+			"width": 140
+		},
+			{
+			"fieldname": "delay_in_payment",
+			"label": _("Delay in payment (Days)"),
+			"fieldtype": "Int",
+			"width": 100
+		}
 	]
 
 def get_conditions(filters):
diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py
index 9399e70..8ac749d 100644
--- a/erpnext/accounts/report/purchase_register/purchase_register.py
+++ b/erpnext/accounts/report/purchase_register/purchase_register.py
@@ -14,13 +14,15 @@
 	if not filters: filters = {}
 
 	invoice_list = get_invoices(filters, additional_query_columns)
-	columns, expense_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns)
+	columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts \
+		= get_columns(invoice_list, additional_table_columns)
 
 	if not invoice_list:
 		msgprint(_("No record found"))
 		return columns, invoice_list
 
 	invoice_expense_map = get_invoice_expense_map(invoice_list)
+	internal_invoice_map = get_internal_invoice_map(invoice_list)
 	invoice_expense_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
 		invoice_expense_map, expense_accounts)
 	invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
@@ -52,10 +54,17 @@
 		# map expense values
 		base_net_total = 0
 		for expense_acc in expense_accounts:
-			expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
+			if inv.is_internal_supplier and inv.company == inv.represents_company:
+				expense_amount = 0
+			else:
+				expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
 			base_net_total += expense_amount
 			row.append(expense_amount)
 
+		# Add amount in unrealized account
+		for account in unrealized_profit_loss_accounts:
+			row.append(flt(internal_invoice_map.get((inv.name, account))))
+
 		# net total
 		row.append(base_net_total or inv.base_net_total)
 
@@ -96,7 +105,8 @@
 			"width": 80
 		}
 	]
-	expense_accounts = tax_accounts = expense_columns = tax_columns = []
+	expense_accounts = tax_accounts = expense_columns = tax_columns = unrealized_profit_loss_accounts = \
+		unrealized_profit_loss_account_columns = []
 
 	if invoice_list:
 		expense_accounts = frappe.db.sql_list("""select distinct expense_account
@@ -112,17 +122,25 @@
 			and parent in (%s) order by account_head""" %
 			', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
 
+		unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
+			from `tabPurchase Invoice` where docstatus = 1 and name in (%s)
+			and ifnull(unrealized_profit_loss_account, '') != ''
+			order by unrealized_profit_loss_account""" %
+			', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
 
 	expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
+	unrealized_profit_loss_account_columns = [(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts]
+
 	for account in tax_accounts:
 		if account not in expense_accounts:
 			tax_columns.append(account + ":Currency/currency:120")
 
-	columns = columns + expense_columns + [_("Net Total") + ":Currency/currency:120"] + tax_columns + \
+	columns = columns + expense_columns + unrealized_profit_loss_account_columns + \
+		[_("Net Total") + ":Currency/currency:120"] + tax_columns + \
 		[_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120",
 			_("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"]
 
-	return columns, expense_accounts, tax_accounts
+	return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
 
 def get_conditions(filters):
 	conditions = ""
@@ -199,6 +217,19 @@
 
 	return invoice_expense_map
 
+def get_internal_invoice_map(invoice_list):
+	unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
+		base_net_total as amount from `tabPurchase Invoice` where name in (%s)
+		and is_internal_supplier = 1 and company = represents_company""" %
+		', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+
+	internal_invoice_map = {}
+	for d in unrealized_amount_details:
+		if d.unrealized_profit_loss_account:
+			internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
+
+	return internal_invoice_map
+
 def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
 	tax_details = frappe.db.sql("""
 		select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py
index b6e61b1..cb2c98b 100644
--- a/erpnext/accounts/report/sales_register/sales_register.py
+++ b/erpnext/accounts/report/sales_register/sales_register.py
@@ -15,13 +15,14 @@
 	if not filters: filters = frappe._dict({})
 
 	invoice_list = get_invoices(filters, additional_query_columns)
-	columns, income_accounts, tax_accounts = get_columns(invoice_list, additional_table_columns)
+	columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(invoice_list, additional_table_columns)
 
 	if not invoice_list:
 		msgprint(_("No record found"))
 		return columns, invoice_list
 
 	invoice_income_map = get_invoice_income_map(invoice_list)
+	internal_invoice_map = get_internal_invoice_map(invoice_list)
 	invoice_income_map, invoice_tax_map = get_invoice_tax_map(invoice_list,
 		invoice_income_map, income_accounts)
 	#Cost Center & Warehouse Map
@@ -70,12 +71,22 @@
 		# map income values
 		base_net_total = 0
 		for income_acc in income_accounts:
-			income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
+			if inv.is_internal_customer and inv.company == inv.represents_company:
+				income_amount = 0
+			else:
+				income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
+
 			base_net_total += income_amount
 			row.update({
 				frappe.scrub(income_acc): income_amount
 			})
 
+		# Add amount in unrealized account
+		for account in unrealized_profit_loss_accounts:
+			row.update({
+				frappe.scrub(account): flt(internal_invoice_map.get((inv.name, account)))
+			})
+
 		# net total
 		row.update({'net_total': base_net_total or inv.base_net_total})
 
@@ -230,6 +241,8 @@
 	tax_accounts = []
 	income_columns = []
 	tax_columns = []
+	unrealized_profit_loss_accounts = []
+	unrealized_profit_loss_account_columns = []
 
 	if invoice_list:
 		income_accounts = frappe.db.sql_list("""select distinct income_account
@@ -243,12 +256,18 @@
 			and parent in (%s) order by account_head""" %
 			', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
 
+		unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
+			from `tabSales Invoice` where docstatus = 1 and name in (%s)
+			and ifnull(unrealized_profit_loss_account, '') != ''
+			order by unrealized_profit_loss_account""" %
+			', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
+
 	for account in income_accounts:
 		income_columns.append({
 			"label": account,
 			"fieldname": frappe.scrub(account),
 			"fieldtype": "Currency",
-			"options": 'currency',
+			"options": "currency",
 			"width": 120
 		})
 
@@ -258,15 +277,24 @@
 				"label": account,
 				"fieldname": frappe.scrub(account),
 				"fieldtype": "Currency",
-				"options": 'currency',
+				"options": "currency",
 				"width": 120
 			})
 
+	for account in unrealized_profit_loss_accounts:
+		unrealized_profit_loss_account_columns.append({
+			"label": account,
+			"fieldname": frappe.scrub(account),
+			"fieldtype": "Currency",
+			"options": "currency",
+			"width": 120
+		})
+
 	net_total_column = [{
 		"label": _("Net Total"),
 		"fieldname": "net_total",
 		"fieldtype": "Currency",
-		"options": 'currency',
+		"options": "currency",
 		"width": 120
 	}]
 
@@ -301,9 +329,10 @@
 		}
 	]
 
-	columns = columns + income_columns + net_total_column + tax_columns + total_columns
+	columns = columns + income_columns + unrealized_profit_loss_account_columns + \
+		net_total_column + tax_columns + total_columns
 
-	return columns, income_accounts, tax_accounts
+	return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
 
 def get_conditions(filters):
 	conditions = ""
@@ -368,7 +397,8 @@
 	return frappe.db.sql("""
 		select name, posting_date, debit_to, project, customer,
 		customer_name, owner, remarks, territory, tax_id, customer_group,
-		base_net_total, base_grand_total, base_rounded_total, outstanding_amount {0}
+		base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
+		is_internal_customer, represents_company, company {0}
 		from `tabSales Invoice`
 		where docstatus = 1 %s order by posting_date desc, name desc""".format(additional_query_columns or '') %
 		conditions, filters, as_dict=1)
@@ -385,6 +415,19 @@
 
 	return invoice_income_map
 
+def get_internal_invoice_map(invoice_list):
+	unrealized_amount_details = frappe.db.sql("""SELECT name, unrealized_profit_loss_account,
+		base_net_total as amount from `tabSales Invoice` where name in (%s)
+		and is_internal_customer = 1 and company = represents_company""" %
+		', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]), as_dict=1)
+
+	internal_invoice_map = {}
+	for d in unrealized_amount_details:
+		if d.unrealized_profit_loss_account:
+			internal_invoice_map.setdefault((d.name, d.unrealized_profit_loss_account), d.amount)
+
+	return internal_invoice_map
+
 def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
 	tax_details = frappe.db.sql("""select parent, account_head,
 		sum(base_tax_amount_after_discount_amount) as tax_amount
diff --git a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
index cae150c..afbd9b4 100644
--- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
+++ b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
@@ -48,7 +48,7 @@
 
 	def import_disease_tasks(self, disease, start_date):
 		disease_doc = frappe.get_doc('Disease', disease)
-		self.create_task(disease_doc.treatment_task, self.name, start_date)
+		self.create_task(disease_doc.treatment_task, self.project, start_date)
 
 	def create_project(self, period, crop_tasks):
 		project = frappe.get_doc({
diff --git a/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.py
index 5510d5a..763b403 100644
--- a/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.py
+++ b/erpnext/agriculture/doctype/crop_cycle/test_crop_cycle.py
@@ -71,4 +71,4 @@
 
 
 def check_project_creation():
-	return True if frappe.db.exists('Project', 'Basil from seed 2017') else False
+	return True if frappe.db.exists('Project', {'project_name': 'Basil from seed 2017'}) else False
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/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index c2579eb..1430827 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -13,17 +13,14 @@
 class AssetValueAdjustment(Document):
 	def validate(self):
 		self.validate_date()
-		self.set_difference_amount()
 		self.set_current_asset_value()
+		self.set_difference_amount()
 
 	def on_submit(self):
 		self.make_depreciation_entry()
 		self.reschedule_depreciations(self.new_asset_value)
 
 	def on_cancel(self):
-		if self.journal_entry:
-			frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
-
 		self.reschedule_depreciations(self.current_asset_value)
 
 	def validate_date(self):
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 47483c9..dd0f065 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);
 	}
 });
 
@@ -58,8 +64,8 @@
 erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
 	setup: function() {
 		this.frm.custom_make_buttons = {
-			'Purchase Receipt': 'Receipt',
-			'Purchase Invoice': 'Invoice',
+			'Purchase Receipt': 'Purchase Receipt',
+			'Purchase Invoice': 'Purchase Invoice',
 			'Stock Entry': 'Material to Supplier',
 			'Payment Entry': 'Payment',
 		}
@@ -158,16 +164,16 @@
 
 					if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
 						let me = this;
-						frappe.model.with_doc("Supplier", me.frm.doc.supplier, () => {
-							let supplier = frappe.model.get_doc("Supplier", me.frm.doc.supplier);
-							let internal = supplier.is_internal_supplier;
-							let disabled = supplier.disabled;
-							if (internal === 1 && disabled === 0) {
-								me.frm.add_custom_button("Inter Company Order", function() {
-									me.make_inter_company_order(me.frm);
-								}, __('Create'));
-							}
-						});
+						let internal = me.frm.doc.is_internal_supplier;
+						if (internal) {
+							let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Sales Order" :
+								"Inter Company Sales Order";
+
+							me.frm.add_custom_button(button_label, function() {
+								me.make_inter_company_order(me.frm);
+							}, __('Create'));
+						}
+
 					}
 				}
 
@@ -347,7 +353,8 @@
 	make_purchase_receipt: function() {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
-			frm: cur_frm
+			frm: cur_frm,
+			freeze_message: __("Creating Purchase Receipt ...")
 		})
 	},
 
@@ -374,7 +381,7 @@
 						material_request_type: "Purchase",
 						docstatus: 1,
 						status: ["!=", "Stopped"],
-						per_ordered: ["<", 99.99],
+						per_ordered: ["<", 100],
 						company: me.frm.doc.company
 					}
 				})
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 75da71c..ee2beea 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -134,6 +134,8 @@
   "ref_sq",
   "column_break_74",
   "party_account_currency",
+  "is_internal_supplier",
+  "represents_company",
   "inter_company_order_reference"
  ],
  "fields": [
@@ -1101,13 +1103,28 @@
   {
    "fieldname": "items_col_break",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fetch_from": "supplier.is_internal_supplier",
+   "fieldname": "is_internal_supplier",
+   "fieldtype": "Check",
+   "label": "Is Internal Supplier"
+  },
+  {
+   "fetch_from": "supplier.represents_company",
+   "fieldname": "represents_company",
+   "fieldtype": "Link",
+   "label": "Represents Company",
+   "options": "Company",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-03 16:46:44.229351",
+ "modified": "2021-01-20 22:07:23.487138",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index c7efb8a..d32e98e 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -123,8 +123,8 @@
 		if self.is_subcontracted == "Yes":
 			for item in self.items:
 				if not item.bom:
-					frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}"\
-						.format(item.item_code, item.idx)))
+					frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}")
+						.format(item.item_code, item.idx))
 
 	def get_schedule_dates(self):
 		for d in self.get('items'):
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index e537771..b76c378 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -224,7 +224,7 @@
 							material_request_type: "Purchase",
 							docstatus: 1,
 							status: ["!=", "Stopped"],
-							per_ordered: ["<", 99.99],
+							per_ordered: ["<", 100],
 							company: me.frm.doc.company
 						}
 					})
@@ -280,7 +280,7 @@
 								material_request_type: "Purchase",
 								docstatus: 1,
 								status: ["!=", "Stopped"],
-								per_ordered: ["<", 99.99]
+								per_ordered: ["<", 100]
 							}
 						});
 						dialog.hide();
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 0ee9d18..edeb135 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -52,7 +52,10 @@
 		self.validate_internal_supplier()
 
 	def validate_internal_supplier(self):
-		if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
+		internal_supplier = frappe.db.get_value("Supplier",
+			{"is_internal_supplier": 1, "represents_company": self.represents_company, "name": ("!=", self.name)}, "name")
+
+		if internal_supplier:
 			frappe.throw(_("Internal Supplier for company {0} already exists").format(
 				frappe.bold(self.represents_company)))
 
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
index a3b2085..a0187b0 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
@@ -44,7 +44,7 @@
 							material_request_type: "Purchase",
 							docstatus: 1,
 							status: ["!=", "Stopped"],
-							per_ordered: ["<", 99.99],
+							per_ordered: ["<", 100],
 							company: me.frm.doc.company
 						}
 					})
diff --git a/erpnext/buying/report/purchase_analytics/purchase_analytics.js b/erpnext/buying/report/purchase_analytics/purchase_analytics.js
index e17973c..ba8535a 100644
--- a/erpnext/buying/report/purchase_analytics/purchase_analytics.js
+++ b/erpnext/buying/report/purchase_analytics/purchase_analytics.js
@@ -75,62 +75,70 @@
 		return Object.assign(options, {
 			checkboxColumn: true,
 			events: {
-				onCheckRow: function(data) {
+				onCheckRow: function (data) {
+					if (!data) return;
+
+					const data_doctype = $(
+						data[2].html
+					)[0].attributes.getNamedItem("data-doctype").value;
+					const tree_type = frappe.query_report.filters[0].value;
+					if (data_doctype != tree_type) return;
+
 					row_name = data[2].content;
 					length = data.length;
 
-					var tree_type = frappe.query_report.filters[0].value;
-
-					if(tree_type == "Supplier" || tree_type == "Item") {
-						row_values = data.slice(4,length-1).map(function (column) {
-							return column.content;
-						})
-					}
-					else {
-						row_values = data.slice(3,length-1).map(function (column) {
-							return column.content;
-						})
+					if (tree_type == "Supplier") {
+						row_values = data
+							.slice(4, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
+					} else if (tree_type == "Item") {
+						row_values = data
+							.slice(5, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
+					} else {
+						row_values = data
+							.slice(3, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
 					}
 
-					entry  = {
-						'name':row_name,
-						'values':row_values
-					}
+					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;
+					let element_found = new_datasets.some((element, index, array)=>{
+						if(element.name == row_name){
+							array.splice(index, 1)
+							return true
 						}
-					}
+						return false
+					})
 
-					if(!found){
+					if (!element_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)
+						datasets: new_datasets,
+					};
+					chart_options = {
+						data: new_data,
+						type: "line",
+					};
+					frappe.query_report.render_chart(chart_options);
 
 					frappe.query_report.raw_chart_data = new_data;
 				},
-			}
+			},
 		});
 	}
 }
diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py
index 47b4866..a73cb0d 100644
--- a/erpnext/buying/utils.py
+++ b/erpnext/buying/utils.py
@@ -35,9 +35,10 @@
 				frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
 
 		# update last purchsae rate
-		if last_purchase_rate:
-			frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""",
-				(flt(last_purchase_rate), d.item_code))
+		frappe.db.set_value('Item', d.item_code, 'last_purchase_rate', flt(last_purchase_rate))
+
+
+
 
 def validate_for_items(doc):
 	items = []
diff --git a/erpnext/change_log/v13/v13_0_0-beta_11.md b/erpnext/change_log/v13/v13_0_0-beta_11.md
new file mode 100644
index 0000000..5c40ffb
--- /dev/null
+++ b/erpnext/change_log/v13/v13_0_0-beta_11.md
@@ -0,0 +1,77 @@
+### Version 13.0.0 Beta 11 Release Notes
+
+#### Features and Enhancements
+
+- Provision to disable serial no and batch selector ([#24398](https://github.com/frappe/erpnext/pull/24398))
+- Multi currency in landed cost voucher ([#24127](https://github.com/frappe/erpnext/pull/24127))
+- Putaway ([#23969](https://github.com/frappe/erpnext/pull/23969))
+- Item valuation for internal stock transfers ([#24200](https://github.com/frappe/erpnext/pull/24200))
+- Batch wise item pricing ([#24470](https://github.com/frappe/erpnext/pull/24470))
+- Project template with dependent tasks ([#24092](https://github.com/frappe/erpnext/pull/24092))
+- Patient History Enhancements ([#24033](https://github.com/frappe/erpnext/pull/24033))
+- Compute Year to Date for Salary Slip components ([#24362](https://github.com/frappe/erpnext/pull/24362))
+- Configurable accounting dimension filters and validations ([#23912](https://github.com/frappe/erpnext/pull/23912))
+- Issue Summary Script Report ([#23603](https://github.com/frappe/erpnext/pull/23603))
+- Issue Analytics Script Report ([#23604](https://github.com/frappe/erpnext/pull/23604))
+- Loan report and enhancements ([#24370](https://github.com/frappe/erpnext/pull/24370))
+- Enhancements to erpnext membership ([#23865](https://github.com/frappe/erpnext/pull/23865))
+- Allow Discharge despite Unbilled Healthcare Services ([#24281](https://github.com/frappe/erpnext/pull/24281))
+- Patient appointment status changes ([#24201](https://github.com/frappe/erpnext/pull/24201))
+- Allow selecting admission service unit in Patient Appointment for inpatients ([#24410](https://github.com/frappe/erpnext/pull/24410))
+- Separate equity tree in CoA SKR04 ([#24095](https://github.com/frappe/erpnext/pull/24095))
+- Do Not Bill Patient Encounters for Inpatients ([#24355](https://github.com/frappe/erpnext/pull/24355))
+- Value Based and Numeric Quality Inspection ([#24181](https://github.com/frappe/erpnext/pull/24181))
+- Deleting account & stock entries on deletion of transaction ([#24298](https://github.com/frappe/erpnext/pull/24298))
+- Remove german sales invoice validation ([#24441](https://github.com/frappe/erpnext/pull/24441))
+- Voice Call Settings doctype added ([#24126](https://github.com/frappe/erpnext/pull/24126))
+- Shopping portal changes ([#24445](https://github.com/frappe/erpnext/pull/24445))
+- Add "Sync Now" to Plaid Settings ([#23602](https://github.com/frappe/erpnext/pull/23602))
+
+#### Fixes
+
+- Multiple pricing rule with margin type as Percentage is not working ([#24204](https://github.com/frappe/erpnext/pull/24204))
+- Allow statistical component in salary structure. ([#24424](https://github.com/frappe/erpnext/pull/24424))
+- Set current asset value before calculating difference amount ([#24119](https://github.com/frappe/erpnext/pull/24119))
+- To use Stock UoM in BOM Stock Report ([#24339](https://github.com/frappe/erpnext/pull/24339))
+- Accounting entries of asset when submitting purchase receipt ([#24191](https://github.com/frappe/erpnext/pull/24191))
+- Cancelling of asset value adjustement ([#24193](https://github.com/frappe/erpnext/pull/24193))
+- Batch/Serial Selector for Scanned Batched Item ([#24338](https://github.com/frappe/erpnext/pull/24338))
+- Link timesheets with corresponding projects ([#24346](https://github.com/frappe/erpnext/pull/24346))
+- Material request wrong status issue ([#24019](https://github.com/frappe/erpnext/pull/24019))
+- UX issues in e-invoicing ([#24358](https://github.com/frappe/erpnext/pull/24358))
+- Company Wise Valuation Rate for RM in BOM ([#24324](https://github.com/frappe/erpnext/pull/24324))
+- Stock ageing should not take cancelled stock entries. ([#24437](https://github.com/frappe/erpnext/pull/24437))
+- Partial loan security unpledging ([#24252](https://github.com/frappe/erpnext/pull/24252))
+- Asset depreciation ledger ([#24226](https://github.com/frappe/erpnext/pull/24226))
+- Back Update from QC based on Batch No ([#24329](https://github.com/frappe/erpnext/pull/24329))
+- Fix for not having fiscal year while creating new company ([#24130](https://github.com/frappe/erpnext/pull/24130))
+- E-invoice print format not showing other charges ([#24474](https://github.com/frappe/erpnext/pull/24474))
+- Tax template update on customer address change ([#24146](https://github.com/frappe/erpnext/pull/24146))
+- Do not manufacture same serial no multiple times ([#24164](https://github.com/frappe/erpnext/pull/24164))
+- Ignore group cost center validation for period closing voucher ([#24375](https://github.com/frappe/erpnext/pull/24375))
+- Partial serial no return issue ([#24207](https://github.com/frappe/erpnext/pull/24207))
+- GSTR-1 double entry issue ([#24376](https://github.com/frappe/erpnext/pull/24376))
+- Not able to create dunning from sales invoice ([#24349](https://github.com/frappe/erpnext/pull/24349))
+- Set company in leave allocation and leave ledger entry ([#24296](https://github.com/frappe/erpnext/pull/24296))
+- Allow leave policy assignment to be canceled. ([#24265](https://github.com/frappe/erpnext/pull/24265))
+- Removed all day event from shift assignment calendar ([#24397](https://github.com/frappe/erpnext/pull/24397))
+- Tax calculation on salary slip for the first month ([#24272](https://github.com/frappe/erpnext/pull/24272))
+- Validate tax template for tax category ([#24402](https://github.com/frappe/erpnext/pull/24402))
+- Numeric/Non-numeric QI UX ([#24517](https://github.com/frappe/erpnext/pull/24517))
+- Finished good produced qty validation ([#24220](https://github.com/frappe/erpnext/pull/24220))
+- Incorrect serial no in the subcontracted purchase receipt ([#24354](https://github.com/frappe/erpnext/pull/24354))
+- Don't validate warehouse values between Material Request and Stock Entry ([#24294](https://github.com/frappe/erpnext/pull/24294))
+- Don't cancel job card if manufacturing entry has made ([#24063](https://github.com/frappe/erpnext/pull/24063))
+- Subscription prepaid date validation ([#24356](https://github.com/frappe/erpnext/pull/24356))
+- Allow addition and removal of employee in payroll Entry ([#24169](https://github.com/frappe/erpnext/pull/24169))
+- Filter Therapy Types and Therapy Plan in Patient Appointment ([#24152](https://github.com/frappe/erpnext/pull/24152))
+- Payment Period based on invoice date report fix/refactor ([#24378](https://github.com/frappe/erpnext/pull/24378))
+- Drop ship partial order fixed ([#24072](https://github.com/frappe/erpnext/pull/24072))
+- E-invoicing qrcode image generation ([#24395](https://github.com/frappe/erpnext/pull/24395))
+- Payment entry multi-currency issue ([#24332](https://github.com/frappe/erpnext/pull/24332))
+- Multiple pricing rule issue ([#24515](https://github.com/frappe/erpnext/pull/24515))
+- Last purchase rate not updating when voucher cancelled if only one voucher is present ([#24322](https://github.com/frappe/erpnext/pull/24322))
+- Do not cancel reference document on Quality Inspection cancellation ([#24197](https://github.com/frappe/erpnext/pull/24197))
+- Refactored fetching & validating address from erpnext rather than gst portal ([#24297](https://github.com/frappe/erpnext/pull/24297))
+- Opportunity Status fix ([#22944](https://github.com/frappe/erpnext/pull/22944))
+- Extra transferred qty has not consumed against work order ([#24495](https://github.com/frappe/erpnext/pull/24495))
\ No newline at end of file
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 0f1aa23..35c6cd3 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -75,6 +75,9 @@
 		self.ensure_supplier_is_not_blocked()
 
 		self.validate_date_with_fiscal_year()
+		self.validate_inter_company_reference()
+
+		self.set_incoming_rate()
 
 		if self.meta.get_field("currency"):
 			self.calculate_taxes_and_totals()
@@ -110,15 +113,21 @@
 			self.set_inter_company_account()
 
 		validate_regional(self)
-		
+
 		validate_einvoice_fields(self)
 
 		if self.doctype != 'Material Request':
 			apply_pricing_rule_on_transaction(self)
-	
+
 	def before_cancel(self):
 		validate_einvoice_fields(self)
 
+	def on_trash(self):
+		# delete sl and gl entries on deletion of transaction
+		if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
+			frappe.db.sql("delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
+			frappe.db.sql("delete from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s", (self.doctype, self.name))
+
 	def validate_deferred_start_and_end_date(self):
 		for d in self.items:
 			if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):
@@ -206,6 +215,17 @@
 				validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
 									 self.meta.get_label(date_field), self)
 
+	def validate_inter_company_reference(self):
+		if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
+			return
+
+		if self.is_internal_transfer():
+			if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
+				or self.get('inter_company_order_reference')):
+				msg = _("Internal Sale or Delivery Reference missing. ")
+				msg += _("Please create purchase from internal sale or delivery document itself")
+				frappe.throw(msg, title=_("Internal Sales Reference Missing"))
+
 	def validate_due_date(self):
 		if self.get('is_pos'): return
 
@@ -448,8 +468,10 @@
 			account_currency = get_account_currency(gl_dict.account)
 
 		if gl_dict.account and self.doctype not in ["Journal Entry",
-													"Period Closing Voucher", "Payment Entry"]:
+			"Period Closing Voucher", "Payment Entry", "Purchase Receipt", "Purchase Invoice", "Stock Entry"]:
 			self.validate_account_currency(gl_dict.account, account_currency)
+
+		if gl_dict.account and self.doctype not in ["Journal Entry", "Period Closing Voucher", "Payment Entry"]:
 			set_balance_in_account_currency(gl_dict, account_currency, self.get("conversion_rate"),
 											self.company_currency)
 
@@ -962,9 +984,9 @@
 			It will an internal transfer if its an internal customer and representation
 			company is same as billing company
 		"""
-		if self.doctype == 'Sales Invoice':
+		if self.doctype in ('Sales Invoice', 'Delivery Note', 'Sales Order'):
 			internal_party_field = 'is_internal_customer'
-		else:
+		elif self.doctype in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
 			internal_party_field = 'is_internal_supplier'
 
 		if self.get(internal_party_field) and (self.represents_company == self.company):
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 6edc020..ab1f027 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -44,7 +44,6 @@
 		self.validate_items()
 		self.set_qty_as_per_stock_uom()
 		self.validate_stock_or_nonstock_items()
-		self.update_tax_category_for_internal_transfer()
 		self.validate_warehouse()
 		self.validate_from_warehouse()
 		self.set_supplier_address()
@@ -100,11 +99,6 @@
 			msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
 			self.update_tax_category(msg)
 
-	def update_tax_category_for_internal_transfer(self):
-		if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
-			msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
-			self.update_tax_category(msg)
-
 	def update_tax_category(self, msg):
 		tax_for_valuation = [d for d in self.get("taxes")
 				if d.category in ["Valuation", "Valuation and Total"]]
@@ -224,6 +218,48 @@
 			else:
 				item.valuation_rate = 0.0
 
+	def set_incoming_rate(self):
+		if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
+			return
+
+		ref_doctype_map = {
+			"Purchase Order": "Sales Order Item",
+			"Purchase Receipt": "Delivery Note Item",
+			"Purchase Invoice": "Sales Invoice Item",
+		}
+
+		ref_doctype = ref_doctype_map.get(self.doctype)
+		items = self.get("items")
+		for d in items:
+			if not cint(self.get("is_return")):
+				# Get outgoing rate based on original item cost based on valuation method
+
+				if not d.get(frappe.scrub(ref_doctype)):
+					outgoing_rate = get_incoming_rate({
+						"item_code": d.item_code,
+						"warehouse": d.get('from_warehouse'),
+						"posting_date": self.get('posting_date') or self.get('transation_date'),
+						"posting_time": self.get('posting_time'),
+						"qty": -1 * flt(d.get('stock_qty')),
+						"serial_no": d.get('serial_no'),
+						"company": self.company,
+						"voucher_type": self.doctype,
+						"voucher_no": self.name,
+						"allow_zero_valuation": d.get("allow_zero_valuation")
+					}, raise_error_if_no_rate=False)
+
+					rate = flt(outgoing_rate * d.conversion_factor, d.precision('rate'))
+				else:
+					rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), 'rate')
+
+				if self.is_internal_transfer():
+					if rate != d.rate:
+						d.rate = rate
+						d.discount_percentage = 0
+						d.discount_amount = 0
+						frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
+							.format(d.idx), alert=1)
+
 	def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
 		supplied_items_cost = 0.0
 		for d in self.get("supplied_items"):
@@ -243,7 +279,7 @@
 
 				d.amount = flt(flt(d.consumed_qty) * flt(d.rate), d.precision("amount"))
 				supplied_items_cost += flt(d.amount)
-		
+
 		return supplied_items_cost
 
 	def validate_for_subcontracting(self):
@@ -336,7 +372,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
@@ -559,6 +595,8 @@
 						from_warehouse_sle = self.get_sl_entries(d, {
 							"actual_qty": -1 * pr_qty,
 							"warehouse": d.from_warehouse,
+							"outgoing_rate": d.rate,
+							"recalculate_rate": 1,
 							"dependant_sle_voucher_detail_no": d.name
 						})
 
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 8fe3816..81f0ad3 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
@@ -622,6 +657,34 @@
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
+def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters):
+	query = """
+		select name
+		from `tabHealthcare Service Unit`
+		where
+			is_group = 0
+			and company = {company}
+			and name like {txt}""".format(
+				company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt)))
+
+	if filters and filters.get('inpatient_record'):
+		from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
+		service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record'))
+
+		# if the patient is admitted, then appointments should be allowed against the admission service unit,
+		# inspite of it being an Inpatient Occupancy service unit
+		if service_unit:
+			query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit))
+		else:
+			query += " and allow_appointments = 1"
+	else:
+		query += " and allow_appointments = 1"
+
+	return frappe.db.sql(query, filters)
+
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
 def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
 
 	item_doc = frappe.get_cached_doc('Item', filters.get('item_code'))
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 85af0ea..0e1829a 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -329,6 +329,7 @@
 			target_doc.po_detail = source_doc.po_detail
 			target_doc.pr_detail = source_doc.pr_detail
 			target_doc.purchase_invoice_item = source_doc.name
+			target_doc.price_list_rate = 0
 
 		elif doctype == "Delivery Note":
 			returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
@@ -354,6 +355,7 @@
 			target_doc.dn_detail = source_doc.dn_detail
 			target_doc.expense_account = source_doc.expense_account
 			target_doc.sales_invoice_item = source_doc.name
+			target_doc.price_list_rate = 0
 			if default_warehouse_for_sales_return:
 				target_doc.warehouse = default_warehouse_for_sales_return
 
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 85cfb95..e085048 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -3,7 +3,7 @@
 
 from __future__ import unicode_literals
 import frappe
-from frappe.utils import cint, flt, cstr, comma_or, get_link_to_form
+from frappe.utils import cint, flt, cstr, get_link_to_form, nowtime
 from frappe import _, throw
 from erpnext.stock.get_item_details import get_bin_details
 from erpnext.stock.utils import get_incoming_rate
@@ -49,7 +49,6 @@
 		self.set_customer_address()
 		self.validate_for_duplicate_items()
 		self.validate_target_warehouse()
-		self.set_incoming_rate()
 
 	def set_missing_values(self, for_validate=False):
 
@@ -191,7 +190,7 @@
 		for it in self.get("items"):
 			if not it.item_code:
 				continue
-			
+
 			last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
 			last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1)
 			if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
@@ -233,7 +232,7 @@
 							'allow_zero_valuation': d.allow_zero_valuation_rate,
 							'sales_invoice_item': d.get("sales_invoice_item"),
 							'dn_detail': d.get("dn_detail"),
-							'incoming_rate': p.incoming_rate
+							'incoming_rate': p.get("incoming_rate")
 						}))
 			else:
 				il.append(frappe._dict({
@@ -252,7 +251,7 @@
 					'allow_zero_valuation': d.allow_zero_valuation_rate,
 					'sales_invoice_item': d.get("sales_invoice_item"),
 					'dn_detail': d.get("dn_detail"),
-					'incoming_rate': d.incoming_rate
+					'incoming_rate': d.get("incoming_rate")
 				}))
 		return il
 
@@ -312,7 +311,7 @@
 				sales_order.update_reserved_qty(so_item_rows)
 
 	def set_incoming_rate(self):
-		if self.doctype not in ("Delivery Note", "Sales Invoice"):
+		if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"):
 			return
 
 		items = self.get("items") + (self.get("packed_items") or [])
@@ -322,15 +321,26 @@
 				d.incoming_rate = get_incoming_rate({
 					"item_code": d.item_code,
 					"warehouse": d.warehouse,
-					"posting_date": self.posting_date,
-					"posting_time": self.posting_time,
-					"qty": -1*flt(d.qty),
-					"serial_no": d.serial_no,
+					"posting_date": self.get('posting_date') or self.get('transaction_date'),
+					"posting_time": self.get('posting_time') or nowtime(),
+					"qty": -1 * flt(d.get('stock_qty') or d.get('actual_qty')),
+					"serial_no": d.get('serial_no'),
 					"company": self.company,
 					"voucher_type": self.doctype,
 					"voucher_no": self.name,
 					"allow_zero_valuation": d.get("allow_zero_valuation")
 				}, raise_error_if_no_rate=False)
+
+				# For internal transfers use incoming rate as the valuation rate
+				if self.is_internal_transfer():
+					rate = flt(d.incoming_rate * d.conversion_factor, d.precision('rate'))
+					if d.rate != rate:
+						d.rate = rate
+						d.discount_percentage = 0
+						d.discount_amount = 0
+						frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
+							.format(d.idx), alert=1)
+
 			elif self.get("return_against"):
 				# Get incoming rate of return entry from reference document
 				# based on original item cost as per valuation method
@@ -391,7 +401,7 @@
 				})
 				if item_row.warehouse:
 					sle.dependant_sle_voucher_detail_no = item_row.name
-			
+
 		return sle
 
 	def set_po_nos(self, for_validate=False):
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 4399976..4b5e347 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -6,6 +6,7 @@
 from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
 from frappe import _
 import frappe.defaults
+from collections import defaultdict
 from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
 from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
 from erpnext.controllers.accounts_controller import AccountsController
@@ -23,6 +24,8 @@
 			self.validate_inspection()
 		self.validate_serialized_batch()
 		self.validate_customer_provided_item()
+		self.validate_internal_transfer()
+		self.validate_putaway_capacity()
 
 	def make_gl_entries(self, gl_entries=None, from_repost=False):
 		if self.docstatus == 2:
@@ -72,6 +75,7 @@
 		warehouse_with_no_account = []
 		precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
 		for item_row in voucher_details:
+
 			sle_list = sle_map.get(item_row.name)
 			if sle_list:
 				for sle in sle_list:
@@ -216,7 +220,7 @@
 		""", (self.doctype, self.name), as_dict=True)
 
 		for sle in stock_ledger_entries:
-				stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
+			stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
 		return stock_ledger
 
 	def make_batches(self, warehouse_field):
@@ -391,6 +395,84 @@
 			if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
 				d.allow_zero_valuation_rate = 1
 
+	def validate_internal_transfer(self):
+		if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
+			and self.is_internal_transfer():
+			self.validate_in_transit_warehouses()
+			self.validate_multi_currency()
+			self.validate_packed_items()
+
+	def validate_in_transit_warehouses(self):
+		if (self.doctype == 'Sales Invoice' and self.get('update_stock')) or self.doctype == 'Delivery Note':
+			for item in self.get('items'):
+				if not item.target_warehouse:
+					frappe.throw(_("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx))
+
+		if (self.doctype == 'Purchase Invoice' and self.get('update_stock')) or self.doctype == 'Purchase Receipt':
+			for item in self.get('items'):
+				if not item.from_warehouse:
+					frappe.throw(_("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx))
+
+	def validate_multi_currency(self):
+		if self.currency != self.company_currency:
+			frappe.throw(_("Internal transfers can only be done in company's default currency"))
+
+	def validate_packed_items(self):
+		if self.doctype in ('Sales Invoice', 'Delivery Note Item') and self.get('packed_items'):
+			frappe.throw(_("Packed Items cannot be transferred internally"))
+
+	def validate_putaway_capacity(self):
+		# if over receipt is attempted while 'apply putaway rule' is disabled
+		# and if rule was applied on the transaction, validate it.
+		from erpnext.stock.doctype.putaway_rule.putaway_rule import get_available_putaway_capacity
+		valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry", "Purchase Invoice",
+			"Stock Reconciliation")
+
+		if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
+			valid_doctype = False
+
+		if valid_doctype:
+			rule_map = defaultdict(dict)
+			for item in self.get("items"):
+				warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
+				rule = frappe.db.get_value("Putaway Rule",
+					{
+						"item_code": item.get("item_code"),
+						"warehouse": item.get(warehouse_field)
+					},
+					["name", "disable"], as_dict=True)
+				if rule:
+					if rule.get("disabled"): continue # dont validate for disabled rule
+
+					if self.doctype == "Stock Reconciliation":
+						stock_qty = flt(item.qty)
+					else:
+						stock_qty = flt(item.transfer_qty) if self.doctype == "Stock Entry" else flt(item.stock_qty)
+
+					rule_name = rule.get("name")
+					if not rule_map[rule_name]:
+						rule_map[rule_name]["warehouse"] = item.get(warehouse_field)
+						rule_map[rule_name]["item"] = item.get("item_code")
+						rule_map[rule_name]["qty_put"] = 0
+						rule_map[rule_name]["capacity"] = get_available_putaway_capacity(rule_name)
+					rule_map[rule_name]["qty_put"] += flt(stock_qty)
+
+			for rule, values in rule_map.items():
+				if flt(values["qty_put"]) > flt(values["capacity"]):
+					message = self.prepare_over_receipt_message(rule, values)
+					frappe.throw(msg=message, title=_("Over Receipt"))
+
+	def prepare_over_receipt_message(self, rule, values):
+		message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
+			.format(
+				frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
+				frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
+			)
+		message += "<br><br>"
+		rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
+		message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link)
+		return message
+
 	def repost_future_sle_and_gle(self):
 		args = frappe._dict({
 			"posting_date": self.posting_date,
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 8dd2e5b..1f50e9c 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -10,6 +10,7 @@
 	validate_taxes_and_charges, validate_inclusive_tax
 from erpnext.stock.get_item_details import _get_item_tax_template
 from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
+from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
 
 class calculate_taxes_and_totals(object):
 	def __init__(self, doc):
@@ -615,7 +616,6 @@
 				self.doc.precision("base_write_off_amount"))
 
 	def calculate_margin(self, item):
-
 		rate_with_margin = 0.0
 		base_rate_with_margin = 0.0
 		if item.price_list_rate:
@@ -624,8 +624,8 @@
 				for d in get_applied_pricing_rules(item.pricing_rules):
 					pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
 
-					if (pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == self.doc.currency)\
-							or (pricing_rule.margin_type == 'Percentage'):
+					if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
+						pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
 						item.margin_type = pricing_rule.margin_type
 						item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
 						has_margin = True
@@ -758,3 +758,35 @@
 	for taxes in itemised_tax.values():
 		for tax_account in taxes:
 			taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
+
+class init_landed_taxes_and_totals(object):
+	def __init__(self, doc):
+		self.doc = doc
+		self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
+		self.set_account_currency()
+		self.set_exchange_rate()
+		self.set_amounts_in_company_currency()
+
+	def set_account_currency(self):
+		company_currency = erpnext.get_company_currency(self.doc.company)
+		for d in self.doc.get(self.tax_field):
+			if not d.account_currency:
+				account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
+				d.account_currency = account_currency or company_currency
+
+	def set_exchange_rate(self):
+		company_currency = erpnext.get_company_currency(self.doc.company)
+		for d in self.doc.get(self.tax_field):
+			if d.account_currency == company_currency:
+				d.exchange_rate = 1
+			elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date:
+				d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
+					account_currency=d.account_currency, company=self.doc.company)
+
+			if not d.exchange_rate:
+				frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
+
+	def set_amounts_in_company_currency(self):
+		for d in self.doc.get(self.tax_field):
+			d.amount = flt(d.amount, d.precision("amount"))
+			d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
\ No newline at end of file
diff --git a/erpnext/controllers/tests/test_item_variant.py b/erpnext/controllers/tests/test_item_variant.py
index c257215..813f0a0 100644
--- a/erpnext/controllers/tests/test_item_variant.py
+++ b/erpnext/controllers/tests/test_item_variant.py
@@ -6,6 +6,7 @@
 
 from erpnext.stock.doctype.item.test_item import set_item_variant_settings
 from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code
+from erpnext.stock.doctype.quality_inspection.test_quality_inspection import create_quality_inspection_parameter
 
 from six import string_types
 
@@ -56,6 +57,8 @@
 
 	qc = frappe.new_doc("Quality Inspection Template")
 	qc.quality_inspection_template_name = qc_template
+
+	create_quality_inspection_parameter("Moisture")
 	qc.append('item_quality_inspection_parameter', {
 		"specification": "Moisture",
 		"value": "&lt; 5%",
diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py
index 63efeb3..2009ebf 100644
--- a/erpnext/crm/doctype/appointment/appointment.py
+++ b/erpnext/crm/doctype/appointment/appointment.py
@@ -126,7 +126,7 @@
 			add_assignemnt({
 				'doctype': self.doctype,
 				'name': self.name,
-				'assign_to': existing_assignee
+				'assign_to': [existing_assignee]
 			})
 			return
 		if self._assign:
@@ -139,7 +139,7 @@
 				add_assignemnt({
 					'doctype': self.doctype,
 					'name': self.name,
-					'assign_to': agent
+					'assign_to': [agent]
 				})
 			break
 
diff --git a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
index 9f996d9..0ee9317 100644
--- a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
+++ b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
@@ -8,12 +8,12 @@
  "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-14 17:38:27.496696",
+ "modified": "2021-01-21 15:28:52.483839",
  "modified_by": "Administrator",
  "name": "Create Opportunity",
  "owner": "Administrator",
  "reference_document": "Opportunity",
- "show_full_form": 0,
+ "show_full_form": 1,
  "title": "Create Opportunity",
  "validate_action": 1
 }
\ No newline at end of file
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/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py
index 6fbcd8a..886a7d8 100644
--- a/erpnext/education/doctype/program_enrollment/program_enrollment.py
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py
@@ -124,21 +124,24 @@
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_program_courses(doctype, txt, searchfield, start, page_len, filters):
-	if filters.get('program'):
-		return frappe.db.sql("""select course, course_name from `tabProgram Course`
-			where  parent = %(program)s and course like %(txt)s {match_cond}
-			order by
-				if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
-				idx desc,
-				`tabProgram Course`.course asc
-			limit {start}, {page_len}""".format(
-				match_cond=get_match_cond(doctype),
-				start=start,
-				page_len=page_len), {
-					"txt": "%{0}%".format(txt),
-					"_txt": txt.replace('%', ''),
-					"program": filters['program']
-				})
+	if not filters.get('program'):
+		frappe.msgprint(_("Please select a Program first."))
+		return []
+
+	return frappe.db.sql("""select course, course_name from `tabProgram Course`
+		where  parent = %(program)s and course like %(txt)s {match_cond}
+		order by
+			if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
+			idx desc,
+			`tabProgram Course`.course asc
+		limit {start}, {page_len}""".format(
+			match_cond=get_match_cond(doctype),
+			start=start,
+			page_len=page_len), {
+				"txt": "%{0}%".format(txt),
+				"_txt": txt.replace('%', ''),
+				"program": filters['program']
+			})
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
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/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
index e55a143..c324228 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
@@ -100,7 +100,6 @@
 		allow_start = self.set_actual_qty()
 		if allow_start:
 			self.db_set('status', 'In Progress')
-			insert_clinical_procedure_to_medical_record(self)
 			return 'success'
 		return 'insufficient stock'
 
@@ -247,21 +246,3 @@
 		}, target_doc, set_missing_values)
 
 	return doc
-
-
-def insert_clinical_procedure_to_medical_record(doc):
-	subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "<br>"
-	if doc.practitioner:
-		subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
-	if subject and doc.notes:
-		subject += '<br/>' + doc.notes
-
-	medical_record = frappe.new_doc('Patient Medical Record')
-	medical_record.patient = doc.patient
-	medical_record.subject = subject
-	medical_record.status = 'Open'
-	medical_record.communication_date = doc.start_date
-	medical_record.reference_doctype = 'Clinical Procedure'
-	medical_record.reference_name = doc.name
-	medical_record.reference_owner = doc.owner
-	medical_record.save(ignore_permissions=True)
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
index 0104386..ddf1bce 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
@@ -17,6 +17,9 @@
   "enable_free_follow_ups",
   "max_visits",
   "valid_days",
+  "inpatient_settings_section",
+  "allow_discharge_despite_unbilled_services",
+  "do_not_bill_inpatient_encounters",
   "healthcare_service_items",
   "inpatient_visit_charge_item",
   "op_consulting_charge_item",
@@ -302,11 +305,28 @@
    "fieldname": "enable_free_follow_ups",
    "fieldtype": "Check",
    "label": "Enable Free Follow-ups"
+  },
+  {
+   "fieldname": "inpatient_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Inpatient Settings"
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_discharge_despite_unbilled_services",
+   "fieldtype": "Check",
+   "label": "Allow Discharge Despite Unbilled Healthcare Services"
+  },
+  {
+   "default": "0",
+   "fieldname": "do_not_bill_inpatient_encounters",
+   "fieldtype": "Check",
+   "label": "Do Not Bill Patient Encounters for Inpatients"
   }
  ],
  "issingle": 1,
  "links": [],
- "modified": "2020-07-08 15:17:21.543218",
+ "modified": "2021-01-13 09:04:35.877700",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare Settings",
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js
index ca97489..a7b06b1 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js
@@ -5,6 +5,7 @@
 	refresh: function(frm) {
 		// Ignore cancellation of doctype on cancel all
 		frm.ignore_doctypes_on_cancel_all = ['Stock Entry'];
+		frm.fields_dict['medication_orders'].grid.wrapper.find('.grid-add-row').hide();
 
 		frm.set_query('item_code', () => {
 			return {
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json
index dd4c423..b1a6ee4 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json
@@ -139,7 +139,6 @@
    "fieldtype": "Table",
    "label": "Inpatient Medication Orders",
    "options": "Inpatient Medication Entry Detail",
-   "read_only": 1,
    "reqd": 1
   },
   {
@@ -180,7 +179,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-03 13:22:37.820707",
+ "modified": "2021-01-11 12:37:46.749659",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Inpatient Medication Entry",
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
index 70ae713..e731908 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
@@ -15,8 +15,6 @@
 		self.validate_medication_orders()
 
 	def get_medication_orders(self):
-		self.validate_datetime_filters()
-
 		# pull inpatient medication orders based on selected filters
 		orders = get_pending_medication_orders(self)
 
@@ -27,22 +25,6 @@
 			self.set('medication_orders', [])
 			frappe.msgprint(_('No pending medication orders found for selected criteria'))
 
-	def validate_datetime_filters(self):
-		if self.from_date and self.to_date:
-			self.validate_from_to_dates('from_date', 'to_date')
-
-		if self.from_date and getdate(self.from_date) > getdate():
-			frappe.throw(_('From Date cannot be after the current date.'))
-
-		if self.to_date and getdate(self.to_date) > getdate():
-			frappe.throw(_('To Date cannot be after the current date.'))
-
-		if self.from_time and self.from_time > nowtime():
-			frappe.throw(_('From Time cannot be after the current time.'))
-
-		if self.to_time and self.to_time > nowtime():
-			frappe.throw(_('To Time cannot be after the current time.'))
-
 	def add_mo_to_table(self, orders):
 		# Add medication orders in the child table
 		self.set('medication_orders', [])
@@ -282,7 +264,7 @@
 
 def get_current_healthcare_service_unit(inpatient_record):
 	ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
-	if ip_record.inpatient_occupancies:
+	if ip_record.status in ['Admitted', 'Discharge Scheduled'] and ip_record.inpatient_occupancies:
 		return ip_record.inpatient_occupancies[-1].service_unit
 	return
 
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
index bc76970..dc549a6 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe, json
 from frappe import _
-from frappe.utils import today, now_datetime, getdate, get_datetime
+from frappe.utils import today, now_datetime, getdate, get_datetime, get_link_to_form
 from frappe.model.document import Document
 from frappe.desk.reportview import get_match_cond
 
@@ -113,6 +113,7 @@
 	inpatient_record.status = 'Admission Scheduled'
 	inpatient_record.save(ignore_permissions = True)
 
+
 @frappe.whitelist()
 def schedule_discharge(args):
 	discharge_order = json.loads(args)
@@ -126,16 +127,19 @@
 		frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status)
 		frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status)
 
+
 def set_details_from_ip_order(inpatient_record, ip_order):
 	for key in ip_order:
 		inpatient_record.set(key, ip_order[key])
 
+
 def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child):
 	for item in encounter_child:
 		table = inpatient_record.append(inpatient_record_child)
 		for df in table.meta.get('fields'):
 			table.set(df.fieldname, item.get(df.fieldname))
 
+
 def check_out_inpatient(inpatient_record):
 	if inpatient_record.inpatient_occupancies:
 		for inpatient_occupancy in inpatient_record.inpatient_occupancies:
@@ -144,54 +148,88 @@
 				inpatient_occupancy.check_out = now_datetime()
 				frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
 
+
 def discharge_patient(inpatient_record):
-	validate_invoiced_inpatient(inpatient_record)
+	validate_inpatient_invoicing(inpatient_record)
 	inpatient_record.discharge_date = today()
 	inpatient_record.status = "Discharged"
 
 	inpatient_record.save(ignore_permissions = True)
 
-def validate_invoiced_inpatient(inpatient_record):
-	pending_invoices = []
+
+def validate_inpatient_invoicing(inpatient_record):
+	if frappe.db.get_single_value("Healthcare Settings", "allow_discharge_despite_unbilled_services"):
+		return
+
+	pending_invoices = get_pending_invoices(inpatient_record)
+
+	if pending_invoices:
+		message = _("Cannot mark Inpatient Record as Discharged since there are unbilled services. ")
+
+		formatted_doc_rows = ''
+
+		for doctype, docnames in pending_invoices.items():
+			formatted_doc_rows += """
+				<td>{0}</td>
+				<td>{1}</td>
+			</tr>""".format(doctype, docnames)
+
+		message += """
+			<table class='table'>
+				<thead>
+					<th>{0}</th>
+					<th>{1}</th>
+				</thead>
+				{2}
+			</table>
+		""".format(_("Healthcare Service"), _("Documents"), formatted_doc_rows)
+
+		frappe.throw(message, title=_("Unbilled Services"), is_minimizable=True, wide=True)
+
+
+def get_pending_invoices(inpatient_record):
+	pending_invoices = {}
 	if inpatient_record.inpatient_occupancies:
 		service_unit_names = False
 		for inpatient_occupancy in inpatient_record.inpatient_occupancies:
-			if inpatient_occupancy.invoiced != 1:
+			if not inpatient_occupancy.invoiced:
 				if service_unit_names:
 					service_unit_names += ", " + inpatient_occupancy.service_unit
 				else:
 					service_unit_names = inpatient_occupancy.service_unit
 		if service_unit_names:
-			pending_invoices.append("Inpatient Occupancy (" + service_unit_names + ")")
+			pending_invoices["Inpatient Occupancy"] = service_unit_names
 
 	docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"]
 
 	for doc in docs:
-		doc_name_list = get_inpatient_docs_not_invoiced(doc, inpatient_record)
+		doc_name_list = get_unbilled_inpatient_docs(doc, inpatient_record)
 		if doc_name_list:
 			pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices)
 
-	if pending_invoices:
-		frappe.throw(_("Can not mark Inpatient Record Discharged, there are Unbilled Invoices {0}").format(", "
-			.join(pending_invoices)), title=_('Unbilled Invoices'))
+	return pending_invoices
+
 
 def get_pending_doc(doc, doc_name_list, pending_invoices):
 	if doc_name_list:
 		doc_ids = False
 		for doc_name in doc_name_list:
+			doc_link = get_link_to_form(doc, doc_name.name)
 			if doc_ids:
-				doc_ids += ", "+doc_name.name
+				doc_ids += ", " + doc_link
 			else:
-				doc_ids = doc_name.name
+				doc_ids = doc_link
 		if doc_ids:
-			pending_invoices.append(doc + " (" + doc_ids + ")")
+			pending_invoices[doc] =  doc_ids
 
 	return pending_invoices
 
-def get_inpatient_docs_not_invoiced(doc, inpatient_record):
+
+def get_unbilled_inpatient_docs(doc, inpatient_record):
 	return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient,
 					'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0})
 
+
 def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None):
 	inpatient_record.admitted_datetime = check_in
 	inpatient_record.status = 'Admitted'
@@ -203,6 +241,7 @@
 	frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted')
 	frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name)
 
+
 def transfer_patient(inpatient_record, service_unit, check_in):
 	item_line = inpatient_record.append('inpatient_occupancies', {})
 	item_line.service_unit = service_unit
@@ -212,6 +251,7 @@
 
 	frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied")
 
+
 def patient_leave_service_unit(inpatient_record, check_out, leave_from):
 	if inpatient_record.inpatient_occupancies:
 		for inpatient_occupancy in inpatient_record.inpatient_occupancies:
@@ -221,6 +261,7 @@
 				frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant")
 	inpatient_record.save(ignore_permissions = True)
 
+
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_leave_from(doctype, txt, searchfield, start, page_len, filters):
diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
index 70706ad..8a918b0 100644
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
@@ -8,6 +8,8 @@
 from frappe.utils import now_datetime, today
 from frappe.utils.make_random import get_random
 from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
+from erpnext.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter
+from erpnext.healthcare.utils import get_encounters_to_invoice
 
 class TestInpatientRecord(unittest.TestCase):
 	def test_admit_and_discharge(self):
@@ -40,6 +42,60 @@
 		self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
 		self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
 
+	def test_allow_discharge_despite_unbilled_services(self):
+		frappe.db.sql("""delete from `tabInpatient Record`""")
+		setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=1)
+		patient = create_patient()
+		# Schedule Admission
+		ip_record = create_inpatient(patient)
+		ip_record.expected_length_of_stay = 0
+		ip_record.save(ignore_permissions = True)
+
+		# Admit
+		service_unit = get_healthcare_service_unit()
+		admit_patient(ip_record, service_unit, now_datetime())
+
+		# Discharge
+		schedule_discharge(frappe.as_json({"patient": patient}))
+		self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
+
+		ip_record = frappe.get_doc("Inpatient Record", ip_record.name)
+		# Should not validate Pending Invoices
+		ip_record.discharge()
+
+		self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record"))
+		self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status"))
+
+		setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=0)
+
+	def test_do_not_bill_patient_encounters_for_inpatients(self):
+		frappe.db.sql("""delete from `tabInpatient Record`""")
+		setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=1)
+		patient = create_patient()
+		# Schedule Admission
+		ip_record = create_inpatient(patient)
+		ip_record.expected_length_of_stay = 0
+		ip_record.save(ignore_permissions = True)
+
+		# Admit
+		service_unit = get_healthcare_service_unit()
+		admit_patient(ip_record, service_unit, now_datetime())
+
+		# Patient Encounter
+		patient_encounter = create_patient_encounter()
+		encounters = get_encounters_to_invoice(patient, "_Test Company")
+		encounter_ids = [entry.reference_name for entry in encounters]
+		self.assertFalse(patient_encounter.name in encounter_ids)
+
+		# Discharge
+		schedule_discharge(frappe.as_json({"patient": patient}))
+		self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status"))
+
+		ip_record = frappe.get_doc("Inpatient Record", ip_record.name)
+		mark_invoiced_inpatient_occupancy(ip_record)
+		discharge_patient(ip_record)
+		setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=0)
+
 	def test_validate_overlap_admission(self):
 		frappe.db.sql("""delete from `tabInpatient Record`""")
 		patient = create_patient()
@@ -63,6 +119,13 @@
 			inpatient_occupancy.invoiced = 1
 		ip_record.save(ignore_permissions = True)
 
+
+def setup_inpatient_settings(key, value):
+	settings = frappe.get_single("Healthcare Settings")
+	settings.set(key, value)
+	settings.save()
+
+
 def create_inpatient(patient):
 	patient_obj = frappe.get_doc('Patient', patient)
 	inpatient_record = frappe.new_doc('Inpatient Record')
@@ -78,11 +141,16 @@
 	inpatient_record.scheduled_date = today()
 	return inpatient_record
 
-def get_healthcare_service_unit():
-	service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
+
+def get_healthcare_service_unit(unit_name=None):
+	if not unit_name:
+		service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1})
+	else:
+		service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name})
+
 	if not service_unit:
 		service_unit = frappe.new_doc("Healthcare Service Unit")
-		service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy"
+		service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy"
 		service_unit.company = "_Test Company"
 		service_unit.service_unit_type = get_service_unit_type()
 		service_unit.inpatient_occupancy = 1
@@ -105,6 +173,7 @@
 		return service_unit.name
 	return service_unit
 
+
 def get_service_unit_type():
 	service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1})
 
@@ -116,6 +185,7 @@
 		return service_unit_type.name
 	return service_unit_type
 
+
 def create_patient():
 	patient = frappe.db.exists('Patient', '_Test IPD Patient')
 	if not patient:
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json
index edf1d91..ac61fea 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.json
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.json
@@ -359,6 +359,7 @@
   {
    "fieldname": "normal_test_items",
    "fieldtype": "Table",
+   "label": "Normal Test Result",
    "options": "Normal Test Result",
    "print_hide": 1
   },
@@ -380,6 +381,7 @@
   {
    "fieldname": "sensitivity_test_items",
    "fieldtype": "Table",
+   "label": "Sensitivity Test Result",
    "options": "Sensitivity Test Result",
    "print_hide": 1,
    "report_hide": 1
@@ -529,6 +531,7 @@
   {
    "fieldname": "descriptive_test_items",
    "fieldtype": "Table",
+   "label": "Descriptive Test Result",
    "options": "Descriptive Test Result",
    "print_hide": 1,
    "report_hide": 1
@@ -549,13 +552,14 @@
   {
    "fieldname": "organism_test_items",
    "fieldtype": "Table",
+   "label": "Organism Test Result",
    "options": "Organism Test Result",
    "print_hide": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-07-30 18:18:38.516215",
+ "modified": "2020-11-30 11:04:17.195848",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Lab Test",
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py
index 2db7743..4b57cd0 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.py
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.py
@@ -17,11 +17,9 @@
 		self.validate_result_values()
 		self.db_set('submitted_date', getdate())
 		self.db_set('status', 'Completed')
-		insert_lab_test_to_medical_record(self)
 
 	def on_cancel(self):
 		self.db_set('status', 'Cancelled')
-		delete_lab_test_from_medical_record(self)
 		self.reload()
 
 	def on_update(self):
@@ -330,60 +328,6 @@
 		return frappe.get_doc('Employee', emp_id)
 	return None
 
-def insert_lab_test_to_medical_record(doc):
-	table_row = False
-	subject = cstr(doc.lab_test_name)
-	if doc.practitioner:
-		subject += frappe.bold(_('Healthcare Practitioner: '))+ doc.practitioner + '<br>'
-	if doc.normal_test_items:
-		item = doc.normal_test_items[0]
-		comment = ''
-		if item.lab_test_comment:
-			comment = str(item.lab_test_comment)
-		table_row = frappe.bold(_('Lab Test Conducted: ')) + item.lab_test_name
-
-		if item.lab_test_event:
-			table_row += frappe.bold(_('Lab Test Event: ')) + item.lab_test_event
-
-		if item.result_value:
-			table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value
-
-		if item.normal_range:
-			table_row += ' ' + _('Normal Range: ') + item.normal_range
-		table_row += ' ' + comment
-
-	elif doc.descriptive_test_items:
-		item = doc.descriptive_test_items[0]
-
-		if item.lab_test_particulars and item.result_value:
-			table_row = item.lab_test_particulars + ' ' + item.result_value
-
-	elif doc.sensitivity_test_items:
-		item = doc.sensitivity_test_items[0]
-
-		if item.antibiotic and item.antibiotic_sensitivity:
-			table_row = item.antibiotic + ' ' + item.antibiotic_sensitivity
-
-	if table_row:
-		subject += '<br>' + table_row
-	if doc.lab_test_comment:
-		subject += '<br>' + cstr(doc.lab_test_comment)
-
-	medical_record = frappe.new_doc('Patient Medical Record')
-	medical_record.patient = doc.patient
-	medical_record.subject = subject
-	medical_record.status = 'Open'
-	medical_record.communication_date = doc.result_date
-	medical_record.reference_doctype = 'Lab Test'
-	medical_record.reference_name = doc.name
-	medical_record.reference_owner = doc.owner
-	medical_record.save(ignore_permissions = True)
-
-def delete_lab_test_from_medical_record(self):
-	medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name=%s', (self.name))
-
-	if medical_record_id and medical_record_id[0][0]:
-		frappe.delete_doc('Patient Medical Record', medical_record_id[0][0])
 
 @frappe.whitelist()
 def get_lab_test_prescribed(patient):
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
index 2d6b645..3d5073b 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
@@ -22,6 +22,7 @@
 				filters: {'status': 'Active'}
 			};
 		});
+
 		frm.set_query('practitioner', function() {
 			return {
 				filters: {
@@ -29,16 +30,27 @@
 				}
 			};
 		});
-		frm.set_query('service_unit', function(){
+
+		frm.set_query('service_unit', function() {
 			return {
+				query: 'erpnext.controllers.queries.get_healthcare_service_units',
 				filters: {
-					'is_group': false,
-					'allow_appointments': true,
-					'company': frm.doc.company
+					company: frm.doc.company,
+					inpatient_record: frm.doc.inpatient_record
 				}
 			};
 		});
 
+		frm.set_query('therapy_plan', function() {
+			return {
+				filters: {
+					'patient': frm.doc.patient
+				}
+			};
+		});
+
+		frm.trigger('set_therapy_type_filter');
+
 		if (frm.is_new()) {
 			frm.page.set_primary_action(__('Check Availability'), function() {
 				if (!frm.doc.patient) {
@@ -136,6 +148,24 @@
 		}
 	},
 
+	therapy_plan: function(frm) {
+		frm.trigger('set_therapy_type_filter');
+	},
+
+	set_therapy_type_filter: function(frm) {
+		if (frm.doc.therapy_plan) {
+			frm.call('get_therapy_types').then(r => {
+				frm.set_query('therapy_type', function() {
+					return {
+						filters: {
+							'name': ['in', r.message]
+						}
+					};
+				});
+			});
+		}
+	},
+
 	therapy_type: function(frm) {
 		if (frm.doc.therapy_type) {
 			frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => {
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index ac35acc..35600e4 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
@@ -23,9 +23,9 @@
   "procedure_template",
   "get_procedure_from_encounter",
   "procedure_prescription",
+  "therapy_plan",
   "therapy_type",
   "get_prescribed_therapies",
-  "therapy_plan",
   "practitioner",
   "practitioner_name",
   "department",
@@ -284,7 +284,7 @@
    "report_hide": 1
   },
   {
-   "depends_on": "eval:doc.patient;",
+   "depends_on": "eval:doc.patient && doc.therapy_plan;",
    "fieldname": "therapy_type",
    "fieldtype": "Link",
    "label": "Therapy",
@@ -292,17 +292,16 @@
    "set_only_once": 1
   },
   {
-   "depends_on": "eval:doc.patient && doc.__islocal;",
+   "depends_on": "eval:doc.patient && doc.therapy_plan && doc.__islocal;",
    "fieldname": "get_prescribed_therapies",
    "fieldtype": "Button",
    "label": "Get Prescribed Therapies"
   },
   {
-   "depends_on": "eval: doc.patient && doc.therapy_type",
+   "depends_on": "eval: doc.patient;",
    "fieldname": "therapy_plan",
    "fieldtype": "Link",
    "label": "Therapy Plan",
-   "mandatory_depends_on": "eval: doc.patient && doc.therapy_type",
    "options": "Therapy Plan"
   },
   {
@@ -348,7 +347,7 @@
   }
  ],
  "links": [],
- "modified": "2020-05-21 03:04:21.400893",
+ "modified": "2020-12-16 13:16:58.578503",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Patient Appointment",
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index e685b20..b05c673 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -18,6 +18,7 @@
 class PatientAppointment(Document):
 	def validate(self):
 		self.validate_overlaps()
+		self.validate_service_unit()
 		self.set_appointment_datetime()
 		self.validate_customer_created()
 		self.set_status()
@@ -68,6 +69,19 @@
 				overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])
 			frappe.throw(overlapping_details, title=_('Appointments Overlapping'))
 
+	def validate_service_unit(self):
+		if self.inpatient_record and self.service_unit:
+			from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit
+
+			is_inpatient_occupancy_unit = frappe.db.get_value('Healthcare Service Unit', self.service_unit,
+				'inpatient_occupancy')
+			service_unit = get_current_healthcare_service_unit(self.inpatient_record)
+			if is_inpatient_occupancy_unit and service_unit != self.service_unit:
+				msg = _('Patient {0} is not admitted in the service unit {1}').format(frappe.bold(self.patient), frappe.bold(self.service_unit)) + '<br>'
+				msg += _('Appointment for service units with Inpatient Occupancy can only be created against the unit where patient has been admitted.')
+				frappe.throw(msg, title=_('Invalid Healthcare Service Unit'))
+
+
 	def set_appointment_datetime(self):
 		self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00")
 
@@ -91,6 +105,17 @@
 		if fee_validity:
 			frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
 
+	def get_therapy_types(self):
+		if not self.therapy_plan:
+			return
+
+		therapy_types = []
+		doc = frappe.get_doc('Therapy Plan', self.therapy_plan)
+		for entry in doc.therapy_plan_details:
+			therapy_types.append(entry.therapy_type)
+
+		return therapy_types
+
 
 @frappe.whitelist()
 def check_payment_fields_reqd(patient):
@@ -145,7 +170,7 @@
 		sales_invoice.flags.ignore_mandatory = True
 		sales_invoice.save(ignore_permissions=True)
 		sales_invoice.submit()
-		frappe.msgprint(_('Sales Invoice {0} created'.format(sales_invoice.name)), alert=True)
+		frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True)
 		frappe.db.set_value('Patient Appointment', appointment_doc.name, 'invoiced', 1)
 		frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name)
 
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index 3df7ba1..f7ec6f5 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -5,7 +5,7 @@
 import unittest
 import frappe
 from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter
-from frappe.utils import nowdate, add_days
+from frappe.utils import nowdate, add_days, now_datetime
 from frappe.utils.make_random import get_random
 from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
 
@@ -23,8 +23,10 @@
 		self.assertEquals(appointment.status, 'Open')
 		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2))
 		self.assertEquals(appointment.status, 'Scheduled')
-		create_encounter(appointment)
+		encounter = create_encounter(appointment)
 		self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
+		encounter.cancel()
+		self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
 
 	def test_start_encounter(self):
 		patient, medical_department, practitioner = create_healthcare_docs()
@@ -76,6 +78,59 @@
 		sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
 		self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled')
 
+	def test_appointment_booking_for_admission_service_unit(self):
+		from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
+		from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
+			create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
+
+		frappe.db.sql("""delete from `tabInpatient Record`""")
+		patient, medical_department, practitioner = create_healthcare_docs()
+		patient = create_patient()
+		# Schedule Admission
+		ip_record = create_inpatient(patient)
+		ip_record.expected_length_of_stay = 0
+		ip_record.save(ignore_permissions = True)
+
+		# Admit
+		service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
+		admit_patient(ip_record, service_unit, now_datetime())
+
+		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit)
+		self.assertEqual(appointment.service_unit, service_unit)
+
+		# Discharge
+		schedule_discharge(frappe.as_json({'patient': patient}))
+		ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
+		mark_invoiced_inpatient_occupancy(ip_record1)
+		discharge_patient(ip_record1)
+
+	def test_invalid_healthcare_service_unit_validation(self):
+		from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
+		from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \
+			create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
+
+		frappe.db.sql("""delete from `tabInpatient Record`""")
+		patient, medical_department, practitioner = create_healthcare_docs()
+		patient = create_patient()
+		# Schedule Admission
+		ip_record = create_inpatient(patient)
+		ip_record.expected_length_of_stay = 0
+		ip_record.save(ignore_permissions = True)
+
+		# Admit
+		service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
+		admit_patient(ip_record, service_unit, now_datetime())
+
+		appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment')
+		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0)
+		self.assertRaises(frappe.exceptions.ValidationError, appointment.save)
+
+		# Discharge
+		schedule_discharge(frappe.as_json({'patient': patient}))
+		ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name)
+		mark_invoiced_inpatient_occupancy(ip_record1)
+		discharge_patient(ip_record1)
+
 
 def create_healthcare_docs():
 	patient = create_patient()
@@ -123,7 +178,7 @@
 		encounter.submit()
 		return encounter
 
-def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0):
+def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, save=1):
 	item = create_healthcare_service_items()
 	frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item)
 	frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item)
@@ -134,12 +189,15 @@
 	appointment.appointment_date = appointment_date
 	appointment.company = '_Test Company'
 	appointment.duration = 15
+	if service_unit:
+		appointment.service_unit = service_unit
 	if invoice:
 		appointment.mode_of_payment = 'Cash'
 		appointment.paid_amount = 500
 	if procedure_template:
 		appointment.procedure_template = create_clinical_procedure_template().get('name')
-	appointment.save(ignore_permissions=True)
+	if save:
+		appointment.save(ignore_permissions=True)
 	return appointment
 
 def create_healthcare_service_items():
@@ -150,6 +208,7 @@
 	item.item_name = 'Consulting Charges'
 	item.item_group = 'Services'
 	item.is_stock_item = 0
+	item.stock_uom = 'Nos'
 	item.save()
 	return item.name
 
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
index 15675f4..b646ff9 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
@@ -210,7 +210,7 @@
   {
    "fieldname": "drug_prescription",
    "fieldtype": "Table",
-   "label": "Items",
+   "label": "Drug Prescription",
    "options": "Drug Prescription"
   },
   {
@@ -328,7 +328,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-05-16 21:00:08.644531",
+ "modified": "2020-11-30 10:39:00.783119",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Patient Encounter",
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
index 87f4249..cc21417 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
@@ -17,10 +17,6 @@
 	def on_update(self):
 		if self.appointment:
 			frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
-		update_encounter_medical_record(self)
-
-	def after_insert(self):
-		insert_encounter_to_medical_record(self)
 
 	def on_submit(self):
 		if self.therapies:
@@ -33,8 +29,6 @@
 		if self.inpatient_record and self.drug_prescription:
 			delete_ip_medication_order(self)
 
-		delete_medical_record(self)
-
 	def set_title(self):
 		self.title = _('{0} with {1}').format(self.patient_name or self.patient,
 			self.practitioner_name or self.practitioner)[:100]
@@ -102,61 +96,7 @@
 			frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True)
 
 
-def insert_encounter_to_medical_record(doc):
-	subject = set_subject_field(doc)
-	medical_record = frappe.new_doc('Patient Medical Record')
-	medical_record.patient = doc.patient
-	medical_record.subject = subject
-	medical_record.status = 'Open'
-	medical_record.communication_date = doc.encounter_date
-	medical_record.reference_doctype = 'Patient Encounter'
-	medical_record.reference_name = doc.name
-	medical_record.reference_owner = doc.owner
-	medical_record.save(ignore_permissions=True)
-
-
-def update_encounter_medical_record(encounter):
-	medical_record_id = frappe.db.exists('Patient Medical Record', {'reference_name': encounter.name})
-
-	if medical_record_id and medical_record_id[0][0]:
-		subject = set_subject_field(encounter)
-		frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
-	else:
-		insert_encounter_to_medical_record(encounter)
-
-
-def delete_medical_record(encounter):
-	record = frappe.db.exists('Patient Medical Record', {'reference_name', encounter.name})
-	if record:
-		frappe.delete_doc('Patient Medical Record', record, force=1)
-
 def delete_ip_medication_order(encounter):
 	record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name})
 	if record:
-		frappe.delete_doc('Inpatient Medication Order', record, force=1)
-
-
-def set_subject_field(encounter):
-	subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '<br>'
-	if encounter.symptoms:
-		subject += frappe.bold(_('Symptoms: ')) + '<br>'
-		for entry in encounter.symptoms:
-			subject += cstr(entry.complaint) + '<br>'
-	else:
-		subject += frappe.bold(_('No Symptoms')) + '<br>'
-
-	if encounter.diagnosis:
-		subject += frappe.bold(_('Diagnosis: ')) + '<br>'
-		for entry in encounter.diagnosis:
-			subject += cstr(entry.diagnosis) + '<br>'
-	else:
-		subject += frappe.bold(_('No Diagnosis')) + '<br>'
-
-	if encounter.drug_prescription:
-		subject += '<br>' + _('Drug(s) Prescribed.')
-	if encounter.lab_test_prescription:
-		subject += '<br>' + _('Test(s) Prescribed.')
-	if encounter.procedure_prescription:
-		subject += '<br>' + _('Procedure(s) Prescribed.')
-
-	return subject
+		frappe.delete_doc('Inpatient Medication Order', record, force=1)
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/patient_history_custom_document_type/__init__.py b/erpnext/healthcare/doctype/patient_history_custom_document_type/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_custom_document_type/__init__.py
diff --git a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json
new file mode 100644
index 0000000..3025c7b
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json
@@ -0,0 +1,55 @@
+{
+ "actions": [],
+ "creation": "2020-11-25 13:40:23.054469",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "document_type",
+  "date_fieldname",
+  "add_edit_fields",
+  "selected_fields"
+ ],
+ "fields": [
+  {
+   "fieldname": "document_type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Document Type",
+   "options": "DocType",
+   "reqd": 1
+  },
+  {
+   "fieldname": "selected_fields",
+   "fieldtype": "Code",
+   "label": "Selected Fields",
+   "read_only": 1
+  },
+  {
+   "fieldname": "add_edit_fields",
+   "fieldtype": "Button",
+   "in_list_view": 1,
+   "label": "Add / Edit Fields"
+  },
+  {
+   "fieldname": "date_fieldname",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Date Fieldname",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-11-30 13:54:37.474671",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Patient History Custom Document Type",
+ "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/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py
new file mode 100644
index 0000000..f0a1f92
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.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 PatientHistoryCustomDocumentType(Document):
+	pass
diff --git a/erpnext/healthcare/doctype/patient_history_settings/__init__.py b/erpnext/healthcare/doctype/patient_history_settings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_settings/__init__.py
diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js
new file mode 100644
index 0000000..453da6a
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js
@@ -0,0 +1,133 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Patient History Settings', {
+	refresh: function(frm) {
+		frm.set_query('document_type', 'custom_doctypes', () => {
+			return {
+				filters: {
+					custom: 1,
+					is_submittable: 1,
+					module: 'Healthcare',
+				}
+			};
+		});
+	},
+
+	field_selector: function(frm, doc, standard=1) {
+		let document_fields = [];
+		if (doc.selected_fields)
+			document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname);
+
+		frm.call({
+			method: 'get_doctype_fields',
+			doc: frm.doc,
+			args: {
+				document_type: doc.document_type,
+				fields: document_fields
+			},
+			freeze: true,
+			callback: function(r) {
+				if (r.message) {
+					let doctype = 'Patient History Custom Document Type';
+					if (standard)
+						doctype = 'Patient History Standard Document Type';
+
+					frm.events.show_field_selector_dialog(frm, doc, doctype, r.message);
+				}
+			}
+		});
+	},
+
+	show_field_selector_dialog: function(frm, doc, doctype, doc_fields) {
+		let d = new frappe.ui.Dialog({
+			title: __('{0} Fields', [__(doc.document_type)]),
+			fields: [
+				{
+					label: __('Select Fields'),
+					fieldtype: 'MultiCheck',
+					fieldname: 'fields',
+					options: doc_fields,
+					columns: 2
+				}
+			]
+		});
+
+		d.$body.prepend(`
+			<div class="columns-search">
+				<input type="text" placeholder="${__('Search')}" data-element="search" class="form-control input-xs">
+			</div>`
+		);
+
+		frappe.utils.setup_search(d.$body, '.unit-checkbox', '.label-area');
+
+		d.set_primary_action(__('Save'), () => {
+			let values = d.get_values().fields;
+
+			let selected_fields = [];
+
+			frappe.model.with_doctype(doc.document_type, function() {
+				for (let idx in values) {
+					let value = values[idx];
+
+					let field = frappe.get_meta(doc.document_type).fields.filter((df) => df.fieldname == value)[0];
+					if (field) {
+						selected_fields.push({
+							label: field.label,
+							fieldname: field.fieldname,
+							fieldtype: field.fieldtype
+						});
+					}
+				}
+
+				d.refresh();
+				frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields));
+			});
+
+			d.hide();
+		});
+
+		d.show();
+	},
+
+	get_date_field_for_dt: function(frm, row) {
+		frm.call({
+			method: 'get_date_field_for_dt',
+			doc: frm.doc,
+			args: {
+				document_type: row.document_type
+			},
+			callback: function(data) {
+				if (data.message) {
+					frappe.model.set_value('Patient History Custom Document Type',
+						row.name, 'date_fieldname', data.message);
+				}
+			}
+		});
+	}
+});
+
+frappe.ui.form.on('Patient History Custom Document Type', {
+	document_type: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		if (row.document_type) {
+			frm.events.get_date_field_for_dt(frm, row);
+		}
+	},
+
+	add_edit_fields: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		if (row.document_type) {
+			frm.events.field_selector(frm, row, 0);
+		}
+	}
+});
+
+frappe.ui.form.on('Patient History Standard Document Type', {
+	add_edit_fields: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		if (row.document_type) {
+			frm.events.field_selector(frm, row);
+		}
+	}
+});
diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json
new file mode 100644
index 0000000..143e2c9
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json
@@ -0,0 +1,55 @@
+{
+ "actions": [],
+ "creation": "2020-11-25 13:41:37.675518",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "standard_doctypes",
+  "section_break_2",
+  "custom_doctypes"
+ ],
+ "fields": [
+  {
+   "fieldname": "section_break_2",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "custom_doctypes",
+   "fieldtype": "Table",
+   "label": "Custom Document Types",
+   "options": "Patient History Custom Document Type"
+  },
+  {
+   "fieldname": "standard_doctypes",
+   "fieldtype": "Table",
+   "label": "Standard Document Types",
+   "options": "Patient History Standard Document Type",
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2020-11-25 13:43:38.511771",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Patient History Settings",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 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/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
new file mode 100644
index 0000000..2e8c994
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
@@ -0,0 +1,188 @@
+# -*- 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
+import json
+from frappe import _
+from frappe.utils import cstr, cint
+from frappe.model.document import Document
+from erpnext.healthcare.page.patient_history.patient_history import get_patient_history_doctypes
+
+class PatientHistorySettings(Document):
+	def validate(self):
+		self.validate_submittable_doctypes()
+		self.validate_date_fieldnames()
+
+	def validate_submittable_doctypes(self):
+		for entry in self.custom_doctypes:
+			if not cint(frappe.db.get_value('DocType', entry.document_type, 'is_submittable')):
+				msg = _('Row #{0}: Document Type {1} is not submittable. ').format(
+					entry.idx, frappe.bold(entry.document_type))
+				msg += _('Patient Medical Record can only be created for submittable document types.')
+				frappe.throw(msg)
+
+	def validate_date_fieldnames(self):
+		for entry in self.custom_doctypes:
+			field = frappe.get_meta(entry.document_type).get_field(entry.date_fieldname)
+			if not field:
+				frappe.throw(_('Row #{0}: No such Field named {1} found in the Document Type {2}.').format(
+					entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
+
+			if field.fieldtype not in ['Date', 'Datetime']:
+				frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format(
+					entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
+
+	def get_doctype_fields(self, document_type, fields):
+		multicheck_fields = []
+		doc_fields = frappe.get_meta(document_type).fields
+
+		for field in doc_fields:
+			if field.fieldtype not in frappe.model.no_value_fields or \
+				field.fieldtype in frappe.model.table_fields and not field.hidden:
+				multicheck_fields.append({
+					'label': field.label,
+					'value': field.fieldname,
+					'checked': 1 if field.fieldname in fields else 0
+				})
+
+		return multicheck_fields
+
+	def get_date_field_for_dt(self, document_type):
+		meta = frappe.get_meta(document_type)
+		date_fields = meta.get('fields', {
+			'fieldtype': ['in', ['Date', 'Datetime']]
+		})
+
+		if date_fields:
+			return date_fields[0].get('fieldname')
+
+def create_medical_record(doc, method=None):
+	medical_record_required = validate_medical_record_required(doc)
+	if not medical_record_required:
+		return
+
+	if frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }):
+		return
+
+	subject = set_subject_field(doc)
+	date_field = get_date_field(doc.doctype)
+	medical_record = frappe.new_doc('Patient Medical Record')
+	medical_record.patient = doc.patient
+	medical_record.subject = subject
+	medical_record.status = 'Open'
+	medical_record.communication_date = doc.get(date_field)
+	medical_record.reference_doctype = doc.doctype
+	medical_record.reference_name = doc.name
+	medical_record.reference_owner = doc.owner
+	medical_record.save(ignore_permissions=True)
+
+
+def update_medical_record(doc, method=None):
+	medical_record_required = validate_medical_record_required(doc)
+	if not medical_record_required:
+		return
+
+	medical_record_id = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name })
+
+	if medical_record_id:
+		subject = set_subject_field(doc)
+		frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject)
+	else:
+		create_medical_record(doc)
+
+
+def delete_medical_record(doc, method=None):
+	medical_record_required = validate_medical_record_required(doc)
+	if not medical_record_required:
+		return
+
+	record = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name })
+	if record:
+		frappe.delete_doc('Patient Medical Record', record, force=1)
+
+
+def set_subject_field(doc):
+	from frappe.utils.formatters import format_value
+
+	meta = frappe.get_meta(doc.doctype)
+	subject = ''
+	patient_history_fields = get_patient_history_fields(doc)
+
+	for entry in patient_history_fields:
+		fieldname = entry.get('fieldname')
+		if entry.get('fieldtype') == 'Table' and doc.get(fieldname):
+			formatted_value = get_formatted_value_for_table_field(doc.get(fieldname), meta.get_field(fieldname))
+			subject += frappe.bold(_(entry.get('label')) + ': ') + '<br>' + cstr(formatted_value) + '<br>'
+
+		else:
+			if doc.get(fieldname):
+				formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc)
+				subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formatted_value) + '<br>'
+
+	return subject
+
+
+def get_date_field(doctype):
+	dt = get_patient_history_config_dt(doctype)
+
+	return frappe.db.get_value(dt, { 'document_type': doctype }, 'date_fieldname')
+
+
+def get_patient_history_fields(doc):
+	dt = get_patient_history_config_dt(doc.doctype)
+	patient_history_fields = frappe.db.get_value(dt, { 'document_type': doc.doctype }, 'selected_fields')
+
+	if patient_history_fields:
+		return json.loads(patient_history_fields)
+
+
+def get_formatted_value_for_table_field(items, df):
+	child_meta = frappe.get_meta(df.options)
+
+	table_head = ''
+	table_row = ''
+	html = ''
+	create_head = True
+	for item in items:
+		table_row += '<tr>'
+		for cdf in child_meta.fields:
+			if cdf.in_list_view:
+				if create_head:
+					table_head += '<td>' + cdf.label + '</td>'
+				if item.get(cdf.fieldname):
+					table_row += '<td>' + str(item.get(cdf.fieldname)) + '</td>'
+				else:
+					table_row += '<td></td>'
+		create_head = False
+		table_row += '</tr>'
+
+	html += "<table class='table table-condensed table-bordered'>" + table_head +  table_row + "</table>"
+
+	return html
+
+
+def get_patient_history_config_dt(doctype):
+	if frappe.db.get_value('DocType', doctype, 'custom'):
+		return 'Patient History Custom Document Type'
+	else:
+		return 'Patient History Standard Document Type'
+
+
+def validate_medical_record_required(doc):
+	if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard \
+		or get_module(doc) != 'Healthcare':
+		return False
+
+	if doc.doctype not in get_patient_history_doctypes():
+		return False
+
+	return True
+
+def get_module(doc):
+	module = doc.meta.module
+	if not module:
+		module = frappe.db.get_value('DocType', doc.doctype, 'module')
+
+	return module
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py
new file mode 100644
index 0000000..c93b788
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+import json
+from frappe.utils import getdate
+from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient
+
+class TestPatientHistorySettings(unittest.TestCase):
+	def setUp(self):
+		dt = create_custom_doctype()
+		settings = frappe.get_single("Patient History Settings")
+		settings.append("custom_doctypes", {
+			"document_type": dt.name,
+			"date_fieldname": "date",
+			"selected_fields": json.dumps([{
+				"label": "Date",
+				"fieldname": "date",
+				"fieldtype": "Date"
+			},
+			{
+				"label": "Rating",
+				"fieldname": "rating",
+				"fieldtype": "Rating"
+			},
+			{
+				"label": "Feedback",
+				"fieldname": "feedback",
+				"fieldtype": "Small Text"
+			}])
+		})
+		settings.save()
+
+	def test_custom_doctype_medical_record(self):
+		# tests for medical record creation of standard doctypes in test_patient_medical_record.py
+		patient = create_patient()
+		doc = create_doc(patient)
+
+		# check for medical record
+		medical_rec = frappe.db.exists("Patient Medical Record", {"status": "Open", "reference_name": doc.name})
+		self.assertTrue(medical_rec)
+
+		medical_rec = frappe.get_doc("Patient Medical Record", medical_rec)
+		expected_subject = "<b>Date: </b>{0}<br><b>Rating: </b>3<br><b>Feedback: </b>Test Patient History Settings<br>".format(
+			frappe.utils.format_date(getdate()))
+		self.assertEqual(medical_rec.subject, expected_subject)
+		self.assertEqual(medical_rec.patient, patient)
+		self.assertEqual(medical_rec.communication_date, getdate())
+
+
+def create_custom_doctype():
+	if not frappe.db.exists("DocType", "Test Patient Feedback"):
+		doc = frappe.get_doc({
+				"doctype": "DocType",
+				"module": "Healthcare",
+				"custom": 1,
+				"is_submittable": 1,
+				"fields": [{
+					"label": "Date",
+					"fieldname": "date",
+					"fieldtype": "Date"
+				},
+				{
+					"label": "Patient",
+					"fieldname": "patient",
+					"fieldtype": "Link",
+					"options": "Patient"
+				},
+				{
+					"label": "Rating",
+					"fieldname": "rating",
+					"fieldtype": "Rating"
+				},
+				{
+					"label": "Feedback",
+					"fieldname": "feedback",
+					"fieldtype": "Small Text"
+				}],
+				"permissions": [{
+					"role": "System Manager",
+					"read": 1
+				}],
+				"name": "Test Patient Feedback",
+			})
+		doc.insert()
+		return doc
+	else:
+		return frappe.get_doc("DocType", "Test Patient Feedback")
+
+
+def create_doc(patient):
+	doc = frappe.get_doc({
+		"doctype": "Test Patient Feedback",
+		"patient": patient,
+		"date": getdate(),
+		"rating": 3,
+		"feedback": "Test Patient History Settings"
+	}).insert()
+	doc.submit()
+
+	return doc
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/__init__.py b/erpnext/healthcare/doctype/patient_history_standard_document_type/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_standard_document_type/__init__.py
diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json
new file mode 100644
index 0000000..b43099c
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json
@@ -0,0 +1,57 @@
+{
+ "actions": [],
+ "creation": "2020-11-25 13:39:36.014814",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "document_type",
+  "date_fieldname",
+  "add_edit_fields",
+  "selected_fields"
+ ],
+ "fields": [
+  {
+   "fieldname": "document_type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Document Type",
+   "options": "DocType",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "selected_fields",
+   "fieldtype": "Code",
+   "label": "Selected Fields",
+   "read_only": 1
+  },
+  {
+   "fieldname": "add_edit_fields",
+   "fieldtype": "Button",
+   "in_list_view": 1,
+   "label": "Add / Edit Fields"
+  },
+  {
+   "fieldname": "date_fieldname",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Date Fieldname",
+   "read_only": 1,
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-11-30 13:54:56.773325",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Patient History Standard Document Type",
+ "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/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py
new file mode 100644
index 0000000..2d94911
--- /dev/null
+++ b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.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 PatientHistoryStandardDocumentType(Document):
+	pass
diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
index 419d956..c1d9872 100644
--- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
+++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
@@ -18,6 +18,7 @@
 		patient, medical_department, practitioner = create_healthcare_docs()
 		appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
 		encounter = create_encounter(appointment)
+
 		# check for encounter
 		medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': encounter.name})
 		self.assertTrue(medical_rec)
diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
index a061c66..7fb159d 100644
--- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
@@ -5,10 +5,10 @@
 
 import frappe
 import unittest
-from frappe.utils import getdate, flt
+from frappe.utils import getdate, flt, nowdate
 from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type
 from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session, make_sales_invoice
-from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient
+from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient, create_appointment
 
 class TestTherapyPlan(unittest.TestCase):
 	def test_creation_on_encounter_submission(self):
@@ -28,6 +28,15 @@
 		frappe.get_doc(session).submit()
 		self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
 
+		patient, medical_department, practitioner = create_healthcare_docs()
+		appointment = create_appointment(patient, practitioner, nowdate())		
+		session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name)
+		session = frappe.get_doc(session)
+		session.submit()
+		self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
+		session.cancel()
+		self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
+
 	def test_therapy_plan_from_template(self):
 		patient = create_patient()
 		template = create_therapy_plan_template()
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
index bc0ff1a..ac01c60 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
@@ -47,7 +47,7 @@
 
 
 @frappe.whitelist()
-def make_therapy_session(therapy_plan, patient, therapy_type, company):
+def make_therapy_session(therapy_plan, patient, therapy_type, company, appointment=None):
 	therapy_type = frappe.get_doc('Therapy Type', therapy_type)
 
 	therapy_session = frappe.new_doc('Therapy Session')
@@ -58,6 +58,7 @@
 	therapy_session.duration = therapy_type.default_duration
 	therapy_session.rate = therapy_type.rate
 	therapy_session.exercises = therapy_type.exercises
+	therapy_session.appointment = appointment
 
 	if frappe.flags.in_test:
 		therapy_session.start_date = today()
diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js
index a2b01c9..fd20003 100644
--- a/erpnext/healthcare/doctype/therapy_session/therapy_session.js
+++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.js
@@ -19,6 +19,15 @@
 				}
 			};
 		});
+
+		frm.set_query('appointment', function() {
+
+			return {
+				filters: {
+					'status': ['in', ['Open', 'Scheduled']]
+				}
+			};
+		});
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.py b/erpnext/healthcare/doctype/therapy_session/therapy_session.py
index 85d0970..51f267f 100644
--- a/erpnext/healthcare/doctype/therapy_session/therapy_session.py
+++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.py
@@ -41,9 +41,15 @@
 
 	def on_submit(self):
 		self.update_sessions_count_in_therapy_plan()
-		insert_session_medical_record(self)
+
+	def on_update(self):
+		if self.appointment:
+			frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed')
 
 	def on_cancel(self):
+		if self.appointment:
+			frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
+
 		self.update_sessions_count_in_therapy_plan(on_cancel=True)
 
 	def update_sessions_count_in_therapy_plan(self, on_cancel=False):
@@ -135,23 +141,3 @@
 	item.reference_dt = 'Therapy Session'
 	item.reference_dn = therapy.name
 	return item
-
-
-def insert_session_medical_record(doc):
-	subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '<br>'
-	if doc.therapy_plan:
-		subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '<br>'
-	if doc.practitioner:
-		subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner
-	subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '<br>'
-	subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '<br>'
-
-	medical_record = frappe.new_doc('Patient Medical Record')
-	medical_record.patient = doc.patient
-	medical_record.subject = subject
-	medical_record.status = 'Open'
-	medical_record.communication_date = doc.start_date
-	medical_record.reference_doctype = 'Therapy Session'
-	medical_record.reference_name = doc.name
-	medical_record.reference_owner = doc.owner
-	medical_record.save(ignore_permissions=True)
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.py b/erpnext/healthcare/doctype/vital_signs/vital_signs.py
index 69d81ff..35c823d 100644
--- a/erpnext/healthcare/doctype/vital_signs/vital_signs.py
+++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.py
@@ -12,47 +12,7 @@
 	def validate(self):
 		self.set_title()
 
-	def on_submit(self):
-		insert_vital_signs_to_medical_record(self)
-
-	def on_cancel(self):
-		delete_vital_signs_from_medical_record(self)
-
 	def set_title(self):
 		self.title = _('{0} on {1}').format(self.patient_name or self.patient,
 			frappe.utils.format_date(self.signs_date))[:100]
 
-def insert_vital_signs_to_medical_record(doc):
-	subject = set_subject_field(doc)
-	medical_record = frappe.new_doc('Patient Medical Record')
-	medical_record.patient = doc.patient
-	medical_record.subject = subject
-	medical_record.status = 'Open'
-	medical_record.communication_date = doc.signs_date
-	medical_record.reference_doctype = 'Vital Signs'
-	medical_record.reference_name = doc.name
-	medical_record.reference_owner = doc.owner
-	medical_record.flags.ignore_mandatory = True
-	medical_record.save(ignore_permissions=True)
-
-def delete_vital_signs_from_medical_record(doc):
-	medical_record = frappe.db.get_value('Patient Medical Record', {'reference_name': doc.name})
-	if medical_record:
-		frappe.delete_doc('Patient Medical Record', medical_record)
-
-def set_subject_field(doc):
-	subject = ''
-	if doc.temperature:
-		subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '<br>'
-	if doc.pulse:
-		subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '<br>'
-	if doc.respiratory_rate:
-		subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '<br>'
-	if doc.bp:
-		subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '<br>'
-	if doc.bmi:
-		subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '<br>'
-	if doc.nutrition_note:
-		subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '<br>'
-
-	return subject
diff --git a/erpnext/healthcare/page/patient_history/patient_history.css b/erpnext/healthcare/page/patient_history/patient_history.css
index 865d6ab..1bb5891 100644
--- a/erpnext/healthcare/page/patient_history/patient_history.css
+++ b/erpnext/healthcare/page/patient_history/patient_history.css
@@ -109,6 +109,11 @@
 	padding-right: 0px;
 }
 
+.patient-history-filter {
+	margin-left: 35px;
+	width: 25%;
+}
+
 #page-medical_record .plot-wrapper {
 	padding: 20px 15px;
 	border-bottom: 1px solid #d1d8dd;
diff --git a/erpnext/healthcare/page/patient_history/patient_history.html b/erpnext/healthcare/page/patient_history/patient_history.html
index 7a9446d..be486c6 100644
--- a/erpnext/healthcare/page/patient_history/patient_history.html
+++ b/erpnext/healthcare/page/patient_history/patient_history.html
@@ -1,6 +1,5 @@
 <div class="col-sm-12">
 	<div class="col-sm-3">
-	<p class="text-center">{%= __("Select Patient") %}</p>
 	<p class="patient" style="margin: auto; max-width: 300px; margin-bottom: 20px;"></p>
 	<div class="patient_details" style="z-index=0"></div>
 	</div>
@@ -11,6 +10,13 @@
 			<div id="chart" class="col-sm-12 patient_vital_charts">
 			</div>
 		</div>
+		<div class="header-separator col-sm-12 d-flex border-bottom py-3" style="display:none"></div>
+		<div class="row">
+			<div class="col-sm-12 d-flex">
+				<div class="patient-history-filter doctype-filter"></div>
+				<div class="patient-history-filter date-filter"></div>
+			</div>
+		</div>
 		<div class="col-sm-12 patient_documents_list">
 		</div>
 		<div class="col-sm-12 text-center py-3">
diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js
index fe5b7bc..54343aa 100644
--- a/erpnext/healthcare/page/patient_history/patient_history.js
+++ b/erpnext/healthcare/page/patient_history/patient_history.js
@@ -1,141 +1,225 @@
-frappe.provide("frappe.patient_history");
+frappe.provide('frappe.patient_history');
 frappe.pages['patient_history'].on_page_load = function(wrapper) {
-	var me = this;
-	var page = frappe.ui.make_app_page({
+	let me = this;
+	let page = frappe.ui.make_app_page({
 		parent: wrapper,
 		title: 'Patient History',
 		single_column: true
 	});
 
-	frappe.breadcrumbs.add("Healthcare");
+	frappe.breadcrumbs.add('Healthcare');
 	let pid = '';
-	page.main.html(frappe.render_template("patient_history", {}));
-	var patient = frappe.ui.form.make_control({
-		parent: page.main.find(".patient"),
+	page.main.html(frappe.render_template('patient_history', {}));
+	page.main.find('.header-separator').hide();
+
+	let patient = frappe.ui.form.make_control({
+		parent: page.main.find('.patient'),
 		df: {
-			fieldtype: "Link",
-			options: "Patient",
-			fieldname: "patient",
-			change: function(){
-				if(pid != patient.get_value() && patient.get_value()){
+			fieldtype: 'Link',
+			options: 'Patient',
+			fieldname: 'patient',
+			placeholder: __('Select Patient'),
+			only_select: true,
+			change: function() {
+				let patient_id = patient.get_value();
+				if (pid != patient_id && patient_id) {
 					me.start = 0;
-					me.page.main.find(".patient_documents_list").html("");
-					get_documents(patient.get_value(), me);
-					show_patient_info(patient.get_value(), me);
-					show_patient_vital_charts(patient.get_value(), me, "bp", "mmHg", "Blood Pressure");
+					me.page.main.find('.patient_documents_list').html('');
+					setup_filters(patient_id, me);
+					get_documents(patient_id, me);
+					show_patient_info(patient_id, me);
+					show_patient_vital_charts(patient_id, me, 'bp', 'mmHg', 'Blood Pressure');
 				}
-				pid = patient.get_value();
+				pid = patient_id;
 			}
 		},
-		only_input: true,
 	});
 	patient.refresh();
 
-	if (frappe.route_options){
+	if (frappe.route_options) {
 		patient.set_value(frappe.route_options.patient);
 	}
 
-	this.page.main.on("click", ".btn-show-chart", function() {
-		var	btn_show_id = $(this).attr("data-show-chart-id"), pts = $(this).attr("data-pts");
-		var title = $(this).attr("data-title");
+	this.page.main.on('click', '.btn-show-chart', function() {
+		let	btn_show_id = $(this).attr('data-show-chart-id'), pts = $(this).attr('data-pts');
+		let title = $(this).attr('data-title');
 		show_patient_vital_charts(patient.get_value(), me, btn_show_id, pts, title);
 	});
 
-	this.page.main.on("click", ".btn-more", function() {
-		var	doctype = $(this).attr("data-doctype"), docname = $(this).attr("data-docname");
-		if(me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched') == "1"){
-			me.page.main.find("."+docname).hide();
-			me.page.main.find("."+docname).parent().find('.document-html').show();
-		}else{
-			if(doctype && docname){
-				let exclude = ["patient", "patient_name", 'patient_sex', "encounter_date"];
+	this.page.main.on('click', '.btn-more', function() {
+		let	doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname');
+		if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') {
+			me.page.main.find('.'+docname).hide();
+			me.page.main.find('.'+docname).parent().find('.document-html').show();
+		} else {
+			if (doctype && docname) {
+				let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date'];
 				frappe.call({
-					method: "erpnext.healthcare.utils.render_doc_as_html",
+					method: 'erpnext.healthcare.utils.render_doc_as_html',
 					args:{
 						doctype: doctype,
 						docname: docname,
 						exclude_fields: exclude
 					},
+					freeze: true,
 					callback: function(r) {
-						if (r.message){
-							me.page.main.find("."+docname).hide();
-							me.page.main.find("."+docname).parent().find('.document-html').html(r.message.html+"\
-							<div align='center'><a class='btn octicon octicon-chevron-up btn-default btn-xs\
-							btn-less' data-doctype='"+doctype+"' data-docname='"+docname+"'></a></div>");
-							me.page.main.find("."+docname).parent().find('.document-html').show();
-							me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched', "1");
+						if (r.message) {
+							me.page.main.find('.' + docname).hide();
+
+							me.page.main.find('.' + docname).parent().find('.document-html').html(
+								`${r.message.html}
+									<div align='center'>
+										<a class='btn octicon octicon-chevron-up btn-default btn-xs btn-less'
+											data-doctype='${doctype}'
+											data-docname='${docname}'>
+										</a>
+									</div>
+								`);
+
+							me.page.main.find('.' + docname).parent().find('.document-html').show();
+							me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1');
 						}
-					},
-					freeze: true
+					}
 				});
 			}
 		}
 	});
 
-	this.page.main.on("click", ".btn-less", function() {
-		var docname = $(this).attr("data-docname");
-		me.page.main.find("."+docname).parent().find('.document-id').show();
-		me.page.main.find("."+docname).parent().find('.document-html').hide();
+	this.page.main.on('click', '.btn-less', function() {
+		let docname = $(this).attr('data-docname');
+		me.page.main.find('.' + docname).parent().find('.document-id').show();
+		me.page.main.find('.' + docname).parent().find('.document-html').hide();
 	});
 	me.start = 0;
-	me.page.main.on("click", ".btn-get-records", function(){
+	me.page.main.on('click', '.btn-get-records', function() {
 		get_documents(patient.get_value(), me);
 	});
 };
 
-var get_documents = function(patient, me){
+let setup_filters = function(patient, me) {
+	$('.doctype-filter').empty();
+	frappe.xcall(
+		'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes'
+	).then(document_types => {
+		let doctype_filter = frappe.ui.form.make_control({
+			parent: $('.doctype-filter'),
+			df: {
+				fieldtype: 'MultiSelectList',
+				fieldname: 'document_type',
+				placeholder: __('Select Document Type'),
+				input_class: 'input-xs',
+				change: () => {
+					me.start = 0;
+					me.page.main.find('.patient_documents_list').html('');
+					get_documents(patient, me, doctype_filter.get_value(), date_range_field.get_value());
+				},
+				get_data: () => {
+					return document_types.map(document_type => {
+						return {
+							description: document_type,
+							value: document_type
+						};
+					});
+				},
+			}
+		});
+		doctype_filter.refresh();
+
+		$('.date-filter').empty();
+		let date_range_field = frappe.ui.form.make_control({
+			df: {
+				fieldtype: 'DateRange',
+				fieldname: 'date_range',
+				placeholder: __('Date Range'),
+				input_class: 'input-xs',
+				change: () => {
+					let selected_date_range = date_range_field.get_value();
+					if (selected_date_range && selected_date_range.length === 2) {
+						me.start = 0;
+						me.page.main.find('.patient_documents_list').html('');
+						get_documents(patient, me, doctype_filter.get_value(), selected_date_range);
+					}
+				}
+			},
+			parent: $('.date-filter')
+		});
+		date_range_field.refresh();
+	});
+};
+
+let get_documents = function(patient, me, document_types="", selected_date_range="") {
+	let filters = {
+		name: patient,
+		start: me.start,
+		page_length: 20
+	};
+	if (document_types)
+		filters['document_types'] = document_types;
+	if (selected_date_range)
+		filters['date_range'] = selected_date_range;
+
 	frappe.call({
-		"method": "erpnext.healthcare.page.patient_history.patient_history.get_feed",
-		args: {
-			name: patient,
-			start: me.start,
-			page_length: 20
-		},
-		callback: function (r) {
-			var data = r.message;
-			if(data.length){
+		'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed',
+		args: filters,
+		callback: function(r) {
+			let data = r.message;
+			if (data.length) {
 				add_to_records(me, data);
-			}else{
-				me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>");
-				me.page.main.find(".btn-get-records").hide();
+			} else {
+				me.page.main.find('.patient_documents_list').append(`
+					<div class='text-muted' align='center'>
+						<br><br>${__('No more records..')}<br><br>
+					</div>`);
+				me.page.main.find('.btn-get-records').hide();
 			}
 		}
 	});
 };
 
-var add_to_records = function(me, data){
-	var details = "<ul class='nav nav-pills nav-stacked'>";
-	var i;
-	for(i=0; i<data.length; i++){
-		if(data[i].reference_doctype){
+let add_to_records = function(me, data) {
+	let details = "<ul class='nav nav-pills nav-stacked'>";
+	let i;
+	for (i=0; i<data.length; i++) {
+		if (data[i].reference_doctype) {
 			let label = '';
-			if(data[i].subject){
-				label += "<br/>"+data[i].subject;
+			if (data[i].subject) {
+				label += "<br/>" + data[i].subject;
 			}
 			data[i] = add_date_separator(data[i]);
-			if(frappe.user_info(data[i].owner).image){
+
+			if (frappe.user_info(data[i].owner).image) {
 				data[i].imgsrc = frappe.utils.get_file_link(frappe.user_info(data[i].owner).image);
-			}
-			else{
+			} else {
 				data[i].imgsrc = false;
 			}
-			var time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
-			time_line_heading += data[i].reference_doctype + " - "+ data[i].reference_name;
-			details += `<li data-toggle='pill' class='patient_doc_menu'
-			data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
-			<div class='col-sm-12 d-flex border-bottom py-3'>`;
-			if (data[i].imgsrc){
-				details += `<span class='mr-3'>
-					<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'>
-					</img>
-			</span>`;
-			}else{
-				details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'><div align='center' class='standard-image'
-					style='background-color: #fafbfc;'>${data[i].practitioner ? data[i].practitioner.charAt(0) : "U"}</div></span>`;
+
+			let time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
+			time_line_heading += data[i].reference_doctype + " - " +
+				`<a onclick="frappe.set_route('Form', '${data[i].reference_doctype}', '${data[i].reference_name}');">
+					${data[i].reference_name}
+				</a>`;
+
+			details += `
+				<li data-toggle='pill' class='patient_doc_menu'
+					data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
+					<div class='col-sm-12 d-flex border-bottom py-3'>`;
+
+			if (data[i].imgsrc) {
+				details += `
+					<span class='mr-3'>
+						<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'></img>
+					</span>`;
+			} else {
+				details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'>
+					<div align='center' class='standard-image' style='background-color: #fafbfc;'>
+						${data[i].practitioner ? data[i].practitioner.charAt(0) : 'U'}
+					</div>
+				</span>`;
 			}
+
 			details += `<div class='d-flex flex-column width-full'>
 					<div>
-						`+time_line_heading+` on
+						`+time_line_heading+`
 							<span>
 								${data[i].date_sep}
 							</span>
@@ -156,133 +240,150 @@
 			</li>`;
 		}
 	}
-	details += "</ul>";
-	me.page.main.find(".patient_documents_list").append(details);
+
+	details += '</ul>';
+	me.page.main.find('.patient_documents_list').append(details);
 	me.start += data.length;
-	if(data.length===20){
+
+	if (data.length === 20) {
 		me.page.main.find(".btn-get-records").show();
-	}else{
+	} else {
 		me.page.main.find(".btn-get-records").hide();
-		me.page.main.find(".patient_documents_list").append("<div class='text-muted' align='center'><br><br>No more records..<br><br></div>");
+		me.page.main.find(".patient_documents_list").append(`
+			<div class='text-muted' align='center'>
+				<br><br>${__('No more records..')}<br><br>
+			</div>`);
 	}
 };
 
-var add_date_separator = function(data) {
-	var date = frappe.datetime.str_to_obj(data.creation);
+let add_date_separator = function(data) {
+	let date = frappe.datetime.str_to_obj(data.communication_date);
+	let pdate = '';
+	let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
 
-	var diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
-	if(diff < 1) {
-		var pdate = 'Today';
-	} else if(diff < 2) {
-		pdate = 'Yesterday';
+	if (diff < 1) {
+		pdate = __('Today');
+	} else if (diff < 2) {
+		pdate = __('Yesterday');
 	} else {
-		pdate = frappe.datetime.global_date_format(date);
+		pdate = __('on ') + frappe.datetime.global_date_format(date);
 	}
 	data.date_sep = pdate;
 	return data;
 };
 
-var show_patient_info = function(patient, me){
+let show_patient_info = function(patient, me) {
 	frappe.call({
-		"method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail",
+		'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
 		args: {
 			patient: patient
 		},
-		callback: function (r) {
-			var data = r.message;
-			var details = "";
-			if(data.image){
-				details += "<div><img class='thumbnail' width=75% src='"+data.image+"'></div>";
+		callback: function(r) {
+			let data = r.message;
+			let details = '';
+			if (data.image) {
+				details += `<div><img class='thumbnail' width=75% src='${data.image}'></div>`;
 			}
-			details += "<b>" + data.patient_name +"</b><br>" + data.sex;
-			if(data.email) details += "<br>" + data.email;
-			if(data.mobile) details += "<br>" + data.mobile;
-			if(data.occupation) details += "<br><br><b>Occupation :</b> " + data.occupation;
-			if(data.blood_group) details += "<br><b>Blood group : </b> " + data.blood_group;
-			if(data.allergies) details +=  "<br><br><b>Allergies : </b> "+  data.allergies.replace("\n", "<br>");
-			if(data.medication) details +=  "<br><b>Medication : </b> "+  data.medication.replace("\n", "<br>");
-			if(data.alcohol_current_use) details +=  "<br><br><b>Alcohol use : </b> "+  data.alcohol_current_use;
-			if(data.alcohol_past_use) details +=  "<br><b>Alcohol past use : </b> "+  data.alcohol_past_use;
-			if(data.tobacco_current_use) details +=  "<br><b>Tobacco use : </b> "+  data.tobacco_current_use;
-			if(data.tobacco_past_use) details +=  "<br><b>Tobacco past use : </b> "+  data.tobacco_past_use;
-			if(data.medical_history) details +=  "<br><br><b>Medical history : </b> "+  data.medical_history.replace("\n", "<br>");
-			if(data.surgical_history) details +=  "<br><b>Surgical history : </b> "+  data.surgical_history.replace("\n", "<br>");
-			if(data.surrounding_factors) details +=  "<br><br><b>Occupational hazards : </b> "+  data.surrounding_factors.replace("\n", "<br>");
-			if(data.other_risk_factors) details += "<br><b>Other risk factors : </b> " + data.other_risk_factors.replace("\n", "<br>");
-			if(data.patient_details) details += "<br><br><b>More info : </b> " + data.patient_details.replace("\n", "<br>");
 
-			if(details){
-				details = "<div style='padding-left:10px; font-size:13px;' align='center'>" + details + "</div>";
+			details += `<b> ${data.patient_name} </b><br> ${data.sex}`;
+			if (data.email) details += `<br> ${data.email}`;
+			if (data.mobile) details += `<br> ${data.mobile}`;
+			if (data.occupation) details += `<br><br><b> ${__('Occupation')} : </b> ${data.occupation}`;
+			if (data.blood_group) details += `<br><b> ${__('Blood Group')} : </b> ${data.blood_group}`;
+			if (data.allergies) details +=  `<br><br><b> ${__('Allerigies')} : </b> ${data.allergies.replace("\n", ", ")}`;
+			if (data.medication) details +=  `<br><b> ${__('Medication')} : </b> ${data.medication.replace("\n", ", ")}`;
+			if (data.alcohol_current_use) details +=  `<br><br><b> ${__('Alcohol use')} : </b> ${data.alcohol_current_use}`;
+			if (data.alcohol_past_use) details +=  `<br><b> ${__('Alcohol past use')} : </b> ${data.alcohol_past_use}`;
+			if (data.tobacco_current_use) details +=  `<br><b> ${__('Tobacco use')} : </b> ${data.tobacco_current_use}`;
+			if (data.tobacco_past_use) details +=  `<br><b> ${__('Tobacco past use')} : </b> ${data.tobacco_past_use}`;
+			if (data.medical_history) details +=  `<br><br><b> ${__('Medical history')} : </b> ${data.medical_history.replace("\n", ", ")}`;
+			if (data.surgical_history) details +=  `<br><b> ${__('Surgical history')} : </b> ${data.surgical_history.replace("\n", ", ")}`;
+			if (data.surrounding_factors) details +=  `<br><br><b> ${__('Occupational hazards')} : </b> ${data.surrounding_factors.replace("\n", ", ")}`;
+			if (data.other_risk_factors) details += `<br><b> ${__('Other risk factors')} : </b> ${data.other_risk_factors.replace("\n", ", ")}`;
+			if (data.patient_details) details += `<br><br><b> ${__('More info')} : </b> ${data.patient_details.replace("\n", ", ")}`;
+
+			if (details) {
+				details = `<div style='padding-left:10px; font-size:13px;' align='left'>` + details + `</div>`;
 			}
-			me.page.main.find(".patient_details").html(details);
+			me.page.main.find('.patient_details').html(details);
 		}
 	});
 };
 
-var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
+let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
 	frappe.call({
-		method: "erpnext.healthcare.utils.get_patient_vitals",
+		method: 'erpnext.healthcare.utils.get_patient_vitals',
 		args:{
 			patient: patient
 		},
 		callback: function(r) {
-			if (r.message){
-				var show_chart_btns_html = "<div style='padding-top:5px;'><a class='btn btn-default btn-xs btn-show-chart' \
-				data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>Blood Pressure</a>\
-				<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' \
-				data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>Respiratory/Pulse Rate</a>\
-				<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' \
-				data-pts='°C or °F' data-title='Temperature'>Temperature</a>\
-				<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' \
-				data-pts='' data-title='BMI'>BMI</a></div>";
-				me.page.main.find(".show_chart_btns").html(show_chart_btns_html);
-				var data = r.message;
+			if (r.message) {
+				let show_chart_btns_html = `
+					<div style='padding-top:10px;'>
+						<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>
+							${__('Blood Pressure')}
+						</a>
+						<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>
+							${__('Respiratory/Pulse Rate')}
+						</a>
+						<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' data-pts='°C or °F' data-title='Temperature'>
+							${__('Temperature')}
+						</a>
+						<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' data-pts='' data-title='BMI'>
+							${__('BMI')}
+						</a>
+					</div>`;
+
+				me.page.main.find('.show_chart_btns').html(show_chart_btns_html);
+				let data = r.message;
 				let labels = [], datasets = [];
 				let bp_systolic = [], bp_diastolic = [], temperature = [];
 				let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = [];
-				for(var i=0; i<data.length; i++){
-					labels.push(data[i].signs_date+"||"+data[i].signs_time);
-					if(btn_show_id=="bp"){
+
+				for (let i=0; i<data.length; i++) {
+					labels.push(data[i].signs_date+'||'+data[i].signs_time);
+
+					if (btn_show_id === 'bp') {
 						bp_systolic.push(data[i].bp_systolic);
 						bp_diastolic.push(data[i].bp_diastolic);
 					}
-					if(btn_show_id=="temperature"){
+					if (btn_show_id === 'temperature') {
 						temperature.push(data[i].temperature);
 					}
-					if(btn_show_id=="pulse_rate"){
+					if (btn_show_id === 'pulse_rate') {
 						pulse.push(data[i].pulse);
 						respiratory_rate.push(data[i].respiratory_rate);
 					}
-					if(btn_show_id=="bmi"){
+					if (btn_show_id === 'bmi') {
 						bmi.push(data[i].bmi);
 						height.push(data[i].height);
 						weight.push(data[i].weight);
 					}
 				}
-				if(btn_show_id=="temperature"){
-					datasets.push({name: "Temperature", values: temperature, chartType:'line'});
+				if (btn_show_id === 'temperature') {
+					datasets.push({name: 'Temperature', values: temperature, chartType: 'line'});
 				}
-				if(btn_show_id=="bmi"){
-					datasets.push({name: "BMI", values: bmi, chartType:'line'});
-					datasets.push({name: "Height", values: height, chartType:'line'});
-					datasets.push({name: "Weight", values: weight, chartType:'line'});
+				if (btn_show_id === 'bmi') {
+					datasets.push({name: 'BMI', values: bmi, chartType: 'line'});
+					datasets.push({name: 'Height', values: height, chartType: 'line'});
+					datasets.push({name: 'Weight', values: weight, chartType: 'line'});
 				}
-				if(btn_show_id=="bp"){
-					datasets.push({name: "BP Systolic", values: bp_systolic, chartType:'line'});
-					datasets.push({name: "BP Diastolic", values: bp_diastolic, chartType:'line'});
+				if (btn_show_id === 'bp') {
+					datasets.push({name: 'BP Systolic', values: bp_systolic, chartType: 'line'});
+					datasets.push({name: 'BP Diastolic', values: bp_diastolic, chartType: 'line'});
 				}
-				if(btn_show_id=="pulse_rate"){
-					datasets.push({name: "Heart Rate / Pulse", values: pulse, chartType:'line'});
-					datasets.push({name: "Respiratory Rate", values: respiratory_rate, chartType:'line'});
+				if (btn_show_id === 'pulse_rate') {
+					datasets.push({name: 'Heart Rate / Pulse', values: pulse, chartType: 'line'});
+					datasets.push({name: 'Respiratory Rate', values: respiratory_rate, chartType: 'line'});
 				}
-				new frappe.Chart( ".patient_vital_charts", {
+				new frappe.Chart('.patient_vital_charts', {
 					data: {
 						labels: labels,
 						datasets: datasets
 					},
 
 					title: title,
-					type: 'axis-mixed', // 'axis-mixed', 'bar', 'line', 'pie', 'percentage'
+					type: 'axis-mixed',
 					height: 200,
 					colors: ['purple', '#ffa3ef', 'light-blue'],
 
@@ -291,9 +392,11 @@
 						formatTooltipY: d => d + ' ' + pts,
 					}
 				});
-			}else{
-				me.page.main.find(".patient_vital_charts").html("");
-				me.page.main.find(".show_chart_btns").html("");
+				me.page.main.find('.header-separator').show();
+			} else {
+				me.page.main.find('.patient_vital_charts').html('');
+				me.page.main.find('.show_chart_btns').html('');
+				me.page.main.find('.header-separator').hide();
 			}
 		}
 	});
diff --git a/erpnext/healthcare/page/patient_history/patient_history.py b/erpnext/healthcare/page/patient_history/patient_history.py
index 772aa4e..4cdfd64 100644
--- a/erpnext/healthcare/page/patient_history/patient_history.py
+++ b/erpnext/healthcare/page/patient_history/patient_history.py
@@ -4,36 +4,70 @@
 
 from __future__ import unicode_literals
 import frappe
+import json
 from frappe.utils import cint
 from erpnext.healthcare.utils import render_docs_as_html
 
 @frappe.whitelist()
-def get_feed(name, start=0, page_length=20):
+def get_feed(name, document_types=None, date_range=None, start=0, page_length=20):
 	"""get feed"""
-	result = frappe.db.sql("""select name, owner, creation,
-		reference_doctype, reference_name, subject
-		from `tabPatient Medical Record`
-		where patient=%(patient)s
-		order by creation desc
-		limit %(start)s, %(page_length)s""",
-		{
-			"patient": name,
-			"start": cint(start),
-			"page_length": cint(page_length)
-		}, as_dict=True)
+	filters = get_filters(name, document_types, date_range)
+
+	result = frappe.db.get_all('Patient Medical Record',
+		fields=['name', 'owner', 'communication_date',
+			'reference_doctype', 'reference_name', 'subject'],
+		filters=filters,
+		order_by='communication_date DESC',
+		limit=cint(page_length),
+		start=cint(start)
+	)
+
 	return result
 
+
+def get_filters(name, document_types=None, date_range=None):
+	filters = {'patient': name}
+	if document_types:
+		document_types = json.loads(document_types)
+		if len(document_types):
+			filters['reference_doctype'] = ['IN', document_types]
+
+	if date_range:
+		try:
+			date_range = json.loads(date_range)
+			if date_range:
+				filters['communication_date'] = ['between', [date_range[0], date_range[1]]]
+		except json.decoder.JSONDecodeError:
+			pass
+
+	return filters
+
+
 @frappe.whitelist()
 def get_feed_for_dt(doctype, docname):
 	"""get feed"""
-	result = frappe.db.sql("""select name, owner, modified, creation,
-			reference_doctype, reference_name, subject
-		from `tabPatient Medical Record`
-		where reference_name=%(docname)s and reference_doctype=%(doctype)s
-		order by creation desc""",
-		{
-			"docname": docname,
-			"doctype": doctype
-		}, as_dict=True)
+	result = frappe.db.get_all('Patient Medical Record',
+		fields=['name', 'owner', 'communication_date',
+			'reference_doctype', 'reference_name', 'subject'],
+		filters={
+			'reference_doctype': doctype,
+			'reference_name': docname
+		},
+		order_by='communication_date DESC'
+	)
 
 	return result
+
+
+@frappe.whitelist()
+def get_patient_history_doctypes():
+	document_types = []
+	settings = frappe.get_single("Patient History Settings")
+
+	for entry in settings.standard_doctypes:
+		document_types.append(entry.document_type)
+
+	for entry in settings.custom_doctypes:
+		document_types.append(entry.document_type)
+
+	return document_types
diff --git a/erpnext/healthcare/setup.py b/erpnext/healthcare/setup.py
index 0684080..bf4df7e 100644
--- a/erpnext/healthcare/setup.py
+++ b/erpnext/healthcare/setup.py
@@ -16,6 +16,7 @@
 	create_healthcare_item_groups()
 	create_sensitivity()
 	add_healthcare_service_unit_tree_root()
+	setup_patient_history_settings()
 
 def create_medical_departments():
 	departments = [
@@ -213,3 +214,82 @@
 		if company:
 			return company[0].name
 	return None
+
+def setup_patient_history_settings():
+	import json
+
+	settings = frappe.get_single('Patient History Settings')
+	configuration = get_patient_history_config()
+	for dt, config in configuration.items():
+		settings.append("standard_doctypes", {
+			"document_type": dt,
+			"date_fieldname": config[0],
+			"selected_fields": json.dumps(config[1])
+		})
+	settings.save()
+
+def get_patient_history_config():
+	return {
+		"Patient Encounter": ("encounter_date", [
+			{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
+			{"label": "Symptoms", "fieldname": "symptoms", "fieldtype": "Table Multiselect"},
+			{"label": "Diagnosis", "fieldname": "diagnosis", "fieldtype": "Table Multiselect"},
+			{"label": "Drug Prescription", "fieldname": "drug_prescription", "fieldtype": "Table"},
+			{"label": "Lab Tests", "fieldname": "lab_test_prescription", "fieldtype": "Table"},
+			{"label": "Clinical Procedures", "fieldname": "procedure_prescription", "fieldtype": "Table"},
+			{"label": "Therapies", "fieldname": "therapies", "fieldtype": "Table"},
+			{"label": "Review Details", "fieldname": "encounter_comment", "fieldtype": "Small Text"}
+		]),
+		"Clinical Procedure": ("start_date", [
+			{"label": "Procedure Template", "fieldname": "procedure_template", "fieldtype": "Link"},
+			{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
+			{"label": "Notes", "fieldname": "notes", "fieldtype": "Small Text"},
+			{"label": "Service Unit", "fieldname": "service_unit", "fieldtype": "Healthcare Service Unit"},
+			{"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"},
+			{"label": "Sample", "fieldname": "sample", "fieldtype": "Link"}
+		]),
+		"Lab Test": ("result_date", [
+			{"label": "Test Template", "fieldname": "template", "fieldtype": "Link"},
+			{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
+			{"label": "Test Name", "fieldname": "lab_test_name", "fieldtype": "Data"},
+			{"label": "Lab Technician Name", "fieldname": "employee_name", "fieldtype": "Data"},
+			{"label": "Sample ID", "fieldname": "sample", "fieldtype": "Link"},
+			{"label": "Normal Test Result", "fieldname": "normal_test_items", "fieldtype": "Table"},
+			{"label": "Descriptive Test Result", "fieldname": "descriptive_test_items", "fieldtype": "Table"},
+			{"label": "Organism Test Result", "fieldname": "organism_test_items", "fieldtype": "Table"},
+			{"label": "Sensitivity Test Result", "fieldname": "sensitivity_test_items", "fieldtype": "Table"},
+			{"label": "Comments", "fieldname": "lab_test_comment", "fieldtype": "Table"}
+		]),
+		"Therapy Session": ("start_date", [
+			{"label": "Therapy Type", "fieldname": "therapy_type", "fieldtype": "Link"},
+			{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
+			{"label": "Therapy Plan", "fieldname": "therapy_plan", "fieldtype": "Link"},
+			{"label": "Duration", "fieldname": "duration", "fieldtype": "Int"},
+			{"label": "Location", "fieldname": "location", "fieldtype": "Link"},
+			{"label": "Healthcare Service Unit", "fieldname": "service_unit", "fieldtype": "Link"},
+			{"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"},
+			{"label": "Exercises", "fieldname": "exercises", "fieldtype": "Table"},
+			{"label": "Total Counts Targeted", "fieldname": "total_counts_targeted", "fieldtype": "Int"},
+			{"label": "Total Counts Completed", "fieldname": "total_counts_completed", "fieldtype": "Int"}
+		]),
+		"Vital Signs": ("signs_date", [
+			{"label": "Body Temperature", "fieldname": "temperature", "fieldtype": "Data"},
+			{"label": "Heart Rate / Pulse", "fieldname": "pulse", "fieldtype": "Data"},
+			{"label": "Respiratory rate", "fieldname": "respiratory_rate", "fieldtype": "Data"},
+			{"label": "Tongue", "fieldname": "tongue", "fieldtype": "Select"},
+			{"label": "Abdomen", "fieldname": "abdomen", "fieldtype": "Select"},
+			{"label": "Reflexes", "fieldname": "reflexes", "fieldtype": "Select"},
+			{"label": "Blood Pressure", "fieldname": "bp", "fieldtype": "Data"},
+			{"label": "Notes", "fieldname": "vital_signs_note", "fieldtype": "Small Text"},
+			{"label": "Height (In Meter)", "fieldname": "height", "fieldtype": "Float"},
+			{"label": "Weight (In Kilogram)", "fieldname": "weight", "fieldtype": "Float"},
+			{"label": "BMI", "fieldname": "bmi", "fieldtype": "Float"}
+		]),
+		"Inpatient Medication Order": ("start_date", [
+			{"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"},
+			{"label": "Start Date", "fieldname": "start_date", "fieldtype": "Date"},
+			{"label": "End Date", "fieldname": "end_date", "fieldtype": "Date"},
+			{"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"},
+			{"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"}
+		])
+	}
\ No newline at end of file
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index 96282f5..40f7f9c 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -6,6 +6,7 @@
 import math
 import frappe
 from frappe import _
+from frappe.utils.formatters import format_value
 from frappe.utils import time_diff_in_hours, rounded
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
 from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
@@ -77,11 +78,13 @@
 
 
 def get_encounters_to_invoice(patient, company):
+	if not isinstance(patient, str):
+		patient = patient.name
 	encounters_to_invoice = []
 	encounters = frappe.get_list(
 		'Patient Encounter',
 		fields=['*'],
-		filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1}
+		filters={'patient': patient, 'company': company, 'invoiced': False, 'docstatus': 1}
 	)
 	if encounters:
 		for encounter in encounters:
@@ -90,6 +93,10 @@
 				income_account = None
 				service_item = None
 				if encounter.practitioner:
+					if encounter.inpatient_record and \
+						frappe.db.get_single_value('Healthcare Settings', 'do_not_bill_inpatient_encounters'):
+						continue
+
 					service_item, practitioner_charge = get_service_item_and_practitioner_charge(encounter)
 					income_account = get_income_account(encounter.practitioner, encounter.company)
 
@@ -642,11 +649,15 @@
 				html += "<table class='table table-condensed table-bordered'>" \
 				+ table_head +  table_row + "</table>"
 			continue
+
 		#on other field types add label and value to html
 		if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields:
-			html +=  '<br>{0} : {1}'.format(df.label or df.fieldname, \
-			doc.get(df.fieldname))
+			if doc.get(df.fieldname):
+				formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc)
+				html +=  '<br>{0} : {1}'.format(df.label or df.fieldname, formatted_value)
+
 			if not has_data : has_data = True
+
 	if sec_on and col_on and has_data:
 		doc_html += section_html + html + '</div></div>'
 	elif sec_on and not col_on and has_data:
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index a2d9d86..1c20555 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -221,6 +221,11 @@
 }
 
 doc_events = {
+	"*": {
+		"on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record",
+		"on_update_after_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record",
+		"on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record"
+	},
 	"Stock Entry": {
 		"on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty",
 		"on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty"
@@ -341,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",
@@ -410,9 +416,6 @@
 	'Italy': {
 		'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.italy.utils.update_itemised_tax_data',
 		'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.italy.utils.sales_invoice_validate',
-	},
-	'Germany': {
-		'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.germany.accounts_controller.validate_regional',
 	}
 }
 user_privacy_documents = [
diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json
index 4f1c04f..dc2aaa4 100644
--- a/erpnext/hr/doctype/employee/employee.json
+++ b/erpnext/hr/doctype/employee/employee.json
@@ -813,7 +813,7 @@
  "idx": 24,
  "image_field": "image",
  "links": [],
- "modified": "2020-10-16 15:02:04.283657",
+ "modified": "2021-01-01 16:54:33.477439",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee",
@@ -855,7 +855,6 @@
    "write": 1
   }
  ],
- "quick_entry": 1,
  "search_fields": "employee_name",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index dfc600c..0fde3a1 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -135,7 +135,7 @@
 				try:
 					frappe.get_doc({
 						"doctype": "File",
-						"file_name": self.image,
+						"file_url": self.image,
 						"attached_to_doctype": "User",
 						"attached_to_name": self.user_id
 					}).insert()
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js
index 7056adf..5037ceb 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.js
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.js
@@ -18,13 +18,18 @@
 			if (!frm.doc.employee) {
 				frappe.msgprint(__("Please select employee first"));
 			}
-			var company_currency = erpnext.get_currency(frm.doc.company);
+			let company_currency = erpnext.get_currency(frm.doc.company);
+			let currencies = [company_currency];
+			if (frm.doc.currency && (frm.doc.currency != company_currency)) {
+				currencies.push(frm.doc.currency);
+			}
+
 			return {
 				filters: {
 					"root_type": "Asset",
 					"is_group": 0,
 					"company": frm.doc.company,
-					"account_currency": ["in", [frm.doc.currency, company_currency]],
+					"account_currency": ["in", currencies],
 				}
 			};
 		});
@@ -181,21 +186,23 @@
 	},
 
 	currency: function(frm) {
-		var from_currency = frm.doc.currency;
-		var company_currency;
-		if (!frm.doc.company) {
-			company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
-		} else {
-			company_currency = erpnext.get_currency(frm.doc.company);
+		if (frm.doc.currency) {
+			var from_currency = frm.doc.currency;
+			var company_currency;
+			if (!frm.doc.company) {
+				company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
+			} else {
+				company_currency = erpnext.get_currency(frm.doc.company);
+			}
+			if (from_currency != company_currency) {
+				frm.events.set_exchange_rate(frm, from_currency, company_currency);
+			} else {
+				frm.set_value("exchange_rate", 1.0);
+				frm.set_df_property('exchange_rate', 'hidden', 1);
+				frm.set_df_property("exchange_rate", "description", "" );
+			}
+			frm.refresh_fields();
 		}
-		if (from_currency != company_currency) {
-			frm.events.set_exchange_rate(frm, from_currency, company_currency);
-		} else {
-			frm.set_value("exchange_rate", 1.0);
-			frm.set_df_property('exchange_rate', 'hidden', 1);
-			frm.set_df_property("exchange_rate", "description", "" );
-		}
-		frm.refresh_fields();
 	},
 
 	set_exchange_rate: function(frm, from_currency, company_currency) {
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
index 4e9ee3b..336e13c 100644
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
@@ -38,7 +38,8 @@
 		onboarding.insert()
 		onboarding.submit()
 
-		self.assertEqual(onboarding.project, 'Employee Onboarding : Test Researcher - test@researcher.com')
+		project_name = frappe.db.get_value("Project", onboarding.project, "project_name")
+		self.assertEqual(project_name, 'Employee Onboarding : Test Researcher - test@researcher.com')
 
 		# don't allow making employee if onboarding is not complete
 		self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
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/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 4a0908d..f9e3a44 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -20,35 +20,36 @@
 		frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """)
 		frappe.db.sql("update `tabExpense Claim` set project = '', task = ''")
 
-		frappe.get_doc({
+		project = frappe.get_doc({
 			"project_name": "_Test Project 1",
 			"doctype": "Project"
-		}).save()
+		})
+		project.save()
 
 		task = frappe.get_doc(dict(
 			doctype = 'Task',
 			subject = '_Test Project Task 1',
 			status = 'Open',
-			project = '_Test Project 1'
+			project = project.name
 		)).insert()
 
 		task_name = task.name
 		payable_account = get_payable_account(company_name)
 
-		make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", "_Test Project 1", task_name)
+		make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", project.name, task_name)
 
 		self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
-		self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)
+		self.assertEqual(frappe.db.get_value("Project", project.name, "total_expense_claim"), 200)
 
-		expense_claim2 = make_expense_claim(payable_account, 600, 500, company_name, "Travel Expenses - _TC4","_Test Project 1", task_name)
+		expense_claim2 = make_expense_claim(payable_account, 600, 500, company_name, "Travel Expenses - _TC4", project.name, task_name)
 
 		self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 700)
-		self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700)
+		self.assertEqual(frappe.db.get_value("Project", project.name, "total_expense_claim"), 700)
 
 		expense_claim2.cancel()
 
 		self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
-		self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)
+		self.assertEqual(frappe.db.get_value("Project", project.name, "total_expense_claim"), 200)
 
 	def test_expense_claim_status(self):
 		payable_account = get_payable_account(company_name)
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/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 4b31501..3a300c0 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -11,6 +11,7 @@
   "employee",
   "employee_name",
   "department",
+  "company",
   "column_break1",
   "leave_type",
   "from_date",
@@ -219,6 +220,15 @@
    "label": "Leave Policy Assignment",
    "options": "Leave Policy Assignment",
    "read_only": 1
+  },
+  {
+   "fetch_from": "employee.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "read_only": 1,
+   "reqd": 1
   }
  ],
  "icon": "fa fa-ok",
@@ -226,7 +236,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-08-20 14:25:10.314323",
+ "modified": "2021-01-04 18:46:13.184104",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Allocation",
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
index 4abba5f..d74760a 100644
--- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2019-05-09 15:47:39.760406",
  "doctype": "DocType",
  "engine": "InnoDB",
@@ -8,6 +9,7 @@
   "leave_type",
   "transaction_type",
   "transaction_name",
+  "company",
   "leaves",
   "column_break_7",
   "from_date",
@@ -106,12 +108,22 @@
    "fieldtype": "Link",
    "label": "Holiday List",
    "options": "Holiday List"
+  },
+  {
+   "fetch_from": "employee.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "read_only": 1,
+   "reqd": 1
   }
  ],
  "in_create": 1,
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
- "modified": "2020-09-04 12:16:36.569066",
+ "links": [],
+ "modified": "2021-01-04 18:47:45.146652",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Ledger Entry",
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
index bbb4222..a0327bd 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json
@@ -111,13 +111,14 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-17 16:27:20.311060",
+ "modified": "2020-12-31 16:43:30.695206",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Policy Assignment",
  "owner": "Administrator",
  "permissions": [
   {
+   "cancel": 1,
    "create": 1,
    "delete": 1,
    "email": 1,
@@ -131,6 +132,7 @@
    "write": 1
   },
   {
+   "cancel": 1,
    "create": 1,
    "delete": 1,
    "email": 1,
@@ -144,6 +146,7 @@
    "write": 1
   },
   {
+   "cancel": 1,
    "create": 1,
    "delete": 1,
    "email": 1,
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index 2c385e8..ab65260 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -88,7 +88,7 @@
 
 def add_assignments(events, start, end, conditions=None):
 	query = """select name, start_date, end_date, employee_name,
-		employee, docstatus
+		employee, docstatus, shift_type
 		from `tabShift Assignment` where
 		start_date >= %(start_date)s
 		or end_date <=  %(end_date)s
@@ -97,18 +97,40 @@
 	if conditions:
 		query += conditions
 
-	for d in frappe.db.sql(query, {"start_date":start, "end_date":end}, as_dict=True):
-		e = {
-			"name": d.name,
-			"doctype": "Shift Assignment",
-			"start_date": d.start_date,
-			"end_date": d.end_date if d.end_date else nowdate(),
-			"title": cstr(d.employee_name) + ": "+ \
-				cstr(d.shift_type),
-			"docstatus": d.docstatus
-		}
-		if e not in events:
-			events.append(e)
+	records = frappe.db.sql(query, {"start_date":start, "end_date":end}, as_dict=True)
+	shift_timing_map = get_shift_type_timing([d.shift_type for d in records])
+
+	for d in records:
+		daily_event_start = d.start_date
+		daily_event_end = d.end_date if d.end_date else getdate()
+		delta = timedelta(days=1)
+		while daily_event_start <= daily_event_end:
+			start_timing = frappe.utils.get_datetime(daily_event_start)+ shift_timing_map[d.shift_type]['start_time']
+			end_timing = frappe.utils.get_datetime(daily_event_start)+ shift_timing_map[d.shift_type]['end_time']
+			daily_event_start += delta
+			e = {
+				"name": d.name,
+				"doctype": "Shift Assignment",
+				"start_date": start_timing,
+				"end_date": end_timing,
+				"title": cstr(d.employee_name) + ": "+ \
+					cstr(d.shift_type),
+				"docstatus": d.docstatus,
+				"allDay": 0
+			}
+			if e not in events:
+				events.append(e)
+
+	return events
+
+def get_shift_type_timing(shift_types):
+	shift_timing_map = {}
+	data = frappe.get_all("Shift Type", filters = {"name": ("IN", shift_types)}, fields = ['name', 'start_time', 'end_time'])
+
+	for d in data:
+		shift_timing_map[d.name] = d
+
+	return shift_timing_map
 
 
 def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=False, next_shift_direction=None):
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js b/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js
index 17a986d..bb692e1 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js
@@ -6,14 +6,8 @@
 		"start": "start_date",
 		"end": "end_date",
 		"id": "name",
-		"docstatus": 1
-	},
-	options: {
-		header: {
-			left: 'prev,next today',
-			center: 'title',
-			right: 'month'
-		}
+		"docstatus": 1,
+		"allDay": "allDay",
 	},
 	get_events_method: "erpnext.hr.doctype.shift_assignment.shift_assignment.get_events"
 }
\ No newline at end of file
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 cd40a66..e607d4f 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -6,6 +6,7 @@
 import frappe, math, json
 import erpnext
 from frappe import _
+from six import string_types
 from frappe.utils import flt, rounded, add_months, nowdate, getdate, now_datetime
 from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
 from erpnext.controllers.accounts_controller import AccountsController
@@ -201,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))
@@ -280,10 +283,13 @@
 		return write_off
 
 @frappe.whitelist()
-def unpledge_security(loan=None, loan_security_pledge=None, as_dict=0, save=0, submit=0, approve=0):
-	# if loan is passed it will be considered as full unpledge
+def unpledge_security(loan=None, loan_security_pledge=None, security_map=None, as_dict=0, save=0, submit=0, approve=0):
+	# if no security_map is passed it will be considered as full unpledge
+	if security_map and isinstance(security_map, string_types):
+		security_map = json.loads(security_map)
+
 	if loan:
-		pledge_qty_map = get_pledged_security_qty(loan)
+		pledge_qty_map = security_map or get_pledged_security_qty(loan)
 		loan_doc = frappe.get_doc('Loan', loan)
 		unpledge_request = create_loan_security_unpledge(pledge_qty_map, loan_doc.name, loan_doc.company,
 			loan_doc.applicant_type, loan_doc.applicant)
@@ -332,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 a63d065..f3c9db6 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -45,7 +45,7 @@
 		create_loan_security_price("Test Security 2", 250, "Nos", get_datetime() , get_datetime(add_to_date(nowdate(), hours=24)))
 
 		self.applicant1 = make_employee("robert_loan@loan.com")
-		make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR')
+		make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant1, currency='INR', company="_Test Company")
 		if not frappe.db.exists("Customer", "_Test Loan Customer"):
 			frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
 
@@ -321,10 +321,68 @@
 		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)
 
+	def test_partial_loan_security_unpledge(self):
+		pledge = [{
+			"loan_security": "Test Security 1",
+			"qty": 2000.00
+		},
+		{
+			"loan_security": "Test Security 2",
+			"qty": 4000.00
+		}]
+
+		loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+		create_pledge(loan_application)
+
+		loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+		loan.submit()
+
+		self.assertEquals(loan.loan_amount, 1000000)
+
+		first_date = '2019-10-01'
+		last_date = '2019-10-30'
+
+		make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
+		process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+
+		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), 600000)
+		repayment_entry.submit()
+
+		unpledge_map = {'Test Security 2': 2000}
+
+		unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
+		unpledge_request.submit()
+		unpledge_request.status = 'Approved'
+		unpledge_request.save()
+		unpledge_request.submit()
+		unpledge_request.load_from_db()
+		self.assertEqual(unpledge_request.docstatus, 1)
+
+	def test_santined_loan_security_unpledge(self):
+		pledge = [{
+			"loan_security": "Test Security 1",
+			"qty": 4000.00
+		}]
+
+		loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+		create_pledge(loan_application)
+
+		loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+		loan.submit()
+
+		self.assertEquals(loan.loan_amount, 1000000)
+
+		unpledge_map = {'Test Security 1': 4000}
+		unpledge_request = unpledge_security(loan=loan.name, security_map = unpledge_map, save=1)
+		unpledge_request.submit()
+		unpledge_request.status = 'Approved'
+		unpledge_request.save()
+		unpledge_request.submit()
+
 	def test_disbursal_check_with_shortfall(self):
 		pledges = [{
 			"loan_security": "Test Security 2",
@@ -415,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_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index 8ec0bfb..6469806 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -81,7 +81,6 @@
 				process_loan_security_shortfall)
 
 def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall):
-
 	existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
 
 	if existing_shortfall:
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index c29f325..c4c2d68 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -30,6 +30,8 @@
 					d.idx, frappe.bold(d.loan_security)))
 
 	def validate_unpledge_qty(self):
+		from erpnext.loan_management.doctype.loan_security_shortfall.loan_security_shortfall import get_ltv_ratio
+
 		pledge_qty_map = get_pledged_security_qty(self.loan)
 
 		ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type",
@@ -42,11 +44,19 @@
 				"valid_upto": (">=", get_datetime())
 			}, as_list=1))
 
-		total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid',
-			'total_interest_payable', 'written_off_amount'])
+		loan_details = frappe.get_value("Loan", self.loan, ['total_payment', 'total_principal_paid',
+			'total_interest_payable', 'written_off_amount', 'disbursed_amount', 'status'], as_dict=1)
 
-		pending_principal_amount = flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount)
+		if loan_details.status == 'Disbursed':
+			pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
+				- flt(loan_details.total_principal_paid) - flt(loan_details.written_off_amount)
+		else:
+			pending_principal_amount = flt(loan_details.disbursed_amount) - flt(loan_details.total_interest_payable) \
+				- flt(loan_details.total_principal_paid) - flt(loan_details.written_off_amount)
+
 		security_value = 0
+		unpledge_qty_map = {}
+		ltv_ratio = 0
 
 		for security in self.securities:
 			pledged_qty = pledge_qty_map.get(security.loan_security, 0)
@@ -57,13 +67,15 @@
 				msg += _("You are trying to unpledge more.")
 				frappe.throw(msg, title=_("Loan Security Unpledge Error"))
 
-			qty_after_unpledge = pledged_qty - security.qty
-			ltv_ratio = ltv_ratio_map.get(security.loan_security_type)
+			unpledge_qty_map.setdefault(security.loan_security, 0)
+			unpledge_qty_map[security.loan_security] += security.qty
 
-			current_price = loan_security_price_map.get(security.loan_security)
-			if not current_price:
-				frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(security.loan_security)))
+		for security in pledge_qty_map:
+			if not ltv_ratio:
+				ltv_ratio = get_ltv_ratio(security)
 
+			qty_after_unpledge = pledge_qty_map.get(security, 0) - unpledge_qty_map.get(security, 0)
+			current_price = loan_security_price_map.get(security)
 			security_value += qty_after_unpledge * current_price
 
 		if not security_value and flt(pending_principal_amount, 2) > 0:
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..ab586bc
--- /dev/null
+++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py
@@ -0,0 +1,139 @@
+# 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": _("Price Valid Upto"), "fieldname": "price_valid_upto", "fieldtype": "Datetime", "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):
+		if qty:
+			row = {}
+			current_value = flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0))
+			valid_upto = loan_security_details.get(key[1], {}).get('valid_upto')
+
+			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,
+				'price_valid_upto': valid_upto,
+				'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2) if total_value_map.get(key[0]) \
+					else 0.0,
+				'currency': currency
+			})
+
+			data.append(row)
+
+	return data
+
+def get_loan_security_details(filters):
+	security_detail_map = {}
+	loan_security_price_map = {}
+	lsp_validity_map = {}
+
+	loan_security_prices = frappe.db.sql("""
+		SELECT loan_security, loan_security_price, valid_upto
+		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_dict=1)
+
+	for security in loan_security_prices:
+		loan_security_price_map.setdefault(security.loan_security, security.loan_security_price)
+		lsp_validity_map.setdefault(security.loan_security, security.valid_upto)
+
+	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)),
+			'valid_upto': lsp_validity_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, {}).get('latest_price', 0)
+
+	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..2bfe6d3
--- /dev/null
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
@@ -0,0 +1,183 @@
+# 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
+from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \
+	 import get_loan_security_details
+
+
+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": _("Loan To Value Ratio"), "fieldname": "loan_to_value", "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]
+
+	current_pledges = get_loan_wise_pledges(filters)
+	loan_wise_security_value = get_loan_wise_security_value(filters, current_pledges)
+
+	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")),
+			"loan_to_value": 0.0,
+			"currency": currency
+		})
+
+		loan['total_outstanding'] = loan['principal_outstanding'] + loan['interest_outstanding'] \
+			+ loan['penalty']
+
+		if loan_wise_security_value.get(loan.loan):
+			loan['loan_to_value'] = (loan['principal_outstanding'] * 100) / loan_wise_security_value.get(loan.loan)
+
+	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))
+
+def get_loan_wise_pledges(filters):
+	loan_wise_unpledges = {}
+	current_pledges = {}
+
+	conditions = ""
+
+	if filters.get('company'):
+		conditions = "AND company = %(company)s"
+
+	unpledges = frappe.db.sql("""
+		SELECT up.loan, 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.loan
+	""".format(conditions=conditions), filters, as_dict=1)
+
+	for unpledge in unpledges:
+		loan_wise_unpledges.setdefault((unpledge.loan, unpledge.loan_security), unpledge.qty)
+
+	pledges = frappe.db.sql("""
+		SELECT lp.loan, 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.loan
+	""".format(conditions=conditions), filters, as_dict=1)
+
+	for security in pledges:
+		current_pledges.setdefault((security.loan, security.loan_security), security.qty)
+		current_pledges[(security.loan, security.loan_security)] -= \
+			loan_wise_unpledges.get((security.loan, security.loan_security), 0.0)
+
+	return current_pledges
+
+def get_loan_wise_security_value(filters, current_pledges):
+	loan_security_details = get_loan_security_details(filters)
+	loan_wise_security_value = {}
+
+	for key in current_pledges:
+		qty = current_pledges.get(key)
+		loan_wise_security_value.setdefault(key[0], 0.0)
+		loan_wise_security_value[key[0]] += \
+			flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0))
+
+	return loan_wise_security_value
\ No newline at end of file
diff --git a/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py b/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py
index b63cc8e..c6f6b99 100644
--- a/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py
+++ b/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py
@@ -103,7 +103,7 @@
 
 	loan_repayments = frappe.get_all("Loan Repayment",
 		filters = query_filters,
-		fields=["posting_date", "applicant", "name", "against_loan", "payment_type", "payable_amount",
+		fields=["posting_date", "applicant", "name", "against_loan", "payable_amount",
 			"pending_principal_amount", "interest_payable", "penalty_amount", "amount_paid"]
 	)
 
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..adc8013
--- /dev/null
+++ b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py
@@ -0,0 +1,84 @@
+# 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": _("Price Valid Upto"), "fieldname": "price_valid_upto", "fieldtype": "Datetime", "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):
+		if value.get('qty'):
+			row = {}
+			current_value = flt(value.get('qty', 0) * loan_security_details.get(security, {}).get('latest_price', 0))
+			valid_upto = loan_security_details.get(security, {}).get('valid_upto')
+
+			row.update(loan_security_details.get(security))
+			row.update({
+				'total_qty': value.get('qty'),
+				'current_value': current_value,
+				'price_valid_upto': valid_upto,
+				'portfolio_percent': flt(current_value * 100 / total_portfolio_value, 2),
+				'pledged_applicant_count': value.get('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/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 1c4b7a1..15affd8 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -411,7 +411,7 @@
 
 cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate;
 
-cur_frm.cscript.bom_no	= function(doc, cdt, cdn) {
+cur_frm.cscript.bom_no = function(doc, cdt, cdn) {
 	get_bom_material_detail(doc, cdt, cdn, false);
 };
 
@@ -419,17 +419,22 @@
 	if (doc.is_default) cur_frm.set_value("is_active", 1);
 };
 
-var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) {
+var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) {
+	if (!doc.company) {
+		frappe.throw({message: __("Please select a Company first."), title: __("Mandatory")});
+	}
+
 	var d = locals[cdt][cdn];
 	if (d.item_code) {
 		return frappe.call({
 			doc: doc,
 			method: "get_bom_material_detail",
 			args: {
-				'item_code': d.item_code,
-				'bom_no': d.bom_no != null ? d.bom_no: '',
+				"company": doc.company,
+				"item_code": d.item_code,
+				"bom_no": d.bom_no != null ? d.bom_no: '',
 				"scrap_items": scrap_items,
-				'qty': d.qty,
+				"qty": d.qty,
 				"stock_qty": d.stock_qty,
 				"include_item_in_manufacturing": d.include_item_in_manufacturing,
 				"uom": d.uom,
@@ -468,7 +473,7 @@
 	}
 
 	if (d.bom_no) {
-		frappe.msgprint(__("You can not change rate if BOM mentioned agianst any item"));
+		frappe.msgprint(__("You cannot change the rate if BOM is mentioned against any Item."));
 		get_bom_material_detail(doc, cdt, cdn, scrap_items);
 	} else {
 		erpnext.bom.calculate_rm_cost(doc);
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 6363242..03beedb 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -65,6 +65,10 @@
 
 	def validate(self):
 		self.route = frappe.scrub(self.name).replace('_', '-')
+
+		if not self.company:
+			frappe.throw(_("Please select a Company first."), title=_("Mandatory"))
+
 		self.clear_operations()
 		self.validate_main_item()
 		self.validate_currency()
@@ -125,6 +129,7 @@
 			self.validate_bom_currecny(item)
 
 			ret = self.get_bom_material_detail({
+				"company": self.company,
 				"item_code": item.item_code,
 				"item_name": item.item_name,
 				"bom_no": item.bom_no,
@@ -213,6 +218,7 @@
 
 		for d in self.get("items"):
 			rate = self.get_rm_rate({
+				"company": self.company,
 				"item_code": d.item_code,
 				"bom_no": d.bom_no,
 				"qty": d.qty,
@@ -611,10 +617,20 @@
 	""" Get weighted average of valuation rate from all warehouses """
 
 	total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0
-	for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin`
-		where item_code=%s""", args['item_code'], as_dict=1):
-			total_qty += flt(d.actual_qty)
-			total_value += flt(d.stock_value)
+	item_bins = frappe.db.sql("""
+		select
+			bin.actual_qty, bin.stock_value
+		from
+			`tabBin` bin, `tabWarehouse` warehouse
+		where
+			bin.item_code=%(item)s
+			and bin.warehouse = warehouse.name
+			and warehouse.company=%(company)s""",
+		{"item": args['item_code'], "company": args['company']}, as_dict=1)
+
+	for d in item_bins:
+		total_qty += flt(d.actual_qty)
+		total_value += flt(d.stock_value)
 
 	if total_qty:
 		valuation_rate =  total_value / total_qty
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index d15d81e..ec28eb7 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -17,6 +17,7 @@
 
 class OperationMismatchError(frappe.ValidationError): pass
 class OperationSequenceError(frappe.ValidationError): pass
+class JobCardCancelError(frappe.ValidationError): pass
 
 class JobCard(Document):
 	def validate(self):
@@ -217,33 +218,49 @@
 		field = "operation_id"
 		data = self.get_current_operation_data()
 		if data and len(data) > 0:
-			for_quantity = data[0].completed_qty
-			time_in_mins = data[0].time_in_mins
+			for_quantity = flt(data[0].completed_qty)
+			time_in_mins = flt(data[0].time_in_mins)
 
-		if self.get(field):
-			time_data = frappe.db.sql("""
+		wo = frappe.get_doc('Work Order', self.work_order)
+		if self.operation_id:
+			self.validate_produced_quantity(for_quantity, wo)
+			self.update_work_order_data(for_quantity, time_in_mins, wo)
+
+	def validate_produced_quantity(self, for_quantity, wo):
+		if self.docstatus < 2: return
+
+		if wo.produced_qty > for_quantity:
+			first_part_msg = (_("The {0} {1} is used to calculate the valuation cost for the finished good {2}.")
+				.format(frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item)))
+
+			second_part_msg = (_("Kindly cancel the Manufacturing Entries first against the work order {0}.")
+				.format(frappe.bold(get_link_to_form("Work Order", self.work_order))))
+
+			frappe.throw(_("{0} {1}").format(first_part_msg, second_part_msg),
+				JobCardCancelError, title = _("Error"))
+
+	def update_work_order_data(self, for_quantity, time_in_mins, wo):
+		time_data = frappe.db.sql("""
 				SELECT
 					min(from_time) as start_time, max(to_time) as end_time
 				FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
 				WHERE
 					jctl.parent = jc.name and jc.work_order = %s
-					and jc.{0} = %s and jc.docstatus = 1
-			""".format(field), (self.work_order, self.get(field)), as_dict=1)
+					and jc.operation_id = %s and jc.docstatus = 1
+			""", (self.work_order, self.operation_id), as_dict=1)
 
-			wo = frappe.get_doc('Work Order', self.work_order)
+		for data in wo.operations:
+			if data.get("name") == self.operation_id:
+				data.completed_qty = for_quantity
+				data.actual_operation_time = time_in_mins
+				data.actual_start_time = time_data[0].start_time if time_data else None
+				data.actual_end_time = time_data[0].end_time if time_data else None
 
-			for data in wo.operations:
-				if data.get("name") == self.get(field):
-					data.completed_qty = for_quantity
-					data.actual_operation_time = time_in_mins
-					data.actual_start_time = time_data[0].start_time if time_data else None
-					data.actual_end_time = time_data[0].end_time if time_data else None
-
-			wo.flags.ignore_validate_update_after_submit = True
-			wo.update_operation_status()
-			wo.calculate_operating_cost()
-			wo.set_actual_dates()
-			wo.save()
+		wo.flags.ignore_validate_update_after_submit = True
+		wo.update_operation_status()
+		wo.calculate_operating_cost()
+		wo.set_actual_dates()
+		wo.save()
 
 	def get_current_operation_data(self):
 		return frappe.get_all('Job Card',
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index ce9699e..06a8e19 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import unittest
 import frappe
-from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today
+from frappe.utils import flt, now, add_months, cint, today, add_to_date
 from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
 	ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError)
 from erpnext.stock.doctype.stock_entry import test_stock_entry
@@ -14,6 +14,7 @@
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
 
 class TestWorkOrder(unittest.TestCase):
 	def setUp(self):
@@ -369,21 +370,49 @@
 		self.assertEqual(ste.total_additional_costs, 1000)
 
 	def test_job_card(self):
+		stock_entries = []
 		data = frappe.get_cached_value('BOM',
 			{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
 
-		if data:
-			frappe.db.set_value("Manufacturing Settings",
-				None, "disable_capacity_planning", 0)
+		bom, bom_item = data
 
-			bom, bom_item = data
+		bom_doc = frappe.get_doc('BOM', bom)
+		work_order = make_wo_order_test_record(item=bom_item, qty=1,
+			bom_no=bom, source_warehouse="_Test Warehouse - _TC")
 
-			bom_doc = frappe.get_doc('BOM', bom)
-			work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
-			self.assertTrue(work_order.planned_end_date)
+		for row in work_order.required_items:
+			stock_entry_doc = test_stock_entry.make_stock_entry(item_code=row.item_code,
+				target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100)
+			stock_entries.append(stock_entry_doc)
 
-			job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
-			self.assertEqual(len(job_cards), len(bom_doc.operations))
+		ste = frappe.get_doc(make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1))
+		ste.submit()
+		stock_entries.append(ste)
+
+		job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
+		self.assertEqual(len(job_cards), len(bom_doc.operations))
+
+		for i, job_card in enumerate(job_cards):
+			doc = frappe.get_doc("Job Card", job_card)
+			doc.append("time_logs", {
+				"from_time": now(),
+				"hours": i,
+				"to_time": add_to_date(now(), i),
+				"completed_qty": doc.for_quantity
+			})
+			doc.submit()
+
+		ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
+		ste1.submit()
+		stock_entries.append(ste1)
+
+		for job_card in job_cards:
+			doc = frappe.get_doc("Job Card", job_card)
+			self.assertRaises(JobCardCancelError, doc.cancel)
+
+		stock_entries.reverse()
+		for stock_entry in stock_entries:
+			stock_entry.cancel()
 
 	def test_capcity_planning(self):
 		frappe.db.set_value("Manufacturing Settings", None, {
@@ -509,19 +538,60 @@
 		ste1.submit()
 		ste_cancel_list.append(ste1)
 
-		print(wo_order.name)
 		ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
 		self.assertEquals(ste3.fg_completed_qty, 2)
 
 		expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4}
 		for row in ste3.items:
 			self.assertEquals(row.qty, expected_qty.get(row.item_code))
-
+		ste_cancel_list.reverse()
 		for ste_doc in ste_cancel_list:
 			ste_doc.cancel()
 
 		frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
 
+	def test_extra_material_transfer(self):
+		frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
+		frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on",
+			"Material Transferred for Manufacture")
+
+		wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
+
+		ste_cancel_list = []
+		ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
+			target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
+		ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+			target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
+
+		ste_cancel_list.extend([ste1, ste2])
+
+		itemwise_qty = {}
+		s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
+		for row in s.items:
+			row.qty = row.qty + 2
+			itemwise_qty.setdefault(row.item_code, row.qty)
+
+		s.submit()
+		ste_cancel_list.append(s)
+
+		ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
+		for ste_row in ste3.items:
+			if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse:
+				self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
+
+		ste3.submit()
+		ste_cancel_list.append(ste3)
+
+		ste2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
+		for ste_row in ste2.items:
+			if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse:
+				self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
+		ste_cancel_list.reverse()
+		for ste_doc in ste_cancel_list:
+			ste_doc.cancel()
+
+		frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
+
 def get_scrap_item_details(bom_no):
 	scrap_items = {}
 	for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index cc93bf9..ca530bb 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -456,10 +456,10 @@
 
 			if data and len(data):
 				dates = [d.posting_datetime for d in data]
-				self.actual_start_date = min(dates)
+				self.db_set('actual_start_date', min(dates))
 
 				if self.status == "Completed":
-					self.actual_end_date = max(dates)
+					self.db_set('actual_end_date', max(dates))
 
 		self.set_lead_time()
 
@@ -725,6 +725,7 @@
 		args.update(item_data)
 
 		args["rate"] = get_bom_item_rate({
+			"company": wo_doc.company,
 			"item_code": args.get("item_code"),
 			"qty": args.get("required_qty"),
 			"uom": args.get("stock_uom"),
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
index 75ebcbc..1c6758e 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
@@ -20,6 +20,7 @@
 		_("Item") + ":Link/Item:150",
 		_("Description") + "::300",
 		_("BOM Qty") + ":Float:160",
+		_("BOM UoM") + "::160",
 		_("Required Qty") + ":Float:120",
 		_("In Stock Qty") + ":Float:120",
 		_("Enough Parts to Build") + ":Float:200",
@@ -32,7 +33,7 @@
 	bom = filters.get("bom")
 
 	table = "`tabBOM Item`"
-	qty_field = "qty"
+	qty_field = "stock_qty"
 
 	qty_to_produce = filters.get("qty_to_produce", 1)
 	if  int(qty_to_produce) <= 0:
@@ -40,7 +41,6 @@
 
 	if filters.get("show_exploded_view"):
 		table = "`tabBOM Explosion Item`"
-		qty_field = "stock_qty"
 
 	if filters.get("warehouse"):
 		warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
@@ -59,6 +59,7 @@
 				bom_item.item_code,
 				bom_item.description ,
 				bom_item.{qty_field},
+				bom_item.stock_uom,
 				bom_item.{qty_field} * {qty_to_produce} / bom.quantity,
 				sum(ledger.actual_qty) as actual_qty,
 				sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity)))
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 44b975e..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.user,
+			'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):
@@ -177,4 +177,4 @@
 			mobile=mobile
 		))
 
-		return member.name
\ No newline at end of file
+		return member.name
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 ca8e01c..6e28865 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -450,7 +450,6 @@
 erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile
 erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order
 erpnext.patches.v9_0.student_admission_childtable_migrate
-erpnext.patches.v9_0.fix_subscription_next_date #2017-10-23
 erpnext.patches.v9_0.add_healthcare_domain
 erpnext.patches.v9_0.set_variant_item_description
 erpnext.patches.v9_0.set_uoms_in_variant_field
@@ -737,10 +736,16 @@
 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
 erpnext.patches.v13_0.update_returned_qty_in_pr_dn
-erpnext.patches.v13_0.create_uae_pos_invoice_fields
\ No newline at end of file
+erpnext.patches.v13_0.create_uae_pos_invoice_fields
+erpnext.patches.v13_0.update_project_template_tasks
+erpnext.patches.v13_0.set_company_in_leave_ledger_entry
+erpnext.patches.v13_0.convert_qi_parameter_to_link_field
+erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
+erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
diff --git a/erpnext/patches/v13_0/add_naming_series_to_old_projects.py b/erpnext/patches/v13_0/add_naming_series_to_old_projects.py
new file mode 100644
index 0000000..5ed9040
--- /dev/null
+++ b/erpnext/patches/v13_0/add_naming_series_to_old_projects.py
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter, delete_property_setter
+
+def execute():
+	frappe.reload_doc("projects", "doctype", "project")
+
+	frappe.db.sql("""UPDATE `tabProject`
+		SET
+			naming_series = 'PROJ-.####'
+		WHERE
+			naming_series is NULL""")
+
diff --git a/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py
new file mode 100644
index 0000000..289b6a7
--- /dev/null
+++ b/erpnext/patches/v13_0/convert_qi_parameter_to_link_field.py
@@ -0,0 +1,23 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doc('stock', 'doctype', 'quality_inspection_parameter')
+
+	# get all distinct parameters from QI readigs table
+	reading_params = frappe.db.get_all("Quality Inspection Reading", fields=["distinct specification"])
+	reading_params = [d.specification for d in reading_params]
+
+	# get all distinct parameters from QI Template as some may be unused in QI
+	template_params = frappe.db.get_all("Item Quality Inspection Parameter", fields=["distinct specification"])
+	template_params = [d.specification for d in template_params]
+
+	params = list(set(reading_params + template_params))
+
+	for parameter in params:
+		if not frappe.db.exists("Quality Inspection Parameter", parameter):
+			frappe.get_doc({
+				"doctype": "Quality Inspection Parameter",
+				"parameter": parameter,
+				"description": parameter
+			}).insert(ignore_permissions=True)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py
new file mode 100644
index 0000000..66857c4
--- /dev/null
+++ b/erpnext/patches/v13_0/set_company_in_leave_ledger_entry.py
@@ -0,0 +1,7 @@
+import frappe
+
+def execute():
+	frappe.reload_doc('HR', 'doctype', 'Leave Allocation')
+	frappe.reload_doc('HR', 'doctype', 'Leave Ledger Entry')
+	frappe.db.sql("""update `tabLeave Ledger Entry` as lle set company = (select company from `tabEmployee` where employee = lle.employee)""")
+	frappe.db.sql("""update `tabLeave Allocation` as la set company = (select company from `tabEmployee` where employee = la.employee)""")
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py
new file mode 100644
index 0000000..de08aa2
--- /dev/null
+++ b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+import frappe
+from erpnext.healthcare.setup import setup_patient_history_settings
+
+def execute():
+	if "Healthcare" not in frappe.get_active_domains():
+		return
+
+	frappe.reload_doc("healthcare", "doctype", "Patient History Settings")
+	frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type")
+	frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type")
+
+	setup_patient_history_settings()
\ No newline at end of file
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/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py
index 561e967..8cf09aa 100644
--- a/erpnext/patches/v13_0/update_old_loans.py
+++ b/erpnext/patches/v13_0/update_old_loans.py
@@ -1,7 +1,7 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import nowdate
+from frappe.utils import nowdate, flt
 from erpnext.accounts.doctype.account.test_account import create_account
 from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
 from erpnext.loan_management.doctype.loan.loan import make_repayment_entry
@@ -113,15 +113,15 @@
 					interest_paid = 0
 					principal_paid = 0
 
-					if total_interest > entry.interest_amount:
-						interest_paid = entry.interest_amount
+					if flt(total_interest) > flt(entry.interest_amount):
+						interest_paid = flt(entry.interest_amount)
 					else:
-						interest_paid = total_interest
+						interest_paid = flt(total_interest)
 
-					if total_principal > entry.payable_principal_amount:
-						principal_paid = entry.payable_principal_amount
+					if flt(total_principal) > flt(entry.payable_principal_amount):
+						principal_paid = flt(entry.payable_principal_amount)
 					else:
-						principal_paid = total_principal
+						principal_paid = flt(total_principal)
 
 					frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
 						SET paid_principal_amount = `paid_principal_amount` + %s,
@@ -129,8 +129,8 @@
 						WHERE name = %s""",
 						(principal_paid, interest_paid, entry.name))
 
-					total_principal -= principal_paid
-					total_interest -= interest_paid
+					total_principal = flt(total_principal) - principal_paid
+					total_interest = flt(total_interest) - interest_paid
 
 def create_loan_type(loan, loan_type_name, penalty_account):
 	loan_type_doc = frappe.new_doc('Loan Type')
diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py
new file mode 100644
index 0000000..8cc27d2
--- /dev/null
+++ b/erpnext/patches/v13_0/update_project_template_tasks.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doc("projects", "doctype", "project_template")
+	frappe.reload_doc("projects", "doctype", "project_template_task")
+	frappe.reload_doc("projects", "doctype", "task")
+
+	# Update property setter status if any
+	property_setter = frappe.db.get_value('Property Setter', {'doc_type': 'Task',
+		'field_name': 'status', 'property': 'options'})
+
+	if property_setter:
+		property_setter_doc = frappe.get_doc('Property Setter', {'doc_type': 'Task',
+			'field_name': 'status', 'property': 'options'})
+		property_setter_doc.value += "\nTemplate"
+		property_setter_doc.save()
+
+	for template_name in frappe.get_all('Project Template'):
+		template = frappe.get_doc("Project Template", template_name.name)
+		replace_tasks = False
+		new_tasks = []
+		for task in template.tasks:
+			if task.subject:
+				replace_tasks = True
+				new_task = frappe.get_doc(dict(
+					doctype = "Task",
+					subject = task.subject,
+					start = task.start,
+					duration = task.duration,
+					task_weight = task.task_weight,
+					description = task.description,
+					is_template = 1
+				)).insert()
+				new_tasks.append(new_task)
+
+		if replace_tasks:
+			template.tasks = []
+			for tsk in new_tasks:
+				template.append("tasks", {
+					"task": tsk.name,
+					"subject": tsk.subject
+				})
+			template.save()
\ No newline at end of file
diff --git a/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py b/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py
index ad043dd..97e217a 100644
--- a/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py
+++ b/erpnext/patches/v4_0/map_charge_to_taxes_and_charges.py
@@ -5,11 +5,11 @@
 import frappe
 
 def execute():
-	# udpate sales cycle
+	# update sales cycle
 	for d in ['Sales Invoice', 'Sales Order', 'Quotation', 'Delivery Note']:
 		frappe.db.sql("""update `tab%s` set taxes_and_charges=charge""" % d)
 
-	# udpate purchase cycle
+	# update purchase cycle
 	for d in ['Purchase Invoice', 'Purchase Order', 'Supplier Quotation', 'Purchase Receipt']:
 		frappe.db.sql("""update `tab%s` set taxes_and_charges=purchase_other_charges""" % d)
 	
diff --git a/erpnext/patches/v9_0/fix_subscription_next_date.py b/erpnext/patches/v9_0/fix_subscription_next_date.py
deleted file mode 100644
index 4595c8d..0000000
--- a/erpnext/patches/v9_0/fix_subscription_next_date.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (c) 2017, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe.utils import getdate
-from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
-
-def execute():
-	frappe.reload_doc('accounts', 'doctype', 'subscription')
-	fields = ["name", "reference_doctype", "reference_document",
-			"start_date", "frequency", "repeat_on_day"]
-
-	for d in fields:
-		if not frappe.db.has_column('Subscription', d):
-			return
-
-	doctypes = ('Purchase Order', 'Sales Order', 'Purchase Invoice', 'Sales Invoice')
-	for data in frappe.get_all('Subscription',
-		fields = fields,
-		filters = {'reference_doctype': ('in', doctypes), 'docstatus': 1}):
-
-		recurring_id = frappe.db.get_value(data.reference_doctype, data.reference_document, "recurring_id")
-		if recurring_id:
-			frappe.db.sql("update `tab{0}` set subscription=%s where recurring_id=%s"
-				.format(data.reference_doctype), (data.name, recurring_id))
-
-		date_field = 'transaction_date'
-		if data.reference_doctype in ['Sales Invoice', 'Purchase Invoice']:
-			date_field = 'posting_date'
-
-		start_date = frappe.db.get_value(data.reference_doctype, data.reference_document, date_field)
-
-		if start_date and getdate(start_date) != getdate(data.start_date):
-			last_ref_date = frappe.db.sql("""
-				select {0}
-				from `tab{1}`
-				where subscription=%s and docstatus < 2
-				order by creation desc
-				limit 1
-			""".format(date_field, data.reference_doctype), data.name)[0][0]
-
-			next_schedule_date = get_next_schedule_date(last_ref_date, data.frequency, data.repeat_on_day)
-
-			frappe.db.set_value("Subscription", data.name, {
-				"start_date": start_date,
-				"next_schedule_date": next_schedule_date
-			}, None)
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.js b/erpnext/payroll/doctype/additional_salary/additional_salary.js
index 0784de9..d1ed91f 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.js
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.js
@@ -13,13 +13,7 @@
 			};
 		});
 
-		if (!frm.doc.currency) return;
-		frm.set_query("salary_component", function() {
-			return {
-				query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
-				filters: {currency: frm.doc.currency, company: frm.doc.company}
-			};
-		});
+		frm.trigger('set_earning_component');
 	},
 
 	employee: function(frm) {
@@ -51,6 +45,19 @@
 		});
 	},
 
+	company: function(frm) {
+		frm.trigger('set_earning_component');
+	},
+
+	set_earning_component: function(frm) {
+		if (!frm.doc.company) return;
+		frm.set_query("salary_component", function() {
+			return {
+				filters: {type: ["in", ["earning", "deduction"]], company: frm.doc.company}
+			};
+		});
+	},
+
 	get_employee_currency: function(frm) {
 		frappe.call({
 			method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
index 9a5a463..4c45580 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
@@ -23,6 +23,7 @@
   "employee_benefits",
   "totals",
   "total_amount",
+  "column_break",
   "pro_rata_dispensed_amount"
  ],
  "fields": [
@@ -139,11 +140,15 @@
    "label": "Company",
    "options": "Company",
    "reqd": 1
+  },
+  {
+   "fieldname": "column_break",
+   "fieldtype": "Column Break"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-25 11:49:05.095101",
+ "modified": "2020-12-14 15:52:08.566418",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Benefit Application",
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.js b/erpnext/payroll/doctype/employee_incentive/employee_incentive.js
index 85d1c54..b2809b1 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.js
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.js
@@ -10,15 +10,7 @@
 				}
 			};
 		});
-
-		if (!frm.doc.currency) return;
-		frm.set_query("salary_component", function() {
-			return {
-				query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
-				filters: {type: "earning", currency: frm.doc.currency, company: frm.doc.company}
-			};
-		});
-
+		frm.trigger('set_earning_component');
 	},
 
 	employee: function(frm) {
@@ -45,11 +37,21 @@
 			callback: function(data) {
 				if (data.message) {
 					frm.set_value("company", data.message.company);
+					frm.trigger('set_earning_component');
 				}
 			}
 		});
 	},
 
+	set_earning_component: function(frm) {
+		if (!frm.doc.company) return;
+		frm.set_query("salary_component", function() {
+			return {
+				filters: {type: "earning", company: frm.doc.company}
+			};
+		});
+	},
+
 	get_employee_currency: function(frm) {
 		frappe.call({
 			method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
index 0609d19..311f352 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
@@ -86,19 +86,21 @@
 
 		self.assertEqual(declaration.total_exemption_amount, 100000)
 
-def create_payroll_period():
-	if not frappe.db.exists("Payroll Period", "_Test Payroll Period"):
+def create_payroll_period(**args):
+	args = frappe._dict(args)
+	name = args.name or "_Test Payroll Period"
+	if not frappe.db.exists("Payroll Period", name):
 		from datetime import date
 		payroll_period = frappe.get_doc(dict(
 			doctype = 'Payroll Period',
-			name = "_Test Payroll Period",
-			company =  erpnext.get_default_company(),
-			start_date = date(date.today().year, 1, 1),
-			end_date = date(date.today().year, 12, 31)
+			name = name,
+			company =  args.company or erpnext.get_default_company(),
+			start_date = args.start_date or date(date.today().year, 1, 1),
+			end_date = args.end_date or date(date.today().year, 12, 31)
 		)).insert()
 		return payroll_period
 	else:
-		return frappe.get_doc("Payroll Period", "_Test Payroll Period")
+		return frappe.get_doc("Payroll Period", name)
 
 def create_exemption_category():
 	if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"):
diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py
index 253f023..81e3647 100644
--- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py
+++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.py
@@ -3,8 +3,11 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-# import frappe
+#import frappe
+import erpnext
 from frappe.model.document import Document
 
 class IncomeTaxSlab(Document):
-	pass
+	def validate(self):
+		if self.company:
+			self.currency = erpnext.get_company_currency(self.company)
diff --git a/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json b/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
index 8a55224..09c7eb9 100644
--- a/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
+++ b/erpnext/payroll/doctype/payroll_employee_detail/payroll_employee_detail.json
@@ -17,8 +17,7 @@
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Employee",
-   "options": "Employee",
-   "read_only": 1
+   "options": "Employee"
   },
   {
    "fetch_from": "employee.employee_name",
@@ -52,7 +51,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-09-30 12:40:07.999878",
+ "modified": "2020-12-17 15:43:29.542977",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Payroll Employee Detail",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index cb48abb..0dcea88 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,15 +12,23 @@
 		}
 		frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet);
 
-		frm.set_query("department", function() {
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+		frm.events.department_filters(frm);
+		frm.events.payroll_payable_account_filters(frm);
+	},
+
+	department_filters: function (frm) {
+		frm.set_query("department", function () {
 			return {
 				"filters": {
 					"company": frm.doc.company,
 				}
 			};
 		});
+	},
 
-		frm.set_query("payroll_payable_account", function() {
+	payroll_payable_account_filters: function (frm) {
+		frm.set_query("payroll_payable_account", function () {
 			return {
 				filters: {
 					"company": frm.doc.company,
@@ -29,20 +39,20 @@
 		});
 	},
 
-	refresh: function(frm) {
+	refresh: function (frm) {
 		if (frm.doc.docstatus == 0) {
-			if(!frm.is_new()) {
+			if (!frm.is_new()) {
 				frm.page.clear_primary_action();
 				frm.add_custom_button(__("Get Employees"),
-					function() {
+					function () {
 						frm.events.get_employee_details(frm);
 					}
 				).toggleClass('btn-primary', !(frm.doc.employees || []).length);
 			}
-			if ((frm.doc.employees || []).length) {
+			if ((frm.doc.employees || []).length && !frappe.model.has_workflow(frm.doctype)) {
 				frm.page.clear_primary_action();
 				frm.page.set_primary_action(__('Create Salary Slips'), () => {
-					frm.save('Submit').then(()=>{
+					frm.save('Submit').then(() => {
 						frm.page.clear_primary_action();
 						frm.refresh();
 						frm.events.refresh(frm);
@@ -61,48 +71,48 @@
 			doc: frm.doc,
 			method: 'fill_employee_details',
 		}).then(r => {
-			if (r.docs && r.docs[0].employees){
+			if (r.docs && r.docs[0].employees) {
 				frm.employees = r.docs[0].employees;
 				frm.dirty();
 				frm.save();
 				frm.refresh();
-				if(r.docs[0].validate_attendance){
+				if (r.docs[0].validate_attendance) {
 					render_employee_attendance(frm, r.message);
 				}
 			}
-		})
+		});
 	},
 
-	create_salary_slips: function(frm) {
+	create_salary_slips: function (frm) {
 		frm.call({
 			doc: frm.doc,
 			method: "create_salary_slips",
-			callback: function(r) {
+			callback: function () {
 				frm.refresh();
 				frm.toolbar.refresh();
 			}
-		})
+		});
 	},
 
-	add_context_buttons: function(frm) {
-		if(frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
+	add_context_buttons: function (frm) {
+		if (frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) {
 			frm.events.add_bank_entry_button(frm);
-		} else if(frm.doc.salary_slips_created) {
-			frm.add_custom_button(__("Submit Salary Slip"), function() {
+		} else if (frm.doc.salary_slips_created) {
+			frm.add_custom_button(__("Submit Salary Slip"), function () {
 				submit_salary_slip(frm);
 			}).addClass("btn-primary");
 		}
 	},
 
-	add_bank_entry_button: function(frm) {
+	add_bank_entry_button: function (frm) {
 		frappe.call({
 			method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.payroll_entry_has_bank_entries',
 			args: {
 				'name': frm.doc.name
 			},
-			callback: function(r) {
+			callback: function (r) {
 				if (r.message && !r.message.submitted) {
-					frm.add_custom_button("Make Bank Entry", function() {
+					frm.add_custom_button("Make Bank Entry", function () {
 						make_bank_entry(frm);
 					}).addClass("btn-primary");
 				}
@@ -122,31 +132,43 @@
 					"company": frm.doc.company
 				}
 			};
-		}),
-		frm.set_query("cost_center", function () {
+		});
+
+		frm.set_query('employee', 'employees', () => {
+			if (!frm.doc.company) {
+				frappe.msgprint(__("Please set a Company"));
+				return []
+			}
+			let filters = {};
+			filters['company'] = frm.doc.company;
+			filters['start_date'] = frm.doc.start_date;
+			filters['end_date'] = frm.doc.end_date;
+
+			if (frm.doc.department) {
+				filters['department'] = frm.doc.department;
+			}
+			if (frm.doc.branch) {
+				filters['branch'] = frm.doc.branch;
+			}
+			if (frm.doc.designation) {
+				filters['designation'] = frm.doc.designation;
+			}
 			return {
-				filters: {
-					"is_group": 0,
-					company: frm.doc.company
-				}
-			};
-		}),
-		frm.set_query("project", function () {
-			return {
-				filters: {
-					company: frm.doc.company
-				}
-			};
+				query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.employee_query",
+				filters: filters
+			}
 		});
 	},
 
 	payroll_frequency: function (frm) {
-		frm.trigger("set_start_end_dates");
-		frm.events.clear_employee_table(frm);
+		frm.trigger("set_start_end_dates").then( ()=> {
+			frm.events.clear_employee_table(frm);
+		});
 	},
 
 	company: function (frm) {
 		frm.events.clear_employee_table(frm);
+		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 	},
 
 	currency: function (frm) {
@@ -164,17 +186,17 @@
 						from_currency: frm.doc.currency,
 						to_currency: company_currency,
 					},
-					callback: function(r) {
+					callback: function (r) {
 						frm.set_value("exchange_rate", flt(r.message));
 						frm.set_df_property('exchange_rate', 'hidden', 0);
-						frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
-							+ " = [?] " + company_currency);
+						frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
+							" = [?] " + company_currency);
 					}
 				});
 			} else {
 				frm.set_value("exchange_rate", 1.0);
 				frm.set_df_property('exchange_rate', 'hidden', 1);
-				frm.set_df_property("exchange_rate", "description", "" );
+				frm.set_df_property("exchange_rate", "description", "");
 			}
 		}
 	},
@@ -192,9 +214,9 @@
 	},
 
 	start_date: function (frm) {
-		if(!in_progress && frm.doc.start_date){
+		if (!in_progress && frm.doc.start_date) {
 			frm.trigger("set_end_date");
-		}else{
+		} else {
 			// reset flag
 			in_progress = false;
 		}
@@ -228,7 +250,7 @@
 		}
 	},
 
-	set_end_date: function(frm){
+	set_end_date: function (frm) {
 		frappe.call({
 			method: 'erpnext.payroll.doctype.payroll_entry.payroll_entry.get_end_date',
 			args: {
@@ -243,19 +265,19 @@
 		});
 	},
 
-	validate_attendance: function(frm){
-		if(frm.doc.validate_attendance && frm.doc.employees){
+	validate_attendance: function (frm) {
+		if (frm.doc.validate_attendance && frm.doc.employees) {
 			frappe.call({
 				method: 'validate_employee_attendance',
 				args: {},
-				callback: function(r) {
+				callback: function (r) {
 					render_employee_attendance(frm, r.message);
 				},
 				doc: frm.doc,
 				freeze: true,
 				freeze_message: __('Validating Employee Attendance...')
 			});
-		}else{
+		} else {
 			frm.fields_dict.attendance_detail_html.html("");
 		}
 	},
@@ -270,18 +292,20 @@
 
 const submit_salary_slip = function (frm) {
 	frappe.confirm(__('This will submit Salary Slips and create accrual Journal Entry. Do you want to proceed?'),
-		function() {
+		function () {
 			frappe.call({
 				method: 'submit_salary_slips',
 				args: {},
-				callback: function() {frm.events.refresh(frm);},
+				callback: function () {
+					frm.events.refresh(frm);
+				},
 				doc: frm.doc,
 				freeze: true,
 				freeze_message: __('Submitting Salary Slips and creating Journal Entry...')
 			});
 		},
-		function() {
-			if(frappe.dom.freeze_count) {
+		function () {
+			if (frappe.dom.freeze_count) {
 				frappe.dom.unfreeze();
 				frm.events.refresh(frm);
 			}
@@ -295,9 +319,11 @@
 		return frappe.call({
 			doc: cur_frm.doc,
 			method: "make_payment_entry",
-			callback: function() {
+			callback: function () {
 				frappe.set_route(
-					'List', 'Journal Entry', {"Journal Entry Account.reference_name": frm.doc.name}
+					'List', 'Journal Entry', {
+						"Journal Entry Account.reference_name": frm.doc.name
+					}
 				);
 			},
 			freeze: true,
@@ -309,11 +335,10 @@
 	}
 };
 
-
-let render_employee_attendance = function(frm, data) {
+let render_employee_attendance = function (frm, data) {
 	frm.fields_dict.attendance_detail_html.html(
 		frappe.render_template('employees_to_mark_attendance', {
 			data: data
 		})
 	);
-}
+};
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
index 7a48dd1..0444134 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.json
@@ -129,8 +129,7 @@
    "fieldname": "employees",
    "fieldtype": "Table",
    "label": "Employee Details",
-   "options": "Payroll Employee Detail",
-   "read_only": 1
+   "options": "Payroll Employee Detail"
   },
   {
    "fieldname": "section_break_13",
@@ -290,7 +289,7 @@
  "icon": "fa fa-cog",
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-23 13:00:33.753228",
+ "modified": "2020-12-17 15:13:17.766210",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Payroll Entry",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 8c2d974..b520cda 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -6,29 +6,43 @@
 import frappe, erpnext
 from frappe.model.document import Document
 from dateutil.relativedelta import relativedelta
-from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT, date_diff
+from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT, date_diff, comma_and
 from frappe import _
 from erpnext.accounts.utils import get_fiscal_year
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
+from frappe.desk.reportview import get_match_cond, get_filters_cond
 
 class PayrollEntry(Document):
 	def onload(self):
 		if not self.docstatus==1 or self.salary_slips_submitted:
-    			return
+				return
 
 		# check if salary slips were manually submitted
 		entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
 		if cint(entries) == len(self.employees):
-    			self.set_onload("submitted_ss", True)
+				self.set_onload("submitted_ss", True)
+
+	def validate(self):
+		self.number_of_employees = len(self.employees)
 
 	def on_submit(self):
 		self.create_salary_slips()
 
 	def before_submit(self):
+		self.validate_employee_details()
 		if self.validate_attendance:
 			if self.validate_employee_attendance():
 				frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
 
+	def validate_employee_details(self):
+		emp_with_sal_slip = []
+		for employee_details in self.employees:
+			if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
+				emp_with_sal_slip.append(employee_details.employee)
+
+		if len(emp_with_sal_slip):
+			frappe.throw(_("Salary Slip already exists for {0} ").format(comma_and(emp_with_sal_slip)))
+
 	def on_cancel(self):
 		frappe.delete_doc("Salary Slip", frappe.db.sql_list("""select name from `tabSalary Slip`
 			where payroll_entry=%s """, (self.name)))
@@ -71,8 +85,17 @@
 					and t2.docstatus = 1
 			%s order by t2.from_date desc
 			""" % cond, {"sal_struct": tuple(sal_struct), "from_date": self.end_date, "payroll_payable_account": self.payroll_payable_account}, as_dict=True)
+
+			emp_list = self.remove_payrolled_employees(emp_list)
 			return emp_list
 
+	def remove_payrolled_employees(self, emp_list):
+		for employee_details in emp_list:
+			if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": self.start_date, "end_date": self.end_date, "docstatus": 1}):
+				emp_list.remove(employee_details)
+
+		return emp_list
+
 	def fill_employee_details(self):
 		self.set('employees', [])
 		employees = self.get_emp_list()
@@ -94,7 +117,7 @@
 		for d in employees:
 			self.append('employees', d)
 
-		self.number_of_employees = len(employees)
+		self.number_of_employees = len(self.employees)
 		if self.validate_attendance:
 			return self.validate_employee_attendance()
 
@@ -126,8 +149,8 @@
 		"""
 		self.check_permission('write')
 		self.created = 1
-		emp_list = [d.employee for d in self.get_emp_list()]
-		if emp_list:
+		employees = [emp.employee for emp in self.employees]
+		if employees:
 			args = frappe._dict({
 				"salary_slip_based_on_timesheet": self.salary_slip_based_on_timesheet,
 				"payroll_frequency": self.payroll_frequency,
@@ -141,10 +164,10 @@
 				"exchange_rate": self.exchange_rate,
 				"currency": self.currency
 			})
-			if len(emp_list) > 30:
-				frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=emp_list, args=args)
+			if len(employees) > 30:
+				frappe.enqueue(create_salary_slips_for_employees, timeout=600, employees=employees, args=args)
 			else:
-				create_salary_slips_for_employees(emp_list, args, publish_progress=False)
+				create_salary_slips_for_employees(employees, args, publish_progress=False)
 				# since this method is called via frm.call this doc needs to be updated manually
 				self.reload()
 
@@ -152,13 +175,12 @@
 		"""
 			Returns list of salary slips based on selected criteria
 		"""
-		cond = self.get_filter_condition()
 
 		ss_list = frappe.db.sql("""
 			select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1
-			where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
-			and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s
-		""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
+			where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s and t1.payroll_entry = %s
+			and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s
+		""", (ss_status, self.start_date, self.end_date, self.name, self.salary_slip_based_on_timesheet), as_dict=as_dict)
 		return ss_list
 
 	def submit_salary_slips(self):
@@ -310,10 +332,9 @@
 	def make_payment_entry(self):
 		self.check_permission('write')
 
-		cond = self.get_filter_condition()
 		salary_slip_name_list = frappe.db.sql(""" select t1.name from `tabSalary Slip` t1
-			where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s
-			""" % ('%s', '%s', cond), (self.start_date, self.end_date), as_list = True)
+			where t1.docstatus = 1 and start_date >= %s and end_date <= %s and t1.payroll_entry = %s
+			""", (self.start_date, self.end_date, self.name), as_list = True)
 
 		if salary_slip_name_list and len(salary_slip_name_list) > 0:
 			salary_slip_total = 0
@@ -528,6 +549,7 @@
 def create_salary_slips_for_employees(employees, args, publish_progress=True):
 	salary_slips_exists_for = get_existing_salary_slips(employees, args)
 	count=0
+	salary_slips_not_created = []
 	for emp in employees:
 		if emp not in salary_slips_exists_for:
 			args.update({
@@ -540,26 +562,18 @@
 			if publish_progress:
 				frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
 					title = _("Creating Salary Slips..."))
-		else:
-			salary_slip_name = frappe.db.sql(
-				'''SELECT 
-						name
-					FROM `tabSalary Slip`
-					WHERE company=%s
-					AND start_date >= %s
-					AND end_date <= %s
-					AND employee = %s
-				''', (args.company, args.start_date, args.end_date, emp), as_dict=True)
 
-			salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name)
-			salary_slip_doc.exchange_rate = args.exchange_rate
-			salary_slip_doc.set_totals()
-			salary_slip_doc.db_update()
+		else:
+			salary_slips_not_created.append(emp)
 
 	payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
 	payroll_entry.db_set("salary_slips_created", 1)
 	payroll_entry.notify_update()
 
+	if salary_slips_not_created:
+		frappe.msgprint(_("Salary Slips already exists for employees {}, and will not be processed by this payroll.")
+			.format(frappe.bold(", ".join([emp for emp in salary_slips_not_created]))) , title=_("Message"), indicator="orange")
+
 def get_existing_salary_slips(employees, args):
 	return frappe.db.sql_list("""
 		select distinct employee from `tabSalary Slip`
@@ -619,3 +633,57 @@
 			'txt': "%%%s%%" % frappe.db.escape(txt),
 			'start': start, 'page_len': page_len
 		})
+
+def get_employee_with_existing_salary_slip(start_date, end_date):
+
+	return frappe.db.sql_list("""
+		select employee from `tabSalary Slip` 
+		where 
+			(start_date between %(start_date)s and %(end_date)s 
+		or 
+			end_date between %(start_date)s and %(end_date)s 
+		or 
+			%(start_date)s between start_date and end_date)
+		and docstatus = 1
+	""", {'start_date': start_date, 'end_date': end_date})
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def employee_query(doctype, txt, searchfield, start, page_len, filters):
+	filters = frappe._dict(filters)
+	conditions = []
+	emp_cond = ''
+	if filters.start_date and filters.end_date:
+		employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date)
+		filters.pop('start_date')
+		filters.pop('end_date')
+		if employee_list:
+			emp_cond += 'and employee not in %(employee_list)s'
+	else:
+		employee_list = []
+	
+
+	return frappe.db.sql("""select name, employee_name from `tabEmployee`
+		where status = 'Active'
+			and docstatus < 2
+			and ({key} like %(txt)s
+				or employee_name like %(txt)s)
+			{emp_cond}
+			{fcond} {mcond}
+		order by
+			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
+			if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
+			idx desc,
+			name, employee_name
+		limit %(start)s, %(page_len)s""".format(**{
+			'key': searchfield,
+			'fcond': get_filters_cond(doctype, filters, conditions),
+			'mcond': get_match_cond(doctype),
+			'emp_cond': emp_cond
+		}), {
+			'txt': "%%%s%%" % txt,
+			'_txt': txt.replace("%", ""),
+			'start': start,
+			'page_len': page_len,
+			'employee_list': employee_list
+		})
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index 54106c8..e098ec7 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -22,7 +22,7 @@
 				frappe.db.sql("delete from `tab%s`" % dt)
 
 		make_earning_salary_component(setup=True, company_list=["_Test Company"])
-		make_deduction_salary_component(setup=True, company_list=["_Test Company"])
+		make_deduction_salary_component(setup=True, test_tax=False, company_list=["_Test Company"])
 
 		frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 0)
 
@@ -107,9 +107,9 @@
 			frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") != "_Test Payroll Payable - _TC":
 				frappe.db.set_value("Company", "_Test Company", "default_payroll_payable_account",
 					"_Test Payroll Payable - _TC")
-
-		make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company", currency=frappe.db.get_value("Company", "_Test Company", "default_currency"))
-		make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company", currency=frappe.db.get_value("Company", "_Test Company", "default_currency"))
+		currency=frappe.db.get_value("Company", "_Test Company", "default_currency")
+		make_salary_structure("_Test Salary Structure 1", "Monthly", employee1, company="_Test Company", currency=currency, test_tax=False)
+		make_salary_structure("_Test Salary Structure 2", "Monthly", employee2, company="_Test Company", currency=currency, test_tax=False)
 
 		dates = get_start_end_dates('Monthly', nowdate())
 		if not frappe.db.get_value("Salary Slip", {"start_date": dates.start_date, "end_date": dates.end_date}):
diff --git a/erpnext/payroll/doctype/payroll_period/payroll_period.py b/erpnext/payroll/doctype/payroll_period/payroll_period.py
index d7893d0..46f6cd8 100644
--- a/erpnext/payroll/doctype/payroll_period/payroll_period.py
+++ b/erpnext/payroll/doctype/payroll_period/payroll_period.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt
+from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt, add_months
 from frappe.model.document import Document
 from erpnext.hr.utils import get_holidays_for_employee
 
@@ -88,6 +88,8 @@
 		period_start = joining_date
 	if relieving_date and getdate(relieving_date) < getdate(period_end):
 		period_end = relieving_date
+		if month_diff(period_end, start_date) > 1:
+			start_date = add_months(start_date, - (month_diff(period_end, start_date)+1))
 
 	total_sub_periods, remaining_sub_periods = 0.0, 0.0
 
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.js b/erpnext/payroll/doctype/retention_bonus/retention_bonus.js
index 6fe8cca..f8bb40a 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.js
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.js
@@ -4,9 +4,13 @@
 frappe.ui.form.on('Retention Bonus', {
 	setup: function(frm) {
 		frm.set_query("employee", function() {
+			if (!frm.doc.company) {
+				frappe.msgprint(__("Please Select Company First"));
+			}
 			return {
 				filters: {
-					"status": "Active"
+					"status": "Active",
+					"company": frm.doc.company
 				}
 			};
 		});
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 abe873d..7460c75 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.js
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js
@@ -116,7 +116,7 @@
 	},
 
 	exchange_rate: function(frm) {
-		calculate_totals(frm);
+		set_totals(frm);
 	},
 
 	hide_loan_section: function(frm) {
@@ -125,24 +125,24 @@
 
 	change_form_labels: function(frm, company_currency) {
 		frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction",
-			"base_net_pay", "base_rounded_total", "base_total_in_words"],
+			"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"],
 		company_currency);
 
-		frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words"],
+		frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words", "year_to_date", "month_to_date"],
 			frm.doc.currency);
 
 		// toggle fields
 		frm.toggle_display(["exchange_rate", "base_hour_rate", "base_gross_pay", "base_total_deduction",
-			"base_net_pay", "base_rounded_total", "base_total_in_words"],
+			"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date"],
 		frm.doc.currency != company_currency);
 	},
 
 	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) {
@@ -151,7 +151,6 @@
 		var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
 		frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields, false);
 		frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields, false);
-		calculate_totals(frm);
 		frm.trigger("set_dynamic_labels");
 	},
 
@@ -206,14 +205,14 @@
 
 frappe.ui.form.on('Salary Slip Timesheet', {
 	time_sheet: function(frm) {
-		calculate_totals(frm);
+		set_totals(frm);
 	},
 	timesheets_remove: function(frm) {
-		calculate_totals(frm);
+		set_totals(frm);
 	}
 });
 
-var calculate_totals = function(frm) {
+var set_totals = function(frm) {
 	if (frm.doc.docstatus === 0) {
 		if (frm.doc.earnings || frm.doc.deductions) {
 			frappe.call({
@@ -229,15 +228,15 @@
 
 frappe.ui.form.on('Salary Detail', {
 	amount: function(frm) {
-		calculate_totals(frm);
+		set_totals(frm);
 	},
 
 	earnings_remove: function(frm) {
-		calculate_totals(frm);
+		set_totals(frm);
 	},
 
 	deductions_remove: function(frm) {
-		calculate_totals(frm);
+		set_totals(frm);
 	},
 
 	salary_component: function(frm, cdt, cdn) {
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 386618c..9f9691b 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -69,9 +69,13 @@
   "net_pay_info",
   "net_pay",
   "base_net_pay",
+  "year_to_date",
+  "base_year_to_date",
   "column_break_53",
   "rounded_total",
   "base_rounded_total",
+  "month_to_date",
+  "base_month_to_date",
   "section_break_55",
   "total_in_words",
   "column_break_69",
@@ -578,13 +582,43 @@
   {
    "fieldname": "column_break_69",
    "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",
+   "options": "currency",
+   "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",
+   "options": "currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "base_year_to_date",
+   "fieldtype": "Currency",
+   "label": "Year To Date(Company Currency)",
+   "options": "Company:company:default_currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "base_month_to_date",
+   "fieldtype": "Currency",
+   "label": "Month To Date(Company Currency)",
+   "options": "Company:company:default_currency",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 9,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-21 23:02:59.400249",
+ "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 20365b1..2d3bc57 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -5,7 +5,7 @@
 import frappe, erpnext
 import datetime, math
 
-from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate
+from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day
 from frappe.model.naming import make_autoname
 
 from frappe import msgprint, _
@@ -18,6 +18,7 @@
 from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
 from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
 from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
+from erpnext.accounts.utils import get_fiscal_year
 
 class SalarySlip(TransactionBase):
 	def __init__(self, *args, **kwargs):
@@ -49,6 +50,9 @@
 			self.get_working_days_details(lwp = self.leave_without_pay)
 
 		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")
@@ -140,8 +144,8 @@
 				self.salary_slip_based_on_timesheet = self._salary_structure_doc.salary_slip_based_on_timesheet or 0
 				self.set_time_sheet()
 				self.pull_sal_struct()
-				payroll_based_on, consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"])
-				return [payroll_based_on, consider_unmarked_attendance_as]
+				ps = frappe.db.get_value("Payroll Settings", None, ["payroll_based_on","consider_unmarked_attendance_as"], as_dict=1)
+				return [ps.payroll_based_on, ps.consider_unmarked_attendance_as]
 
 	def set_time_sheet(self):
 		if self.salary_slip_based_on_timesheet:
@@ -421,16 +425,19 @@
 	def calculate_net_pay(self):
 		if self.salary_structure:
 			self.calculate_component_amounts("earnings")
-		self.gross_pay = self.get_component_totals("earnings")
+		self.gross_pay = self.get_component_totals("earnings", depends_on_payment_days=1)
 		self.base_gross_pay = flt(flt(self.gross_pay) * flt(self.exchange_rate), self.precision('base_gross_pay'))
 
 		if self.salary_structure:
 			self.calculate_component_amounts("deductions")
-		self.total_deduction = self.get_component_totals("deductions")
-		self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction'))
 
 		self.set_loan_repayment()
+		self.set_component_amounts_based_on_payment_days()
+		self.set_net_pay()
 
+	def set_net_pay(self):
+		self.total_deduction = self.get_component_totals("deductions")
+		self.base_total_deduction = flt(flt(self.total_deduction) * flt(self.exchange_rate), self.precision('base_total_deduction'))
 		self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
 		self.rounded_total = rounded(self.net_pay)
 		self.base_net_pay = flt(flt(self.net_pay) * flt(self.exchange_rate), self.precision('base_net_pay'))
@@ -452,8 +459,6 @@
 		else:
 			self.add_tax_components(payroll_period)
 
-		self.set_component_amounts_based_on_payment_days(component_type)
-
 	def add_structure_components(self, component_type):
 		data = self.get_data_for_eval()
 		for struct_row in self._salary_structure_doc.get(component_type):
@@ -573,7 +578,7 @@
 					'default_amount': amount if not struct_row.get("is_additional_component") else 0,
 					'depends_on_payment_days' : struct_row.depends_on_payment_days,
 					'salary_component' : struct_row.salary_component,
-					'abbr' : struct_row.abbr,
+					'abbr' : struct_row.abbr or struct_row.get("salary_component_abbr"),
 					'additional_salary': additional_salary,
 					'do_not_include_in_total' : struct_row.do_not_include_in_total,
 					'is_tax_applicable': struct_row.is_tax_applicable,
@@ -810,7 +815,7 @@
 			cint(row.depends_on_payment_days) and cint(self.total_working_days) and
 			(not self.salary_slip_based_on_timesheet or
 				getdate(self.start_date) < joining_date or
-				getdate(self.end_date) > relieving_date
+				(relieving_date and getdate(self.end_date) > relieving_date)
 			)):
 			additional_amount = flt((flt(row.additional_amount) * flt(self.payment_days)
 				/ cint(self.total_working_days)), row.precision("additional_amount"))
@@ -943,15 +948,21 @@
 		struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary
 		return struct_row
 
-	def get_component_totals(self, component_type):
+	def get_component_totals(self, component_type, depends_on_payment_days=0):
+		joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
+			["date_of_joining", "relieving_date"])
+
 		total = 0.0
 		for d in self.get(component_type):
 			if not d.do_not_include_in_total:
-				d.amount = flt(d.amount, d.precision("amount"))
-				total += d.amount
+				if depends_on_payment_days:
+					amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
+				else:
+					amount = flt(d.amount, d.precision("amount"))
+				total += amount
 		return total
 
-	def set_component_amounts_based_on_payment_days(self, component_type):
+	def set_component_amounts_based_on_payment_days(self):
 		joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
 			["date_of_joining", "relieving_date"])
 
@@ -961,8 +972,9 @@
 		if not joining_date:
 			frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
 
-		for d in self.get(component_type):
-			d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
+		for component_type in ("earnings", "deductions"):
+			for d in self.get(component_type):
+				d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount"))
 
 	def set_loan_repayment(self):
 		self.total_loan_repayment = 0
@@ -1086,17 +1098,17 @@
 		self.calculate_net_pay()
 
 	def set_totals(self):
-		self.gross_pay = 0
+		self.gross_pay = 0.0
 		if self.salary_slip_based_on_timesheet == 1:
 			self.calculate_total_for_salary_slip_based_on_timesheet()
 		else:
-			self.total_deduction = 0
+			self.total_deduction = 0.0
 			if self.earnings:
 				for earning in self.earnings:
-					self.gross_pay += flt(earning.amount)
+					self.gross_pay += flt(earning.amount, earning.precision("amount"))
 			if self.deductions:
 				for deduction in self.deductions:
-					self.total_deduction += flt(deduction.amount)
+					self.total_deduction += flt(deduction.amount, deduction.precision("amount"))
 			self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) - flt(self.total_loan_repayment)
 		self.set_base_totals()
 
@@ -1125,6 +1137,82 @@
 				self.gross_pay += self.earnings[i].amount
 		self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
 
+	def compute_year_to_date(self):
+		year_to_date = 0
+		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'],
+			filters = {'employee_name' : self.employee_name,
+				'start_date' : ['>=', period_start_date],
+				'end_date' : ['<', period_end_date],
+				'name': ['!=', self.name],
+				'docstatus': 1
+			})
+
+		year_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
+
+		year_to_date += self.net_pay
+		self.year_to_date = year_to_date
+
+	def compute_month_to_date(self):
+		month_to_date = 0
+		first_day_of_the_month = get_first_day(self.start_date)
+		salary_slip_sum = frappe.get_list('Salary Slip',
+			fields = ['sum(net_pay) as sum'],
+			filters = {'employee_name' : self.employee_name,
+				'start_date' : ['>=', first_day_of_the_month],
+				'end_date' : ['<', self.start_date],
+				'name': ['!=', self.name],
+				'docstatus': 1
+			})
+
+		month_to_date = flt(salary_slip_sum[0].sum) if salary_slip_sum else 0.0
+
+		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))
@@ -1135,4 +1223,4 @@
 
 def generate_password_for_pdf(policy_template, employee):
 	employee = frappe.get_doc("Employee", employee)
-	return policy_template.format(**employee.as_dict())
+	return policy_template.format(**employee.as_dict())
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 5daf1d4..f58a8e5 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -9,7 +9,7 @@
 import random
 from erpnext.accounts.utils import get_fiscal_year
 from frappe.utils.make_random import get_random
-from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day
+from frappe.utils import getdate, nowdate, add_days, add_months, flt, get_first_day, get_last_day, cstr
 from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
 from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_month_details
 from erpnext.hr.doctype.employee.test_employee import make_employee
@@ -240,7 +240,11 @@
 			interest_income_account='Interest Income Account - _TC',
 			penalty_income_account='Penalty Income Account - _TC')
 
-		make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR')
+		payroll_period = create_payroll_period(name="_Test Payroll Period 1", company="_Test Company")
+
+		make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR',
+			payroll_period=payroll_period)
+
 		frappe.db.sql("""delete from `tabLoan""")
 		loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
 		loan.repay_from_salary = 1
@@ -290,6 +294,65 @@
 		self.assertEqual(salary_slip.gross_pay, 78000)
 		self.assertEqual(salary_slip.base_gross_pay, 78000*70)
 
+	def test_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)
+
+		salary_slips = frappe.get_all('Salary Slip', fields=['year_to_date', 'net_pay'], filters={'employee_name':
+			'test_ytd@salary.com'}, order_by = 'posting_date')
+
+		year_to_date = 0
+		for slip in salary_slips:
+			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
@@ -410,10 +473,7 @@
 		salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip"
 
 	employee = frappe.db.get_value("Employee", {"user_id": user})
-	if not frappe.db.exists('Salary Structure', salary_structure):
-		salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee)
-	else:
-		salary_structure_doc = frappe.get_doc('Salary Structure', salary_structure)
+	salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee)
 	salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})})
 
 	if not salary_slip_name:
@@ -557,14 +617,6 @@
 			"amount": 200,
 			"exempted_from_income_tax": 1
 
-		},
-		{
-			"salary_component": 'TDS',
-			"abbr":'T',
-			"type": "Deduction",
-			"depends_on_payment_days": 0,
-			"variable_based_on_taxable_salary": 1,
-			"round_to_the_nearest_integer": 1
 		}
 	]
 	if not test_tax:
@@ -575,6 +627,15 @@
 			"type": "Deduction",
 			"round_to_the_nearest_integer": 1
 		})
+	else:
+		data.append({
+			"salary_component": 'TDS',
+			"abbr":'T',
+			"type": "Deduction",
+			"depends_on_payment_days": 0,
+			"variable_based_on_taxable_salary": 1,
+			"round_to_the_nearest_integer": 1
+		})
 	if setup or test_tax:
 		make_salary_component(data, test_tax, company_list)
 
@@ -631,8 +692,13 @@
 	}).submit()
 	return claim_date
 
-def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=erpnext.get_default_currency()):
-	frappe.db.sql("""delete from `tabIncome Tax Slab`""")
+def create_tax_slab(payroll_period, effective_date = None, allow_tax_exemption = False, dont_submit = False, currency=None,
+	company=None):
+	if not currency:
+		currency = erpnext.get_default_currency()
+
+	if company:
+		currency = erpnext.get_company_currency(company)
 
 	slabs = [
 		{
@@ -652,31 +718,38 @@
 		}
 	]
 
-	income_tax_slab = frappe.new_doc("Income Tax Slab")
-	income_tax_slab.name = "Tax Slab: " + payroll_period.name
-	income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
-	income_tax_slab.currency = currency
+	income_tax_slab_name = frappe.db.get_value("Income Tax Slab", {"currency": currency})
+	if not income_tax_slab_name:
+		income_tax_slab = frappe.new_doc("Income Tax Slab")
+		income_tax_slab.name = "Tax Slab: " + payroll_period.name + " " + cstr(currency)
+		income_tax_slab.effective_from = effective_date or add_days(payroll_period.start_date, -2)
+		income_tax_slab.company = company or ''
+		income_tax_slab.currency = currency
 
-	if allow_tax_exemption:
-		income_tax_slab.allow_tax_exemption = 1
-		income_tax_slab.standard_tax_exemption_amount = 50000
+		if allow_tax_exemption:
+			income_tax_slab.allow_tax_exemption = 1
+			income_tax_slab.standard_tax_exemption_amount = 50000
 
-	for item in slabs:
-		income_tax_slab.append("slabs", item)
+		for item in slabs:
+			income_tax_slab.append("slabs", item)
 
-	income_tax_slab.append("other_taxes_and_charges", {
-		"description": "cess",
-		"percent": 4
-	})
+		income_tax_slab.append("other_taxes_and_charges", {
+			"description": "cess",
+			"percent": 4
+		})
 
-	income_tax_slab.save()
-	if not dont_submit:
-		income_tax_slab.submit()
+		income_tax_slab.save()
+		if not dont_submit:
+			income_tax_slab.submit()
 
-def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True):
+		return income_tax_slab.name
+	else:
+		return income_tax_slab_name
+
+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 7daae49..1378bf0 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -55,26 +55,26 @@
 	},
 
 	set_earning_deduction_component: function(frm) {
-		if(!frm.doc.currency && !frm.doc.company) return;
+		if(!frm.doc.company) return;
 		frm.set_query("salary_component", "earnings", function() {
 			return {
-				query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
-				filters: {type: "earning", currency: frm.doc.currency, company: frm.doc.company}
+				filters: {type: "earning", company: frm.doc.company}
 			};
 		});
 		frm.set_query("salary_component", "deductions", function() {
 			return {
-				query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
-				filters: {type: "deduction", currency: frm.doc.currency, company: frm.doc.company}
+				filters: {type: "deduction", company: frm.doc.company}
 			};
 		});
 	},
 
+	company: function(frm) {
+		frm.trigger('set_earning_deduction_component');
+	},
 
 	currency: function(frm) {
 		calculate_totals(frm.doc);
 		frm.trigger("set_dynamic_labels")
-		frm.trigger('set_earning_deduction_component');
 		frm.refresh()
 	},
 
@@ -118,6 +118,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 877e41d..1712081 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py
@@ -207,17 +207,3 @@
 
 	return list(set([d.employee for d in employees]))
 
-@frappe.whitelist()
-@frappe.validate_and_sanitize_search_inputs
-def get_earning_deduction_components(doctype, txt, searchfield, start, page_len, filters):
-	if len(filters) < 3:
-		return {}
-
-	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
-		order by salary_component
-	""", (filters['type'], filters['company']) )
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index abb6697..f2fb558 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -114,7 +114,7 @@
 		self.assertEqual(sal_struct.currency, 'USD')
 
 def make_salary_structure(salary_structure, payroll_frequency, employee=None, dont_submit=False, other_details=None,
-	test_tax=False, company=None, currency=erpnext.get_default_currency()):
+	test_tax=False, company=None, currency=erpnext.get_default_currency(), payroll_period=None):
 	if test_tax:
 		frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure))
 
@@ -141,16 +141,24 @@
 
 	if employee and not frappe.db.get_value("Salary Structure Assignment",
 		{'employee':employee, 'docstatus': 1}) and salary_structure_doc.docstatus==1:
-			create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency)
+			create_salary_structure_assignment(employee, salary_structure, company=company, currency=currency,
+			payroll_period=payroll_period)
 
 	return salary_structure_doc
 
-def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency()):
+def create_salary_structure_assignment(employee, salary_structure, from_date=None, company=None, currency=erpnext.get_default_currency(),
+	payroll_period=None):
+
 	if frappe.db.exists("Salary Structure Assignment", {"employee": employee}):
 		frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""",(employee))
 
-	payroll_period = create_payroll_period()
-	create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency)
+	if not payroll_period:
+		payroll_period = create_payroll_period()
+
+	income_tax_slab = frappe.db.get_value("Income Tax Slab", {"currency": currency})
+
+	if not income_tax_slab:
+		income_tax_slab = create_tax_slab(payroll_period, allow_tax_exemption=True, currency=currency)
 
 	salary_structure_assignment = frappe.new_doc("Salary Structure Assignment")
 	salary_structure_assignment.employee = employee
@@ -162,7 +170,7 @@
 	salary_structure_assignment.payroll_payable_account = get_payable_account(company)
 	salary_structure_assignment.company = company or erpnext.get_default_company()
 	salary_structure_assignment.save(ignore_permissions=True)
-	salary_structure_assignment.income_tax_slab = "Tax Slab: _Test Payroll Period"
+	salary_structure_assignment.income_tax_slab = income_tax_slab
 	salary_structure_assignment.submit()
 	return salary_structure_assignment
 
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
index dccb5df..a0c3013 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.py
@@ -43,7 +43,7 @@
 
 	def set_payroll_payable_account(self):
 		if not self.payroll_payable_account:
-			payroll_payable_account = frappe.db.get_value('Company', self.company, 'default_payable_account')
+			payroll_payable_account = frappe.db.get_value('Company', self.company, 'default_payroll_payable_account')
 			if not payroll_payable_account:
 				payroll_payable_account = frappe.db.get_value(
 					"Account", {
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/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index f3cecd9..3cdfcb2 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -2,12 +2,13 @@
  "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
- "autoname": "field:project_name",
+ "autoname": "naming_series:",
  "creation": "2013-03-07 11:55:07",
  "doctype": "DocType",
  "document_type": "Setup",
  "engine": "InnoDB",
  "field_order": [
+  "naming_series",
   "project_name",
   "status",
   "project_type",
@@ -440,13 +441,24 @@
    "fieldtype": "Text",
    "label": "Message",
    "mandatory_depends_on": "collect_progress"
+  },
+  {
+   "fieldname": "naming_series",
+   "fieldtype": "Select",
+   "label": "Series",
+   "no_copy": 1,
+   "options": "PROJ-.####",
+   "print_hide": 1,
+   "reqd": 1,
+   "set_only_once": 1
   }
  ],
  "icon": "fa fa-puzzle-piece",
  "idx": 29,
+ "index_web_pages_for_search": 1,
  "links": [],
  "max_attachments": 4,
- "modified": "2020-04-08 22:11:14.552615",
+ "modified": "2020-09-02 11:54:01.223620",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Project",
@@ -488,5 +500,6 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "timeline_field": "customer",
+ "title_field": "project_name",
  "track_seen": 1
 }
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 5bbd29c..60f85b0 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -13,6 +13,7 @@
 from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_users_email
 from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
 from frappe.model.document import Document
+from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list
 
 class Project(Document):
 	def get_feed(self):
@@ -54,17 +55,64 @@
 				self.project_type = template.project_type
 
 			# create tasks from template
+			project_tasks = []
+			tmp_task_details = []
 			for task in template.tasks:
-				frappe.get_doc(dict(
-					doctype = 'Task',
-					subject = task.subject,
-					project = self.name,
-					status = 'Open',
-					exp_start_date = add_days(self.expected_start_date, task.start),
-					exp_end_date = add_days(self.expected_start_date, task.start + task.duration),
-					description = task.description,
-					task_weight = task.task_weight
-				)).insert()
+				template_task_details = frappe.get_doc("Task", task.task)
+				tmp_task_details.append(template_task_details)
+				task = self.create_task_from_template(template_task_details)
+				project_tasks.append(task)
+			self.dependency_mapping(tmp_task_details, project_tasks)
+
+	def create_task_from_template(self, task_details):
+		return frappe.get_doc(dict(
+				doctype = 'Task',
+				subject = task_details.subject,
+				project = self.name,
+				status = 'Open',
+				exp_start_date = self.calculate_start_date(task_details),
+				exp_end_date = self.calculate_end_date(task_details),
+				description = task_details.description,
+				task_weight = task_details.task_weight,
+				type = task_details.type,
+				issue = task_details.issue,
+				is_group = task_details.is_group
+			)).insert()
+
+	def calculate_start_date(self, task_details):
+		self.start_date = add_days(self.expected_start_date, task_details.start)
+		self.start_date = update_if_holiday(self.holiday_list, self.start_date)
+		return self.start_date
+
+	def calculate_end_date(self, task_details):
+		self.end_date = add_days(self.start_date, task_details.duration)
+		return update_if_holiday(self.holiday_list, self.end_date)
+
+	def dependency_mapping(self, template_tasks, project_tasks):
+		for template_task in template_tasks:
+			project_task = list(filter(lambda x: x.subject == template_task.subject, project_tasks))[0]
+			project_task = frappe.get_doc("Task", project_task.name)
+			self.check_depends_on_value(template_task, project_task, project_tasks)
+			self.check_for_parent_tasks(template_task, project_task, project_tasks)
+
+	def check_depends_on_value(self, template_task, project_task, project_tasks):
+		if template_task.get("depends_on") and not project_task.get("depends_on"):
+			for child_task in template_task.get("depends_on"):
+				child_task_subject = frappe.db.get_value("Task", child_task.task, "subject")
+				corresponding_project_task = list(filter(lambda x: x.subject == child_task_subject, project_tasks))
+				if len(corresponding_project_task):
+					project_task.append("depends_on",{
+						"task": corresponding_project_task[0].name
+					})
+					project_task.save()
+
+	def check_for_parent_tasks(self, template_task, project_task, project_tasks):
+		if template_task.get("parent_task") and not project_task.get("parent_task"):
+			parent_task_subject = frappe.db.get_value("Task", template_task.get("parent_task"), "subject")
+			corresponding_project_task = list(filter(lambda x: x.subject == parent_task_subject, project_tasks))
+			if len(corresponding_project_task):
+				project_task.parent_task = corresponding_project_task[0].name
+				project_task.save()
 
 	def is_row_updated(self, row, existing_task_data, fields):
 		if self.get("__islocal") or not existing_task_data: return True
@@ -493,3 +541,9 @@
 
 	project.status = status
 	project.save()
+
+def update_if_holiday(holiday_list, date):
+	holiday_list = holiday_list or get_holiday_list()
+	while is_holiday(holiday_list, date):
+		date = add_days(date, 1)
+	return date
diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py
index 0c4f6f1..d85c826 100644
--- a/erpnext/projects/doctype/project/test_project.py
+++ b/erpnext/projects/doctype/project/test_project.py
@@ -7,60 +7,131 @@
 test_records = frappe.get_test_records('Project')
 test_ignore = ["Sales Order"]
 
-from erpnext.projects.doctype.project_template.test_project_template import get_project_template, make_project_template
-from erpnext.projects.doctype.project.project import set_project_status
-
-from frappe.utils import getdate
+from erpnext.projects.doctype.project_template.test_project_template import make_project_template
+from erpnext.projects.doctype.project.project import update_if_holiday
+from erpnext.projects.doctype.task.test_task import create_task
+from frappe.utils import getdate, nowdate, add_days
 
 class TestProject(unittest.TestCase):
-	def test_project_with_template(self):
-		frappe.db.sql('delete from tabTask where project = "Test Project with Template"')
-		frappe.delete_doc('Project', 'Test Project with Template')
+	def test_project_with_template_having_no_parent_and_depend_tasks(self):
+		project_name = "Test Project with Template - No Parent and Dependend Tasks"
+		frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
+		frappe.delete_doc('Project', project_name)
 
-		project = get_project('Test Project with Template')
+		task1 = task_exists("Test Template Task with No Parent and Dependency")
+		if not task1:
+			task1 = create_task(subject="Test Template Task with No Parent and Dependency", is_template=1, begin=5, duration=3)
 
-		tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc')
+		template = make_project_template("Test Project Template - No Parent and Dependend Tasks", [task1])
+		project = get_project(project_name, template)
+		tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks'], dict(project=project.name), order_by='creation asc')
 
-		task1 = tasks[0]
-		self.assertEqual(task1.subject, 'Task 1')
-		self.assertEqual(task1.description, 'Task 1 description')
-		self.assertEqual(getdate(task1.exp_start_date), getdate('2019-01-01'))
-		self.assertEqual(getdate(task1.exp_end_date), getdate('2019-01-04'))
+		self.assertEqual(tasks[0].subject, 'Test Template Task with No Parent and Dependency')
+		self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 5, 3))
+		self.assertEqual(len(tasks), 1)
 
-		self.assertEqual(len(tasks), 4)
-		task4 = tasks[3]
-		self.assertEqual(task4.subject, 'Task 4')
-		self.assertEqual(getdate(task4.exp_end_date), getdate('2019-01-06'))
+	def test_project_template_having_parent_child_tasks(self):
+		project_name = "Test Project with Template - Tasks with Parent-Child Relation"
+		frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
+		frappe.delete_doc('Project', project_name)
 
-def get_project(name):
-	template = get_project_template()
+		task1 = task_exists("Test Template Task Parent")
+		if not task1:
+			task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=1)
+
+		task2 = task_exists("Test Template Task Child 1")
+		if not task2:
+			task2 = create_task(subject="Test Template Task Child 1", parent_task=task1.name, is_template=1, begin=1, duration=3)
+
+		task3 = task_exists("Test Template Task Child 2")
+		if not task3:
+			task3 = create_task(subject="Test Template Task Child 2", parent_task=task1.name, is_template=1, begin=2, duration=3)
+
+		template = make_project_template("Test Project Template  - Tasks with Parent-Child Relation", [task1, task2, task3])
+		project = get_project(project_name, template)
+		tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc')
+
+		self.assertEqual(tasks[0].subject, 'Test Template Task Parent')
+		self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 1))
+
+		self.assertEqual(tasks[1].subject, 'Test Template Task Child 1')
+		self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3))
+		self.assertEqual(tasks[1].parent_task, tasks[0].name)
+
+		self.assertEqual(tasks[2].subject, 'Test Template Task Child 2')
+		self.assertEqual(getdate(tasks[2].exp_end_date), calculate_end_date(project, 2, 3))
+		self.assertEqual(tasks[2].parent_task, tasks[0].name)
+
+		self.assertEqual(len(tasks), 3)
+
+	def test_project_template_having_dependent_tasks(self):
+		project_name = "Test Project with Template - Dependent Tasks"
+		frappe.db.sql(""" delete from tabTask where project = %s  """, project_name)
+		frappe.delete_doc('Project', project_name)
+
+		task1 = task_exists("Test Template Task for Dependency")
+		if not task1:
+			task1 = create_task(subject="Test Template Task for Dependency", is_template=1, begin=3, duration=1)
+
+		task2 = task_exists("Test Template Task with Dependency")
+		if not task2:
+			task2 = create_task(subject="Test Template Task with Dependency", depends_on=task1.name, is_template=1, begin=2, duration=2)
+
+		template = make_project_template("Test Project with Template - Dependent Tasks", [task1, task2])
+		project = get_project(project_name, template)
+		tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name'], dict(project=project.name), order_by='creation asc')
+
+		self.assertEqual(tasks[1].subject, 'Test Template Task with Dependency')
+		self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 2, 2))
+		self.assertTrue(tasks[1].depends_on_tasks.find(tasks[0].name) >= 0 )
+
+		self.assertEqual(tasks[0].subject, 'Test Template Task for Dependency')
+		self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 3, 1) )
+
+		self.assertEqual(len(tasks), 2)
+
+def get_project(name, template):
 
 	project = frappe.get_doc(dict(
 		doctype = 'Project',
 		project_name = name,
 		status = 'Open',
 		project_template = template.name,
-		expected_start_date = '2019-01-01'
+		expected_start_date = nowdate()
 	)).insert()
 
 	return project
 
 def make_project(args):
 	args = frappe._dict(args)
-	if args.project_template_name:
-		template = make_project_template(args.project_template_name)
-	else:
-		template = get_project_template()
+
+	if args.project_name and frappe.db.exists("Project", {"project_name": args.project_name}):
+		return frappe.get_doc("Project", {"project_name": args.project_name})
 
 	project = frappe.get_doc(dict(
 		doctype = 'Project',
 		project_name = args.project_name,
 		status = 'Open',
-		project_template = template.name,
 		expected_start_date = args.start_date
 	))
 
-	if not frappe.db.exists("Project", args.project_name):
-		project.insert()
+	if args.project_template_name:
+		template = make_project_template(args.project_template_name)
+		project.project_template = template.name
 
-	return project
\ No newline at end of file
+	project.insert()
+
+	return project
+
+def task_exists(subject):
+	result = frappe.db.get_list("Task", filters={"subject": subject},fields=["name"])
+	if not len(result):
+		return False
+	return frappe.get_doc("Task", result[0].name)
+
+def calculate_end_date(project, start, duration):
+	start = add_days(project.expected_start_date, start)
+	start = update_if_holiday(project.holiday_list, start)
+	end = add_days(start, duration)
+	end = update_if_holiday(project.holiday_list, end)
+	return getdate(end)
\ No newline at end of file
diff --git a/erpnext/projects/doctype/project_template/project_template.js b/erpnext/projects/doctype/project_template/project_template.js
index d7a876d..3d3c15c 100644
--- a/erpnext/projects/doctype/project_template/project_template.js
+++ b/erpnext/projects/doctype/project_template/project_template.js
@@ -5,4 +5,23 @@
 	// refresh: function(frm) {
 
 	// }
+	setup: function (frm) {
+		frm.set_query("task", "tasks", function () {
+			return {
+				filters: {
+					"is_template": 1
+				}
+			};
+		});
+	}
+});
+
+frappe.ui.form.on('Project Template Task', {
+	task: function (frm, cdt, cdn) {
+		var row = locals[cdt][cdn];
+		frappe.db.get_value("Task", row.task, "subject", (value) => {
+			row.subject = value.subject;
+			refresh_field("tasks");
+		});
+	}
 });
diff --git a/erpnext/projects/doctype/project_template/project_template.py b/erpnext/projects/doctype/project_template/project_template.py
index ac78135..aace402 100644
--- a/erpnext/projects/doctype/project_template/project_template.py
+++ b/erpnext/projects/doctype/project_template/project_template.py
@@ -3,8 +3,28 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-# import frappe
+import frappe
 from frappe.model.document import Document
+from frappe import _
+from frappe.utils import get_link_to_form
 
 class ProjectTemplate(Document):
-	pass
+
+	def validate(self):
+		self.validate_dependencies()
+
+	def validate_dependencies(self):
+		for task in self.tasks:
+			task_details = frappe.get_doc("Task", task.task)
+			if task_details.depends_on:
+				for dependency_task in task_details.depends_on:
+					if not self.check_dependent_task_presence(dependency_task.task):
+						task_details_format = get_link_to_form("Task",task_details.name)
+						dependency_task_format = get_link_to_form("Task", dependency_task.task)
+						frappe.throw(_("Task {0} depends on Task {1}. Please add Task {1} to the Tasks list.").format(frappe.bold(task_details_format), frappe.bold(dependency_task_format)))
+	
+	def check_dependent_task_presence(self, task):
+		for task_details in self.tasks:
+			if task_details.task == task:
+				return True
+		return False
diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py
index 2c5831a..95663cd 100644
--- a/erpnext/projects/doctype/project_template/test_project_template.py
+++ b/erpnext/projects/doctype/project_template/test_project_template.py
@@ -5,44 +5,25 @@
 
 import frappe
 import unittest
+from erpnext.projects.doctype.task.test_task import create_task
 
 class TestProjectTemplate(unittest.TestCase):
 	pass
 
-def get_project_template():
-	if not frappe.db.exists('Project Template', 'Test Project Template'):
-		frappe.get_doc(dict(
-			doctype = 'Project Template',
-			name = 'Test Project Template',
-			tasks = [
-				dict(subject='Task 1', description='Task 1 description',
-					start=0, duration=3),
-				dict(subject='Task 2', description='Task 2 description',
-					start=0, duration=2),
-				dict(subject='Task 3', description='Task 3 description',
-					start=2, duration=4),
-				dict(subject='Task 4', description='Task 4 description',
-					start=3, duration=2),
-			]
-		)).insert()
-
-	return frappe.get_doc('Project Template', 'Test Project Template')
-
 def make_project_template(project_template_name, project_tasks=[]):
 	if not frappe.db.exists('Project Template', project_template_name):
-		frappe.get_doc(dict(
-			doctype = 'Project Template',
-			name = project_template_name,
-			tasks = project_tasks or [
-				dict(subject='Task 1', description='Task 1 description',
-					start=0, duration=3),
-				dict(subject='Task 2', description='Task 2 description',
-					start=0, duration=2),
-				dict(subject='Task 3', description='Task 3 description',
-					start=2, duration=4),
-				dict(subject='Task 4', description='Task 4 description',
-					start=3, duration=2),
+		project_tasks = project_tasks or [
+				create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3),
+				create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2),
 			]
-		)).insert()
+		doc = frappe.get_doc(dict(
+			doctype = 'Project Template',
+			name = project_template_name
+		))
+		for task in project_tasks:
+			doc.append("tasks",{
+				"task": task.name
+			})
+		doc.insert()
 
 	return frappe.get_doc('Project Template', project_template_name)
\ No newline at end of file
diff --git a/erpnext/projects/doctype/project_template_task/project_template_task.json b/erpnext/projects/doctype/project_template_task/project_template_task.json
index 8644d89..69530b1 100644
--- a/erpnext/projects/doctype/project_template_task/project_template_task.json
+++ b/erpnext/projects/doctype/project_template_task/project_template_task.json
@@ -1,203 +1,41 @@
 {
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
  "creation": "2019-02-18 17:24:41.830096",
- "custom": 0,
- "docstatus": 0,
  "doctype": "DocType",
- "document_type": "",
  "editable_grid": 1,
  "engine": "InnoDB",
+ "field_order": [
+  "task",
+  "subject"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
+   "columns": 2,
+   "fieldname": "task",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Task",
+   "options": "Task",
+   "reqd": 1
+  },
+  {
+   "columns": 6,
    "fieldname": "subject",
-   "fieldtype": "Data",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
+   "fieldtype": "Read Only",
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Subject",
-   "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": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "start",
-   "fieldtype": "Int",
-   "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": "Begin On (Days)",
-   "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": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "duration",
-   "fieldtype": "Int",
-   "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": "Duration (Days)",
-   "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": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "task_weight",
-   "fieldtype": "Float",
-   "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": "Task Weight",
-   "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
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "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,
-   "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
+   "label": "Subject"
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
  "istable": 1,
- "max_attachments": 0,
- "modified": "2019-02-18 18:30:22.688966",
+ "links": [],
+ "modified": "2021-01-07 15:13:40.995071",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Project Template Task",
- "name_case": "",
  "owner": "Administrator",
  "permissions": [],
  "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
  "sort_field": "modified",
  "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index 27f1a71..160cc58 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -12,6 +12,7 @@
   "issue",
   "type",
   "is_group",
+  "is_template",
   "column_break0",
   "status",
   "priority",
@@ -22,9 +23,11 @@
   "sb_timeline",
   "exp_start_date",
   "expected_time",
+  "start",
   "column_break_11",
   "exp_end_date",
   "progress",
+  "duration",
   "is_milestone",
   "sb_details",
   "description",
@@ -112,7 +115,7 @@
    "no_copy": 1,
    "oldfieldname": "status",
    "oldfieldtype": "Select",
-   "options": "Open\nWorking\nPending Review\nOverdue\nCompleted\nCancelled"
+   "options": "Open\nWorking\nPending Review\nOverdue\nTemplate\nCompleted\nCancelled"
   },
   {
    "fieldname": "priority",
@@ -360,6 +363,24 @@
    "label": "Completed By",
    "no_copy": 1,
    "options": "User"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_template",
+   "fieldtype": "Check",
+   "label": "Is Template"
+  },
+  {
+   "depends_on": "is_template",
+   "fieldname": "start",
+   "fieldtype": "Int",
+   "label": "Begin On (Days)"
+  },
+  {
+   "depends_on": "is_template",
+   "fieldname": "duration",
+   "fieldtype": "Int",
+   "label": "Duration (Days)"
   }
  ],
  "icon": "fa fa-check",
@@ -367,7 +388,7 @@
  "is_tree": 1,
  "links": [],
  "max_attachments": 5,
- "modified": "2020-07-03 12:36:04.960457",
+ "modified": "2020-12-28 11:32:58.714991",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Task",
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index fb84094..a996033 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -17,291 +17,305 @@
 class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass
 
 class Task(NestedSet):
-	nsm_parent_field = 'parent_task'
+    nsm_parent_field = 'parent_task'
 
-	def get_feed(self):
-		return '{0}: {1}'.format(_(self.status), self.subject)
+    def get_feed(self):
+        return '{0}: {1}'.format(_(self.status), self.subject)
 
-	def get_customer_details(self):
-		cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer)
-		if cust:
-			ret = {'customer_name': cust and cust[0][0] or ''}
-			return ret
+    def get_customer_details(self):
+        cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer)
+        if cust:
+            ret = {'customer_name': cust and cust[0][0] or ''}
+            return ret
 
-	def validate(self):
-		self.validate_dates()
-		self.validate_parent_project_dates()
-		self.validate_progress()
-		self.validate_status()
-		self.update_depends_on()
+    def validate(self):
+        self.validate_dates()
+        self.validate_parent_project_dates()
+        self.validate_progress()
+        self.validate_status()
+        self.update_depends_on()
+        self.validate_dependencies_for_template_task()
 
-	def validate_dates(self):
-		if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
-			frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \
-				frappe.bold("Expected End Date")))
+    def validate_dates(self):
+        if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
+            frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \
+                frappe.bold("Expected End Date")))
 
-		if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date):
-			frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \
-				frappe.bold("Actual End Date")))
+        if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date):
+            frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \
+                frappe.bold("Actual End Date")))
 
-	def validate_parent_project_dates(self):
-		if not self.project or frappe.flags.in_test:
-			return
+    def validate_parent_project_dates(self):
+        if not self.project or frappe.flags.in_test:
+            return
 
-		expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date")
+        expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date")
 
-		if expected_end_date:
-			validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected")
-			validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual")
+        if expected_end_date:
+            validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected")
+            validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual")
 
-	def validate_status(self):
-		if self.status!=self.get_db_value("status") and self.status == "Completed":
-			for d in self.depends_on:
-				if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):
-					frappe.throw(_("Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.").format(frappe.bold(self.name), frappe.bold(d.task)))
+    def validate_status(self):
+        if self.is_template and self.status != "Template":
+            self.status = "Template"
+        if self.status!=self.get_db_value("status") and self.status == "Completed":
+            for d in self.depends_on:
+                if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):
+                    frappe.throw(_("Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.").format(frappe.bold(self.name), frappe.bold(d.task)))
 
-			close_all_assignments(self.doctype, self.name)
+            close_all_assignments(self.doctype, self.name)
 
-	def validate_progress(self):
-		if flt(self.progress or 0) > 100:
-			frappe.throw(_("Progress % for a task cannot be more than 100."))
+    def validate_progress(self):
+        if flt(self.progress or 0) > 100:
+            frappe.throw(_("Progress % for a task cannot be more than 100."))
 
-		if flt(self.progress) == 100:
-			self.status = 'Completed'
+        if flt(self.progress) == 100:
+            self.status = 'Completed'
 
-		if self.status == 'Completed':
-			self.progress = 100
+        if self.status == 'Completed':
+            self.progress = 100
 
-	def update_depends_on(self):
-		depends_on_tasks = self.depends_on_tasks or ""
-		for d in self.depends_on:
-			if d.task and not d.task in depends_on_tasks:
-				depends_on_tasks += d.task + ","
-		self.depends_on_tasks = depends_on_tasks
+    def validate_dependencies_for_template_task(self):
+        if self.is_template:
+            self.validate_depends_on_tasks()
+                
+    def validate_depends_on_tasks(self):
+        if self.depends_on:
+            for task in self.depends_on:
+                if not frappe.db.get_value("Task", task.task, "is_template"):
+                    dependent_task_format = """<a href="#Form/Task/{0}">{0}</a>""".format(task.task)
+                    frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format))
 
-	def update_nsm_model(self):
-		frappe.utils.nestedset.update_nsm(self)
+    def update_depends_on(self):
+        depends_on_tasks = self.depends_on_tasks or ""
+        for d in self.depends_on:
+            if d.task and d.task not in depends_on_tasks:
+                depends_on_tasks += d.task + ","
+        self.depends_on_tasks = depends_on_tasks
 
-	def on_update(self):
-		self.update_nsm_model()
-		self.check_recursion()
-		self.reschedule_dependent_tasks()
-		self.update_project()
-		self.unassign_todo()
-		self.populate_depends_on()
+    def update_nsm_model(self):
+        frappe.utils.nestedset.update_nsm(self)
 
-	def unassign_todo(self):
-		if self.status == "Completed":
-			close_all_assignments(self.doctype, self.name)
-		if self.status == "Cancelled":
-			clear(self.doctype, self.name)
+    def on_update(self):
+        self.update_nsm_model()
+        self.check_recursion()
+        self.reschedule_dependent_tasks()
+        self.update_project()
+        self.unassign_todo()
+        self.populate_depends_on()
 
-	def update_total_expense_claim(self):
-		self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim`
-			where project = %s and task = %s and docstatus=1""",(self.project, self.name))[0][0]
+    def unassign_todo(self):
+        if self.status == "Completed":
+            close_all_assignments(self.doctype, self.name)
+        if self.status == "Cancelled":
+            clear(self.doctype, self.name)
 
-	def update_time_and_costing(self):
-		tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date,
-			sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount,
-			sum(hours) as time from `tabTimesheet Detail` where task = %s and docstatus=1"""
-			,self.name, as_dict=1)[0]
-		if self.status == "Open":
-			self.status = "Working"
-		self.total_costing_amount= tl.total_costing_amount
-		self.total_billing_amount= tl.total_billing_amount
-		self.actual_time= tl.time
-		self.act_start_date= tl.start_date
-		self.act_end_date= tl.end_date
+    def update_total_expense_claim(self):
+        self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim`
+            where project = %s and task = %s and docstatus=1""",(self.project, self.name))[0][0]
 
-	def update_project(self):
-		if self.project and not self.flags.from_project:
-			frappe.get_cached_doc("Project", self.project).update_project()
+    def update_time_and_costing(self):
+        tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date,
+            sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount,
+            sum(hours) as time from `tabTimesheet Detail` where task = %s and docstatus=1"""
+            ,self.name, as_dict=1)[0]
+        if self.status == "Open":
+            self.status = "Working"
+        self.total_costing_amount= tl.total_costing_amount
+        self.total_billing_amount= tl.total_billing_amount
+        self.actual_time= tl.time
+        self.act_start_date= tl.start_date
+        self.act_end_date= tl.end_date
 
-	def check_recursion(self):
-		if self.flags.ignore_recursion_check: return
-		check_list = [['task', 'parent'], ['parent', 'task']]
-		for d in check_list:
-			task_list, count = [self.name], 0
-			while (len(task_list) > count ):
-				tasks = frappe.db.sql(" select %s from `tabTask Depends On` where %s = %s " %
-					(d[0], d[1], '%s'), cstr(task_list[count]))
-				count = count + 1
-				for b in tasks:
-					if b[0] == self.name:
-						frappe.throw(_("Circular Reference Error"), CircularReferenceError)
-					if b[0]:
-						task_list.append(b[0])
+    def update_project(self):
+        if self.project and not self.flags.from_project:
+            frappe.get_cached_doc("Project", self.project).update_project()
 
-				if count == 15:
-					break
+    def check_recursion(self):
+        if self.flags.ignore_recursion_check: return
+        check_list = [['task', 'parent'], ['parent', 'task']]
+        for d in check_list:
+            task_list, count = [self.name], 0
+            while (len(task_list) > count ):
+                tasks = frappe.db.sql(" select %s from `tabTask Depends On` where %s = %s " %
+                    (d[0], d[1], '%s'), cstr(task_list[count]))
+                count = count + 1
+                for b in tasks:
+                    if b[0] == self.name:
+                        frappe.throw(_("Circular Reference Error"), CircularReferenceError)
+                    if b[0]:
+                        task_list.append(b[0])
 
-	def reschedule_dependent_tasks(self):
-		end_date = self.exp_end_date or self.act_end_date
-		if end_date:
-			for task_name in frappe.db.sql("""
-				select name from `tabTask` as parent
-				where parent.project = %(project)s
-					and parent.name in (
-						select parent from `tabTask Depends On` as child
-						where child.task = %(task)s and child.project = %(project)s)
-			""", {'project': self.project, 'task':self.name }, as_dict=1):
-				task = frappe.get_doc("Task", task_name.name)
-				if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open":
-					task_duration = date_diff(task.exp_end_date, task.exp_start_date)
-					task.exp_start_date = add_days(end_date, 1)
-					task.exp_end_date = add_days(task.exp_start_date, task_duration)
-					task.flags.ignore_recursion_check = True
-					task.save()
+                if count == 15:
+                    break
 
-	def has_webform_permission(self):
-		project_user = frappe.db.get_value("Project User", {"parent": self.project, "user":frappe.session.user} , "user")
-		if project_user:
-			return True
+    def reschedule_dependent_tasks(self):
+        end_date = self.exp_end_date or self.act_end_date
+        if end_date:
+            for task_name in frappe.db.sql("""
+                select name from `tabTask` as parent
+                where parent.project = %(project)s
+                    and parent.name in (
+                        select parent from `tabTask Depends On` as child
+                        where child.task = %(task)s and child.project = %(project)s)
+            """, {'project': self.project, 'task':self.name }, as_dict=1):
+                task = frappe.get_doc("Task", task_name.name)
+                if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open":
+                    task_duration = date_diff(task.exp_end_date, task.exp_start_date)
+                    task.exp_start_date = add_days(end_date, 1)
+                    task.exp_end_date = add_days(task.exp_start_date, task_duration)
+                    task.flags.ignore_recursion_check = True
+                    task.save()
 
-	def populate_depends_on(self):
-		if self.parent_task:
-			parent = frappe.get_doc('Task', self.parent_task)
-			if not self.name in [row.task for row in parent.depends_on]:
-				parent.append("depends_on", {
-					"doctype": "Task Depends On",
-					"task": self.name,
-					"subject": self.subject
-				})
-				parent.save()
+    def has_webform_permission(self):
+        project_user = frappe.db.get_value("Project User", {"parent": self.project, "user":frappe.session.user} , "user")
+        if project_user:
+            return True
 
-	def on_trash(self):
-		if check_if_child_exists(self.name):
-			throw(_("Child Task exists for this Task. You can not delete this Task."))
+    def populate_depends_on(self):
+        if self.parent_task:
+            parent = frappe.get_doc('Task', self.parent_task)
+            if self.name not in [row.task for row in parent.depends_on]:
+                parent.append("depends_on", {
+                    "doctype": "Task Depends On",
+                    "task": self.name,
+                    "subject": self.subject
+                })
+                parent.save()
 
-		self.update_nsm_model()
+    def on_trash(self):
+        if check_if_child_exists(self.name):
+            throw(_("Child Task exists for this Task. You can not delete this Task."))
 
-	def after_delete(self):
-		self.update_project()
+        self.update_nsm_model()
 
-	def update_status(self):
-		if self.status not in ('Cancelled', 'Completed') and self.exp_end_date:
-			from datetime import datetime
-			if self.exp_end_date < datetime.now().date():
-				self.db_set('status', 'Overdue', update_modified=False)
-				self.update_project()
+    def after_delete(self):
+        self.update_project()
+
+    def update_status(self):
+        if self.status not in ('Cancelled', 'Completed') and self.exp_end_date:
+            from datetime import datetime
+            if self.exp_end_date < datetime.now().date():
+                self.db_set('status', 'Overdue', update_modified=False)
+                self.update_project()
 
 @frappe.whitelist()
 def check_if_child_exists(name):
-	child_tasks = frappe.get_all("Task", filters={"parent_task": name})
-	child_tasks = [get_link_to_form("Task", task.name) for task in child_tasks]
-	return child_tasks
+    child_tasks = frappe.get_all("Task", filters={"parent_task": name})
+    child_tasks = [get_link_to_form("Task", task.name) for task in child_tasks]
+    return child_tasks
 
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_project(doctype, txt, searchfield, start, page_len, filters):
-	from erpnext.controllers.queries import get_match_cond
-	return frappe.db.sql(""" select name from `tabProject`
-			where %(key)s like %(txt)s
-				%(mcond)s
-			order by name
-			limit %(start)s, %(page_len)s""" % {
-				'key': searchfield,
-				'txt': frappe.db.escape('%' + txt + '%'),
-				'mcond':get_match_cond(doctype),
-				'start': start,
-				'page_len': page_len
-			})
+    from erpnext.controllers.queries import get_match_cond
+    return frappe.db.sql(""" select name from `tabProject`
+            where %(key)s like %(txt)s
+                %(mcond)s
+            order by name
+            limit %(start)s, %(page_len)s""" % {
+                'key': searchfield,
+                'txt': frappe.db.escape('%' + txt + '%'),
+                'mcond':get_match_cond(doctype),
+                'start': start,
+                'page_len': page_len
+            })
 
 
 @frappe.whitelist()
 def set_multiple_status(names, status):
-	names = json.loads(names)
-	for name in names:
-		task = frappe.get_doc("Task", name)
-		task.status = status
-		task.save()
+    names = json.loads(names)
+    for name in names:
+        task = frappe.get_doc("Task", name)
+        task.status = status
+        task.save()
 
 def set_tasks_as_overdue():
-	tasks = frappe.get_all("Task", filters={"status": ["not in", ["Cancelled", "Completed"]]}, fields=["name", "status", "review_date"])
-	for task in tasks:
-		if task.status == "Pending Review":
-			if getdate(task.review_date) > getdate(today()):
-				continue
-		frappe.get_doc("Task", task.name).update_status()
+    tasks = frappe.get_all("Task", filters={"status": ["not in", ["Cancelled", "Completed"]]}, fields=["name", "status", "review_date"])
+    for task in tasks:
+        if task.status == "Pending Review":
+            if getdate(task.review_date) > getdate(today()):
+                continue
+        frappe.get_doc("Task", task.name).update_status()
 
 
 @frappe.whitelist()
 def make_timesheet(source_name, target_doc=None, ignore_permissions=False):
-	def set_missing_values(source, target):
-		target.append("time_logs", {
-			"hours": source.actual_time,
-			"completed": source.status == "Completed",
-			"project": source.project,
-			"task": source.name
-		})
+    def set_missing_values(source, target):
+        target.append("time_logs", {
+            "hours": source.actual_time,
+            "completed": source.status == "Completed",
+            "project": source.project,
+            "task": source.name
+        })
 
-	doclist = get_mapped_doc("Task", source_name, {
-			"Task": {
-				"doctype": "Timesheet"
-			}
-		}, target_doc, postprocess=set_missing_values, ignore_permissions=ignore_permissions)
+    doclist = get_mapped_doc("Task", source_name, {
+            "Task": {
+                "doctype": "Timesheet"
+            }
+        }, target_doc, postprocess=set_missing_values, ignore_permissions=ignore_permissions)
 
-	return doclist
+    return doclist
 
 
 @frappe.whitelist()
 def get_children(doctype, parent, task=None, project=None, is_root=False):
 
-	filters = [['docstatus', '<', '2']]
+    filters = [['docstatus', '<', '2']]
 
-	if task:
-		filters.append(['parent_task', '=', task])
-	elif parent and not is_root:
-		# via expand child
-		filters.append(['parent_task', '=', parent])
-	else:
-		filters.append(['ifnull(`parent_task`, "")', '=', ''])
+    if task:
+        filters.append(['parent_task', '=', task])
+    elif parent and not is_root:
+        # via expand child
+        filters.append(['parent_task', '=', parent])
+    else:
+        filters.append(['ifnull(`parent_task`, "")', '=', ''])
 
-	if project:
-		filters.append(['project', '=', project])
+    if project:
+        filters.append(['project', '=', project])
 
-	tasks = frappe.get_list(doctype, fields=[
-		'name as value',
-		'subject as title',
-		'is_group as expandable'
-	], filters=filters, order_by='name')
+    tasks = frappe.get_list(doctype, fields=[
+        'name as value',
+        'subject as title',
+        'is_group as expandable'
+    ], filters=filters, order_by='name')
 
-	# return tasks
-	return tasks
+    # return tasks
+    return tasks
 
 @frappe.whitelist()
 def add_node():
-	from frappe.desk.treeview import make_tree_args
-	args = frappe.form_dict
-	args.update({
-		"name_field": "subject"
-	})
-	args = make_tree_args(**args)
+    from frappe.desk.treeview import make_tree_args
+    args = frappe.form_dict
+    args.update({
+        "name_field": "subject"
+    })
+    args = make_tree_args(**args)
 
-	if args.parent_task == 'All Tasks' or args.parent_task == args.project:
-		args.parent_task = None
+    if args.parent_task == 'All Tasks' or args.parent_task == args.project:
+        args.parent_task = None
 
-	frappe.get_doc(args).insert()
+    frappe.get_doc(args).insert()
 
 @frappe.whitelist()
 def add_multiple_tasks(data, parent):
-	data = json.loads(data)
-	new_doc = {'doctype': 'Task', 'parent_task': parent if parent!="All Tasks" else ""}
-	new_doc['project'] = frappe.db.get_value('Task', {"name": parent}, 'project') or ""
+    data = json.loads(data)
+    new_doc = {'doctype': 'Task', 'parent_task': parent if parent!="All Tasks" else ""}
+    new_doc['project'] = frappe.db.get_value('Task', {"name": parent}, 'project') or ""
 
-	for d in data:
-		if not d.get("subject"): continue
-		new_doc['subject'] = d.get("subject")
-		new_task = frappe.get_doc(new_doc)
-		new_task.insert()
+    for d in data:
+        if not d.get("subject"): continue
+        new_doc['subject'] = d.get("subject")
+        new_task = frappe.get_doc(new_doc)
+        new_task.insert()
 
 def on_doctype_update():
-	frappe.db.add_index("Task", ["lft", "rgt"])
+    frappe.db.add_index("Task", ["lft", "rgt"])
 
 def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date):
-	if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0:
-		frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date))
+    if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0:
+        frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date))
 
-	if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0:
-		frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date))
+    if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0:
+        frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date))
diff --git a/erpnext/projects/doctype/task/task_list.js b/erpnext/projects/doctype/task/task_list.js
index 941fe97..39734ee 100644
--- a/erpnext/projects/doctype/task/task_list.js
+++ b/erpnext/projects/doctype/task/task_list.js
@@ -20,7 +20,8 @@
 			"Pending Review": "orange",
 			"Working": "orange",
 			"Completed": "green",
-			"Cancelled": "dark grey"
+			"Cancelled": "dark grey",
+			"Template": "blue"
 		}
 		return [__(doc.status), colors[doc.status], "status,=," + doc.status];
 	},
diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py
index 47a28fd..0fad5e8 100644
--- a/erpnext/projects/doctype/task/test_task.py
+++ b/erpnext/projects/doctype/task/test_task.py
@@ -30,14 +30,16 @@
 		})
 
 	def test_reschedule_dependent_task(self):
+		project = frappe.get_value("Project", {"project_name": "_Test Project"})
+
 		task1 = create_task("_Test Task 1", nowdate(), add_days(nowdate(), 10))
 
 		task2 = create_task("_Test Task 2", add_days(nowdate(), 11), add_days(nowdate(), 15), task1.name)
-		task2.get("depends_on")[0].project = "_Test Project"
+		task2.get("depends_on")[0].project = project
 		task2.save()
 
 		task3 = create_task("_Test Task 3", add_days(nowdate(), 11), add_days(nowdate(), 15), task2.name)
-		task3.get("depends_on")[0].project = "_Test Project"
+		task3.get("depends_on")[0].project = project
 		task3.save()
 
 		task1.update({
@@ -97,14 +99,19 @@
 
 		self.assertEqual(frappe.db.get_value("Task", task.name, "status"), "Overdue")
 
-def create_task(subject, start=None, end=None, depends_on=None, project=None, save=True):
+def create_task(subject, start=None, end=None, depends_on=None, project=None, parent_task=None, is_group=0, is_template=0, begin=0, duration=0, save=True):
 	if not frappe.db.exists("Task", subject):
 		task = frappe.new_doc('Task')
 		task.status = "Open"
 		task.subject = subject
 		task.exp_start_date = start or nowdate()
 		task.exp_end_date = end or nowdate()
-		task.project = project or "_Test Project"
+		task.project = project or None if is_template else frappe.get_value("Project", {"project_name": "_Test Project"})
+		task.is_template = is_template
+		task.start = begin
+		task.duration = duration
+		task.is_group = is_group
+		task.parent_task = parent_task
 		if save:
 			task.save()
 	else:
@@ -116,5 +123,4 @@
 		})
 		if save:
 			task.save()
-
 	return task
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index a5ce44d..4cb3804 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -89,10 +89,11 @@
 
 	def test_timesheet_billing_based_on_project(self):
 		emp = make_employee("test_employee_6@salary.com")
+		project = frappe.get_value("Project", {"project_name": "_Test Project"})
 
-		timesheet = make_timesheet(emp, simulate=True, billable=1, project = '_Test Project', company='_Test Company')
+		timesheet = make_timesheet(emp, simulate=True, billable=1, project=project, company='_Test Company')
 		sales_invoice = create_sales_invoice(do_not_save=True)
-		sales_invoice.project = '_Test Project'
+		sales_invoice.project = project
 		sales_invoice.submit()
 
 		ts = frappe.get_doc('Timesheet', timesheet.name)
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index b068245..b123af5 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -134,7 +134,7 @@
 		});
 	},
 
-	project: function(frm) {
+	parent_project: function(frm) {
 		set_project_in_timelog(frm);
 	},
 
@@ -168,8 +168,8 @@
 	},
 
 	time_logs_add: function(frm, cdt, cdn) {
-		if(frm.doc.project) {
-			frappe.model.set_value(cdt, cdn, 'project', frm.doc.project);
+		if(frm.doc.parent_project) {
+			frappe.model.set_value(cdt, cdn, 'project', frm.doc.parent_project);
 		}
 
 		var $trigger_again = $('.form-grid').find('.grid-row').find('.btn-open-row');
@@ -308,7 +308,9 @@
 };
 
 function set_project_in_timelog(frm) {
-	if(frm.doc.project){
-		erpnext.utils.copy_value_in_all_rows(frm.doc, frm.doc.doctype, frm.doc.name, "time_logs", "project");
+	if(frm.doc.parent_project) {
+		$.each(frm.doc.time_logs || [], function(i, item) {
+			frappe.model.set_value(item.doctype, item.name, "project", frm.doc.parent_project);
+		});
 	}
 }
\ No newline at end of file
diff --git a/erpnext/projects/doctype/timesheet/timesheet.json b/erpnext/projects/doctype/timesheet/timesheet.json
index 4c2edf4..b286821 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.json
+++ b/erpnext/projects/doctype/timesheet/timesheet.json
@@ -15,7 +15,7 @@
   "column_break_3",
   "salary_slip",
   "status",
-  "project",
+  "parent_project",
   "employee_detail",
   "employee",
   "employee_name",
@@ -261,7 +261,7 @@
    "read_only": 1
   },
   {
-   "fieldname": "project",
+   "fieldname": "parent_project",
    "fieldtype": "Link",
    "label": "Project",
    "options": "Project"
@@ -271,7 +271,7 @@
  "idx": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-29 07:50:35.938231",
+ "modified": "2021-01-08 20:51:14.590080",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Timesheet",
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 2f15cbc..f0212db 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -55,6 +55,8 @@
 	"js/item-dashboard.min.js": [
 		"stock/dashboard/item_dashboard.html",
 		"stock/dashboard/item_dashboard_list.html",
-		"stock/dashboard/item_dashboard.js"
+		"stock/dashboard/item_dashboard.js",
+		"stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html",
+		"stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html"
 	]
 }
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/buying.js b/erpnext/public/js/controllers/buying.js
index db85a3e..a2a723d 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -195,6 +195,10 @@
 		this._super(doc, cdt, cdn);
 	},
 
+	batch_no: function(doc, cdt, cdn) {
+		this._super(doc, cdt, cdn);
+	},
+
 	received_qty: function(doc, cdt, cdn) {
 		this.calculate_accepted_qty(doc, cdt, cdn)
 	},
@@ -516,4 +520,4 @@
 	});
 
 	dialog.show();
-}
+}
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 3bc20f8..9627600 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();
@@ -103,9 +105,19 @@
 		frappe.ui.form.on(this.frm.doctype + " Item", {
 			items_add: function(frm, cdt, cdn) {
 				var item = frappe.get_doc(cdt, cdn);
-				if(!item.warehouse && frm.doc.set_warehouse) {
+				if (!item.warehouse && frm.doc.set_warehouse) {
 					item.warehouse = frm.doc.set_warehouse;
 				}
+
+				if (!item.target_warehouse && frm.doc.set_target_warehouse) {
+					item.target_warehouse = frm.doc.set_target_warehouse;
+				}
+
+				if (!item.from_warehouse && frm.doc.set_from_warehouse) {
+					item.from_warehouse = frm.doc.set_from_warehouse;
+				}
+
+				erpnext.accounts.dimensions.copy_dimension_from_first_row(frm, cdt, cdn, 'items');
 			}
 		});
 
@@ -159,16 +171,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) {
@@ -233,6 +235,8 @@
 				}
 			};
 
+			this.frm.trigger('set_default_internal_warehouse');
+
 			return frappe.run_serially([
 				() => set_value('currency', currency),
 				() => set_value('price_list_currency', currency),
@@ -543,6 +547,7 @@
 							company: me.frm.doc.company,
 							order_type: me.frm.doc.order_type,
 							is_pos: cint(me.frm.doc.is_pos),
+							is_return: cint(me.frm.doc.is_return),
 							is_subcontracted: me.frm.doc.is_subcontracted,
 							transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date,
 							ignore_pricing_rule: me.frm.doc.ignore_pricing_rule,
@@ -594,12 +599,22 @@
 										return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
 											.then((r) => {
 												if (r.message &&
-													(r.message.has_batch_no || r.message.has_serial_no)) {
+												(r.message.has_batch_no || r.message.has_serial_no)) {
 													frappe.flags.hide_serial_batch_dialog = false;
 												}
 											});
 								},
 								() => {
+									// check if batch serial selector is disabled or not
+									if (show_batch_dialog && !frappe.flags.hide_serial_batch_dialog)
+										return frappe.db.get_single_value('Stock Settings', 'disable_serial_no_and_batch_selector')
+											.then((value) => {
+												if (value) {
+													frappe.flags.hide_serial_batch_dialog = true;
+												}
+											});
+								},
+								() => {
 									if(show_batch_dialog && !frappe.flags.hide_serial_batch_dialog) {
 										var d = locals[cdt][cdn];
 										$.each(r.message, function(k, v) {
@@ -653,7 +668,7 @@
 				args: item_args
 			},
 			callback: function(r) {
-				frappe.model.set_value(item.doctype, item.name, 'rate', r.message);
+				frappe.model.set_value(item.doctype, item.name, 'rate', r.message * item.conversion_factor);
 			}
 		});
 	},
@@ -719,6 +734,31 @@
 		this.calculate_taxes_and_totals(false);
 	},
 
+	update_stock: function() {
+		this.frm.trigger('set_default_internal_warehouse');
+	},
+
+	set_default_internal_warehouse: function() {
+		let me = this;
+		if ((this.frm.doc.doctype === 'Sales Invoice' && me.frm.doc.update_stock)
+			|| this.frm.doc.doctype == 'Delivery Note') {
+			if (this.frm.doc.is_internal_customer && this.frm.doc.company === this.frm.doc.represents_company) {
+				frappe.db.get_value('Company', this.frm.doc.company, 'default_in_transit_warehouse', function(value) {
+					me.frm.set_value('set_target_warehouse', value.default_in_transit_warehouse);
+				});
+			}
+		}
+
+		if ((this.frm.doc.doctype === 'Purchase Invoice' && me.frm.doc.update_stock)
+			|| this.frm.doc.doctype == 'Purchase Receipt') {
+			if (this.frm.doc.is_internal_supplier && this.frm.doc.company === this.frm.doc.represents_company) {
+				frappe.db.get_value('Company', this.frm.doc.company, 'default_in_transit_warehouse', function(value) {
+					me.frm.set_value('set_from_warehouse', value.default_in_transit_warehouse);
+				});
+			}
+		}
+	},
+
 	company: function() {
 		var me = this;
 		var set_pricing = function() {
@@ -805,7 +845,7 @@
 			in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) {
 			erpnext.utils.get_shipping_address(this.frm, function(){
 				set_party_account(set_pricing);
-			})
+			});
 
 			// Get default company billing address in Purchase Invoice, Order and Receipt
 			frappe.call({
@@ -1104,6 +1144,11 @@
 		}
 	},
 
+	batch_no: function(doc, cdt, cdn) {
+		let item = frappe.get_doc(cdt, cdn);
+		this.apply_price_list(item, true);
+	},
+
 	toggle_conversion_factor: function(item) {
 		// toggle read only property for conversion factor field if the uom and stock uom are same
 		if(this.frm.get_field('items').grid.fields_map.conversion_factor) {
@@ -1408,6 +1453,7 @@
 					"pricing_rules": d.pricing_rules,
 					"warehouse": d.warehouse,
 					"serial_no": d.serial_no,
+					"batch_no": d.batch_no,
 					"price_list_rate": d.price_list_rate,
 					"conversion_factor": d.conversion_factor || 1.0
 				});
@@ -1966,6 +2012,14 @@
 		this.autofill_warehouse(this.frm.doc.items, "warehouse", this.frm.doc.set_warehouse);
 	},
 
+	set_target_warehouse: function() {
+		this.autofill_warehouse(this.frm.doc.items, "target_warehouse", this.frm.doc.set_target_warehouse);
+	},
+
+	set_from_warehouse: function() {
+		this.autofill_warehouse(this.frm.doc.items, "from_warehouse", this.frm.doc.set_from_warehouse);
+	},
+
 	autofill_warehouse : function (child_table, warehouse_field, warehouse) {
 		if (warehouse && child_table && child_table.length) {
 			let doctype = child_table[0].doctype;
@@ -2030,3 +2084,35 @@
 		}, show_dialog);
 	});
 }
+
+erpnext.apply_putaway_rule = (frm, purpose=null) => {
+	if (!frm.doc.company) {
+		frappe.throw({message: __("Please select a Company first."), title: __("Mandatory")});
+	}
+	if (!frm.doc.items.length) return;
+
+	frappe.call({
+		method: "erpnext.stock.doctype.putaway_rule.putaway_rule.apply_putaway_rule",
+		args: {
+			doctype: frm.doctype,
+			items: frm.doc.items,
+			company: frm.doc.company,
+			sync: true,
+			purpose: purpose
+		},
+		callback: (result) => {
+			if (!result.exc && result.message) {
+				frm.clear_table("items");
+
+				let items =  result.message;
+				items.forEach((row) => {
+					delete row["name"]; // dont overwrite name from server side
+					let child = frm.add_child("items");
+					Object.assign(child, row);
+					frm.script_manager.trigger("qty", child.doctype, child.name);
+				});
+				frm.get_field("items").grid.refresh();
+			}
+		}
+	});
+};
\ No newline at end of file
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/telephony.js b/erpnext/public/js/telephony.js
index bd7f890..f9caade 100644
--- a/erpnext/public/js/telephony.js
+++ b/erpnext/public/js/telephony.js
@@ -20,4 +20,4 @@
 				});
 		}
 	}
-});
\ No newline at end of file
+});
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/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index 770704e..808dd5a 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -276,6 +276,12 @@
 
 erpnext.utils.get_shipping_address = function(frm, callback){
 	if (frm.doc.company) {
+		if (!(frm.doc.inter_com_order_reference || frm.doc.internal_invoice_reference ||
+			frm.doc.internal_order_reference)) {
+			if (callback) {
+				return callback();
+			}
+		}
 		frappe.call({
 			method: "erpnext.accounts.custom.address.get_shipping_address",
 			args: {
diff --git a/erpnext/regional/doctype/datev_settings/datev_settings.json b/erpnext/regional/doctype/datev_settings/datev_settings.json
index 713e8e3..f60de4c 100644
--- a/erpnext/regional/doctype/datev_settings/datev_settings.json
+++ b/erpnext/regional/doctype/datev_settings/datev_settings.json
@@ -7,13 +7,14 @@
  "engine": "InnoDB",
  "field_order": [
   "client",
-  "account_number_length",
-  "column_break_2",
   "client_number",
-  "section_break_4",
+  "column_break_2",
+  "consultant_number",
   "consultant",
+  "section_break_4",
+  "account_number_length",
   "column_break_6",
-  "consultant_number"
+  "temporary_against_account_number"
  ],
  "fields": [
   {
@@ -66,10 +67,17 @@
    "fieldtype": "Int",
    "label": "Account Number Length",
    "reqd": 1
+  },
+  {
+   "allow_in_quick_entry": 1,
+   "fieldname": "temporary_against_account_number",
+   "fieldtype": "Data",
+   "label": "Temporary Against Account Number",
+   "reqd": 1
   }
  ],
  "links": [],
- "modified": "2020-11-05 17:52:11.674329",
+ "modified": "2020-11-19 19:00:09.088816",
  "modified_by": "Administrator",
  "module": "Regional",
  "name": "DATEV Settings",
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
index 5c1c79d..3034370 100644
--- a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
@@ -24,9 +24,8 @@
   },
   {
    "fieldname": "reference_invoice",
-   "fieldtype": "Link",
-   "label": "Reference Invoice",
-   "options": "Sales Invoice"
+   "fieldtype": "Data",
+   "label": "Reference Invoice"
   },
   {
    "fieldname": "headers",
@@ -64,7 +63,7 @@
  ],
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-12-24 21:09:38.882866",
+ "modified": "2021-01-13 12:06:57.253111",
  "modified_by": "Administrator",
  "module": "Regional",
  "name": "E Invoice Request Log",
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
index 4dcb22a..db8bda7 100644
--- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
@@ -7,6 +7,7 @@
  "field_order": [
   "enable",
   "section_break_2",
+  "sandbox_mode",
   "credentials",
   "auth_token",
   "token_expiry"
@@ -41,12 +42,18 @@
    "label": "Credentials",
    "mandatory_depends_on": "enable",
    "options": "E Invoice User"
+  },
+  {
+   "default": "0",
+   "fieldname": "sandbox_mode",
+   "fieldtype": "Check",
+   "label": "Sandbox Mode"
   }
  ],
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-12-22 15:34:57.280044",
+ "modified": "2021-01-13 12:04:49.449199",
  "modified_by": "Administrator",
  "module": "Regional",
  "name": "E Invoice Settings",
diff --git a/erpnext/regional/germany/accounts_controller.py b/erpnext/regional/germany/accounts_controller.py
deleted file mode 100644
index 5b2b31f..0000000
--- a/erpnext/regional/germany/accounts_controller.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import frappe
-from frappe import _
-from frappe import msgprint
-
-
-REQUIRED_FIELDS = {
-	"Sales Invoice": [
-		{
-			"field_name": "company_address",
-			"regulation": "§ 14 Abs. 4 Nr. 1 UStG"
-		},
-		{
-			"field_name": "company_tax_id",
-			"regulation": "§ 14 Abs. 4 Nr. 2 UStG"
-		},
-		{
-			"field_name": "taxes",
-			"regulation": "§ 14 Abs. 4 Nr. 8 UStG"
-		},
-		{
-			"field_name": "customer_address",
-			"regulation": "§ 14 Abs. 4 Nr. 1 UStG",
-			"condition": "base_grand_total > 250"
-		}
-	]
-}
-
-
-def validate_regional(doc):
-	"""Check if required fields for this document are present."""
-	required_fields = REQUIRED_FIELDS.get(doc.doctype)
-	if not required_fields:
-		return
-
-	meta = frappe.get_meta(doc.doctype)
-	field_map = {field.fieldname: field.label for field in meta.fields}
-
-	for field in required_fields:
-		condition = field.get("condition")
-		if condition and not frappe.safe_eval(condition, doc.as_dict()):
-			continue
-
-		field_name = field.get("field_name")
-		regulation = field.get("regulation")
-		if field_name and not doc.get(field_name):
-			missing(field_map.get(field_name), regulation)
-
-
-def missing(field_label, regulation):
-	"""Notify the user that a required field is missing."""
-	context = 'Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.'
-	msgprint(_('Remember to set {field_label}. It is required by {regulation}.', context=context).format(
-			field_label=frappe.bold(_(field_label)),
-			regulation=regulation
-		)
-	)
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
index 9c86cc8..a530683 100644
--- a/erpnext/regional/india/e_invoice/einvoice.js
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -18,6 +18,9 @@
 
 			if (!irn && !__unsaved) {
 				const action = () => {
+					if (frm.doc.__unsaved) {
+						frappe.throw(__('Please save the document to generate IRN.'));
+					}
 					frappe.call({
 						method: 'erpnext.regional.india.e_invoice.utils.get_einvoice',
 						args: { doctype, docname: name },
@@ -42,7 +45,7 @@
 						"default": "1-Duplicate",
 						"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
 					},
-					{ 
+					{
 						"label": "Remark",
 						"fieldname": "remark",
 						"fieldtype": "Data",
@@ -57,7 +60,7 @@
 							const data = d.get_values();
 							frappe.call({
 								method: 'erpnext.regional.india.e_invoice.utils.cancel_irn',
-								args: { 
+								args: {
 									doctype,
 									docname: name,
 									irn: irn,
@@ -252,7 +255,7 @@
 		title: __("Preview"),
 		wide: 1,
 		fields: [
-			{ 
+			{
 				"label": "Preview",
 				"fieldname": "preview_html",
 				"fieldtype": "HTML"
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 02ce6c1..0f604ef 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -11,11 +11,12 @@
 import base64
 import frappe
 import traceback
+import io
 from frappe import _, bold
 from pyqrcode import create as qrcreate
 from frappe.integrations.utils import make_post_request, make_get_request
 from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
-from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date
+from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form
 
 def validate_einvoice_fields(doc):
 	einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
@@ -60,7 +61,7 @@
 	elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP'
 	elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP'
 
-	if not supply_type: 
+	if not supply_type:
 		rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export')
 		frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export),
 			title=_('Invalid Supply Type'))
@@ -84,29 +85,32 @@
 	))
 
 def get_party_details(address_name):
-	address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
-	gstin = address.get('gstin')
+	d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
 
-	gstin_details = get_gstin_details(gstin)
-	legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName')
-	location = gstin_details.get('AddrLoc') or address.get('city')
-	state_code = gstin_details.get('StateCode')
-	pincode = gstin_details.get('AddrPncd')
-	address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno'))
-	address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt'))
-	email_id = address.get('email_id')
-	phone = address.get('phone')
-	# get last 10 digit 
-	phone = phone.replace(" ", "")[-10:] if phone else ''
+	if (not d.gstin
+		or not d.city
+		or not d.pincode
+		or not d.address_title
+		or not d.address_line1
+		or not d.gst_state_number):
 
-	if state_code == 97:
+		frappe.throw(
+			msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format(
+				get_link_to_form('Address', address_name)
+			),
+			title=_('Missing Address Fields')
+		)
+
+	if d.gst_state_number == 97:
 		# according to einvoice standard
 		pincode = 999999
 
 	return frappe._dict(dict(
-		gstin=gstin, legal_name=legal_name, location=location,
-		pincode=pincode, state_code=state_code, address_line1=address_line1,
-		address_line2=address_line2, email=email_id, phone=phone
+		gstin=d.gstin, legal_name=d.address_title,
+		location=d.city, pincode=d.pincode,
+		state_code=d.gst_state_number,
+		address_line1=d.address_line1,
+		address_line2=d.address_line2
 	))
 
 def get_gstin_details(gstin):
@@ -122,19 +126,27 @@
 	if details:
 		frappe.local.gstin_cache[key] = details
 		return details
-	
+
 	if not details:
 		return GSPConnector.get_gstin_details(gstin)
 
 def get_overseas_address_details(address_name):
-	address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value(
-		'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id']
+	address_title, address_line1, address_line2, city = frappe.db.get_value(
+		'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city']
 	)
 
+	if not address_title or not address_line1 or not city:
+		frappe.throw(
+			msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format(
+				get_link_to_form('Address', address_name)
+			),
+			title=_('Missing Address Fields')
+		)
+
 	return frappe._dict(dict(
-		gstin='URP', legal_name=address_title, address_line1=address_line1,
-		address_line2=address_line2, email=email_id, phone=phone,
-		pincode=999999, state_code=96, place_of_supply=96, location=city
+		gstin='URP', legal_name=address_title, location=city,
+		address_line1=address_line1, address_line2=address_line2,
+		pincode=999999, state_code=96, place_of_supply=96
 	))
 
 def get_item_list(invoice):
@@ -146,19 +158,21 @@
 		item.update(d.as_dict())
 
 		item.sr_no = d.idx
-		item.discount_amount = abs(item.discount_amount * item.qty)
-		item.description = d.item_name
+		item.description = d.item_name.replace('"', '\\"')
+
 		item.qty = abs(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.discount_amount = abs(item.discount_amount * item.qty)
+		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
 		item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y'
+		item.serial_no = ""
 
 		item = update_item_taxes(invoice, item)
-		
+
 		item.total_value = abs(
 			item.taxable_value + item.igst_amount + item.sgst_amount +
 			item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges
@@ -185,7 +199,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]
@@ -204,14 +218,20 @@
 
 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.discount_amount
-	invoice_value_details.round_off = invoice.rounding_adjustment
+
+	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)
-	
+
 	invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
-	
+
 	return invoice_value_details
 
 def update_invoice_taxes(invoice, invoice_value_details):
@@ -228,13 +248,13 @@
 			if t.account_head in gst_accounts.cess_account:
 				# using after discount amt since item also uses after discount amt for cess calc
 				invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
-			
+
 			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
 
 def get_payment_details(invoice):
@@ -272,7 +292,25 @@
 		vehicle_type=vehicle_type[invoice.gst_vehicle_type]
 	))
 
+def validate_mandatory_fields(invoice):
+	if not invoice.company_address:
+		frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields'))
+	if not invoice.customer_address:
+		frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields'))
+	if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
+		frappe.throw(
+			_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
+			title=_('Missing Fields')
+		)
+	if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
+		frappe.throw(
+			_('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'),
+			title=_('Missing Fields')
+		)
+
 def make_einvoice(invoice):
+	validate_mandatory_fields(invoice)
+
 	schema = read_json('einv_template')
 
 	transaction_details = get_transaction_details(invoice)
@@ -288,20 +326,23 @@
 		place_of_supply = get_place_of_supply(invoice, invoice.doctype) or invoice.billing_address_gstin
 		place_of_supply = place_of_supply[:2]
 		buyer_details.update(dict(place_of_supply=place_of_supply))
-	
+
 	shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
 	if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
-		shipping_details = get_party_details(invoice.shipping_address_name)
-	
+		if invoice.gst_category == 'Overseas':
+			shipping_details = get_overseas_address_details(invoice.shipping_address_name)
+		else:
+			shipping_details = get_party_details(invoice.shipping_address_name)
+
 	if invoice.is_pos and invoice.base_paid_amount:
 		payment_details = get_payment_details(invoice)
-	
+
 	if invoice.is_return and invoice.return_against:
 		prev_doc_details = get_return_doc_reference(invoice)
-	
+
 	if invoice.transporter:
 		eway_bill_details = get_eway_bill_details(invoice)
-	
+
 	# not yet implemented
 	dispatch_details = period_details = export_details = frappe._dict({})
 
@@ -313,7 +354,7 @@
 		export_details=export_details, eway_bill_details=eway_bill_details
 	)
 	einvoice = json.loads(einvoice)
-	
+
 	validations = json.loads(read_json('einv_validation'))
 	errors = validate_einvoice(validations, einvoice)
 	if errors:
@@ -351,7 +392,7 @@
 					# remove empty dicts
 					einvoice.pop(fieldname, None)
 			continue
-		
+
 		# convert to int or str
 		if value_type == 'string':
 			einvoice[fieldname] = str(value)
@@ -375,7 +416,7 @@
 			errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum))
 		if pattern_str and not pattern.match(value):
 			errors.append(field_validation.get('validationMsg'))
-	
+
 	return errors
 
 class RequestFailed(Exception): pass
@@ -383,16 +424,20 @@
 class GSPConnector():
 	def __init__(self, doctype=None, docname=None):
 		self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
+		sandbox_mode = self.e_invoice_settings.sandbox_mode
+
 		self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
 		self.credentials = self.get_credentials()
 
-		self.base_url = 'https://gsp.adaequare.com'
-		self.authenticate_url = self.base_url + '/gsp/authenticate?grant_type=token'
-		self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
-		self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
-		self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
+		# authenticate url is same for sandbox & live
+		self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token'
+		self.base_url = 'https://gsp.adaequare.com' if not sandbox_mode else 'https://gsp.adaequare.com/test'
+
 		self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
-		self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi'
+		self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
+		self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
+		self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
+		self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB'
 		self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
 
 	def get_credentials(self):
@@ -402,19 +447,19 @@
 		else:
 			credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
 		return credentials
-	
+
 	def get_seller_gstin(self):
 		gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
 		if not gstin:
 			frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
 		return gstin
-	
+
 	def get_auth_token(self):
 		if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0:
 			self.fetch_auth_token()
-		
+
 		return self.e_invoice_settings.auth_token
-	
+
 	def make_request(self, request_type, url, headers=None, data=None):
 		if request_type == 'post':
 			res = make_post_request(url, headers=headers, data=data)
@@ -423,7 +468,7 @@
 
 		self.log_request(url, headers, data, res)
 		return res
-	
+
 	def log_request(self, url, headers, data, res):
 		headers.update({ 'password': self.credentials.password })
 		request_log = frappe.get_doc({
@@ -435,7 +480,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):
@@ -448,12 +493,13 @@
 			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)
 			self.raise_error(True)
-	
+
 	def get_headers(self):
 		return {
 			'content-type': 'application/json',
@@ -475,14 +521,13 @@
 			else:
 				self.log_error(res)
 				raise RequestFailed
-		
+
 		except RequestFailed:
 			self.raise_error()
 
 		except Exception:
 			self.log_error()
 			self.raise_error(True)
-	
 	@staticmethod
 	def get_gstin_details(gstin):
 		'''fetch and cache GSTIN details'''
@@ -520,7 +565,7 @@
 
 			else:
 				raise RequestFailed
-		
+
 		except RequestFailed:
 			errors = self.sanitize_error_message(res.get('message'))
 			self.raise_error(errors=errors)
@@ -528,7 +573,7 @@
 		except Exception:
 			self.log_error(data)
 			self.raise_error(True)
-	
+
 	def get_irn_details(self, irn):
 		headers = self.get_headers()
 
@@ -539,7 +584,7 @@
 				return res.get('result')
 			else:
 				raise RequestFailed
-		
+
 		except RequestFailed:
 			errors = self.sanitize_error_message(res.get('message'))
 			self.raise_error(errors=errors)
@@ -547,7 +592,7 @@
 		except Exception:
 			self.log_error()
 			self.raise_error(True)
-	
+
 	def cancel_irn(self, irn, reason, remark):
 		headers = self.get_headers()
 		data = json.dumps({
@@ -569,7 +614,7 @@
 
 			else:
 				raise RequestFailed
-		
+
 		except RequestFailed:
 			errors = self.sanitize_error_message(res.get('message'))
 			self.raise_error(errors=errors)
@@ -577,7 +622,6 @@
 		except Exception:
 			self.log_error(data)
 			self.raise_error(True)
-	
 	def generate_eway_bill(self, **kwargs):
 		args = frappe._dict(kwargs)
 
@@ -618,7 +662,7 @@
 		except Exception:
 			self.log_error(data)
 			self.raise_error(True)
-	
+
 	def cancel_eway_bill(self, eway_bill, reason, remark):
 		headers = self.get_headers()
 		data = json.dumps({
@@ -626,7 +670,8 @@
 			'cancelRsnCode': reason,
 			'cancelRmrk': remark
 		}, indent=4)
-
+		headers["username"] = headers["user_name"]
+		del headers["user_name"]
 		try:
 			res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
 			if res.get('success'):
@@ -649,7 +694,7 @@
 		except Exception:
 			self.log_error(data)
 			self.raise_error(True)
-	
+
 	def sanitize_error_message(self, message):
 		'''
 			On validation errors, response message looks something like this:
@@ -688,7 +733,7 @@
 			"Exception:", err_tb
 		])
 		frappe.log_error(title=_('E Invoice Request Failed'), message=message)
-	
+
 	def raise_error(self, raise_exception=False, errors=[]):
 		title = _('E Invoice Request Failed')
 		if errors:
@@ -701,7 +746,7 @@
 				raise_exception=raise_exception,
 				indicator='red'
 			)
-	
+
 	def set_einvoice_data(self, res):
 		enc_signed_invoice = res.get('SignedInvoice')
 		dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data']
@@ -719,28 +764,27 @@
 			'label': _('IRN Generated')
 		}
 		self.update_invoice()
-	
 	def attach_qrcode_image(self):
 		qrcode = self.invoice.signed_qr_code
 		doctype = self.invoice.doctype
 		docname = self.invoice.name
+		filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__")
 
-		_file = frappe.new_doc('File')
-		_file.update({
-			'file_name': f'QRCode_{docname}.png',
-			'attached_to_doctype': doctype,
-			'attached_to_name': docname,
-			'content': 'qrcode',
-			'is_private': 1
-		})
-		_file.insert()
-		frappe.db.commit()
+		qr_image = io.BytesIO()
 		url = qrcreate(qrcode, error='L')
-		abs_file_path = os.path.abspath(_file.get_full_path())
-		url.png(abs_file_path, scale=2, quiet_zone=1)
-
+		url.png(qr_image, scale=2, quiet_zone=1)
+		_file = frappe.get_doc({
+			"doctype": "File",
+			"file_name": filename,
+			"attached_to_doctype": doctype,
+			"attached_to_name": docname,
+			"attached_to_field": "qrcode_image",
+			"is_private": 1,
+			"content": qr_image.getvalue()})
+		_file.save()
+		frappe.db.commit()
 		self.invoice.qrcode_image = _file.file_url
-	
+
 	def update_invoice(self):
 		self.invoice.flags.ignore_validate_update_after_submit = True
 		self.invoice.flags.ignore_validate = True
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 5321a9a..5261984 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -7,7 +7,7 @@
 from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
 from frappe.permissions import add_permission, update_permission_property
 from erpnext.regional.india import states
-from erpnext.accounts.utils import get_fiscal_year
+from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
 from frappe.utils import today
 
 def setup(company=None, patch=True):
@@ -629,15 +629,20 @@
 
 def set_tax_withholding_category(company):
 	accounts = []
+	fiscal_year = None
 	abbr = frappe.get_value("Company", company, "abbr")
 	tds_account = frappe.get_value("Account", 'TDS Payable - {0}'.format(abbr), 'name')
 
 	if company and tds_account:
 		accounts = [dict(company=company, account=tds_account)]
 
-	fiscal_year = get_fiscal_year(today(), company=company)[0]
-	docs = get_tds_details(accounts, fiscal_year)
+	try:
+		fiscal_year = get_fiscal_year(today(), verbose=0, company=company)[0]
+	except FiscalYearError:
+		pass
 
+	docs = get_tds_details(accounts, fiscal_year)
+	
 	for d in docs:
 		try:
 			doc = frappe.get_doc(d)
@@ -650,11 +655,14 @@
 			if accounts:
 				doc.append("accounts", accounts[0])
 
-			# if fiscal year don't match with any of the already entered data, append rate row
-			fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year]
-			if not fy_exist:
-				doc.append("rates", d.get('rates')[0])
-
+			if fiscal_year:
+				# if fiscal year don't match with any of the already entered data, append rate row
+				fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year]
+				if not fy_exist:
+					doc.append("rates", d.get('rates')[0])
+					
+			doc.flags.ignore_permissions = True
+			doc.flags.ignore_mandatory = True
 			doc.save()
 
 def set_tds_account(docs, company):
diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js
index 87baece..f09d3d0 100644
--- a/erpnext/regional/india/taxes.js
+++ b/erpnext/regional/india/taxes.js
@@ -40,14 +40,12 @@
 				callback: function(r) {
 					if(r.message) {
 						frm.set_value('taxes_and_charges', r.message.taxes_and_charges);
+						frm.set_value('taxes', r.message.taxes);
 						frm.set_value('place_of_supply', r.message.place_of_supply);
-					} else if (frm.doc.is_internal_supplier || frm.doc.is_internal_customer) {
-						frm.set_value('taxes_and_charges', '');
-						frm.set_value('taxes', []);
 					}
 				}
 			});
 		}
 	});
-};
+}
 
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 95ff291..d6200c9 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -48,6 +48,9 @@
 		validate_gstin_check_digit(doc.gstin)
 		set_gst_state_and_state_number(doc)
 
+		if not doc.gst_state:
+			frappe.throw(_("Please Enter GST state"))
+
 		if doc.gst_state_number != doc.gstin[:2]:
 			frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
 				.format(doc.gst_state_number))
@@ -168,7 +171,7 @@
 
 	if is_internal_transfer(party_details, doctype):
 		party_details.taxes_and_charges = ''
-		party_details.taxes = ''
+		party_details.taxes = []
 		return party_details
 
 	if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
diff --git a/erpnext/regional/print_format/irs_1099_form/irs_1099_form.json b/erpnext/regional/print_format/irs_1099_form/irs_1099_form.json
index ce8c44a..e59700f 100644
--- a/erpnext/regional/print_format/irs_1099_form/irs_1099_form.json
+++ b/erpnext/regional/print_format/irs_1099_form/irs_1099_form.json
@@ -1,23 +1,26 @@
-[
- {
-	"align_labels_right": 0,
-	"css": "",
-	"custom_format": 1,
-	"default_print_language": "en",
-	"disabled": 0,
-	"doc_type": "Supplier",
-	"docstatus": 0,
-	"doctype": "Print Format",
-	"font": "Default",
-	"format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div class=\\\"print-heading\\\">\\t\\t\\t\\t<h2>TAX Invoice<br><small>{{ doc.name }}</small>\\t\\t\\t\\t</h2></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"customer_name\", \"label\": \"Customer Name\"}, {\"print_hide\": 0, \"fieldname\": \"customer_name_in_arabic\", \"label\": \"Customer Name in Arabic\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"posting_date\", \"label\": \"Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Address\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company\", \"label\": \"Company\"}, {\"print_hide\": 0, \"fieldname\": \"company_trn\", \"label\": \"Company TRN\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company_address_display\", \"label\": \"Company Address\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"item_code\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"200px\"}, {\"print_hide\": 0, \"fieldname\": \"uom\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_code\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"items\", \"label\": \"Items\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"total\", \"label\": \"Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"charge_type\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"row_id\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"account_head\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"cost_center\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"300px\"}, {\"print_hide\": 0, \"fieldname\": \"rate\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"total\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_amount_after_discount_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_tax_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_total\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_tax_amount_after_discount_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"item_wise_tax_detail\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"taxes\", \"label\": \"Sales Taxes and Charges\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"grand_total\", \"label\": \"Grand Total\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\", \"label\": \"Rounded Total\"}, {\"print_hide\": 0, \"fieldname\": \"in_words\", \"align\": \"left\", \"label\": \"In Words\"}]",
-	"html": "<div id=\"copy_a\" style=\"position: relative; top:0cm; width:17cm;height:28.0cm;\">\n  <table>\n  <tbody>\n    <tr style=\"height:12mm\">\n      <td class=\"tbs rbs lbs bbs\" style=\"width:86mm\" colspan=\"4\"; rowspan=\"3\">PAYER'S name, street address, city or town, state or province, country, ZIP<br>or foreign postal code, and telephone no.<br>\n\t{{company if company else \"\"}}<br>\n\t{{payer_street_address if payer_street_address else \"\"}}\n</td>\n      <td class=\"tbs rbs lbs bbs\" style=\"width:35mm\">1 Rents</td>\n      <td class=\"tbs rbs lbs bbs\" style=\"width:25mm\" rowspan=\"2\">OMB No. 1545-0115<br><yone>20</yone><ytwo>18</ytwo><br>Form 1099-MISC</td>\n      <td class=\"lbs bbs\" style=\"width:38mm\" colspan=\"2\" rowspan=\"2\">Miscellaneous Income</td>\n    </tr>\n    <tr style=\"height:12mm\">\n      <td class=\"tbs rbs lbs bbs\" style=\"width:35mm\">2 Royalties</td>\n    </tr>\n    <tr style=\"height:9mm\">\n      <td class=\"tbs rbs lbs bbs\" >3 Other Income<br>\n\t{{payments if payments else \"\"}}\n\t</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">4 Federal Income tax withheld</td>\n      <td class=\"tbs lbs bbs\" style=\"width:29mm\" rowspan=\"2\">Copy A<br>For<br>Internal Revenue<br>Service Center<br><br>File with Form 1096</td>\n    </tr>\n    <tr style=\"height:16mm\">\n      <td class=\"tbs rbs lbs bbs\" style=\"width:43mm\">PAYER'S TIN<br>\n\t{{company_tin if company_tin else \"\"}}\n\t</td>\n      \n      <td class=\"tbs rbs lbs bbs\" colspan=\"3\">RECIPIENT'S TIN<br><br>\n      {{tax_id if tax_id else \"None\"}}\n</td>\n      <td class=\"tbs rbs lbs bbs\" >Fishing boat proceeds</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">6 Medical and health care payments</td>\n    </tr>\n    <tr style=\"height:12mm\">\n      <td class=\"tbs rbs lbs bbs\" colspan=\"4\">RECIPIENT'S name <br>\n         {{supplier if supplier else \"\"}}\n      </td>\n      <td class=\"tbs rbs lbs bbs\" >7 Nonemployee compensation<br>\n\t</td> \n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">Substitute payments in lieu of dividends or interest</td>\n      <td class=\"tbs lbs bbs\" rowspan=\"6\">For Privacy Act<br>and Paperwork<br>Reduction Act<br>Notice, see the<br>2018 General<br>Instructions for<br>Certain<br>Information<br>Returns.</td>\n    </tr>\n    <tr style=\"height:6mm\">\n      <td class=\"tbs rbs lbs bbs\" colspan=\"4\" rowspan=\"2\">Street address (including apt. no.)<br>\n\t{{recipient_street_address if recipient_street_address else \"\"}}\n\t</td>\n      <td class=\"tbs rbs lbs bbs\" >$___________</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">$___________</td>\n    </tr>\n    <tr style=\"height:7mm\">\n      <td class=\"tbs rbs lbs bbs\" rowspan=\"2\">9 Payer made direct sales of<br>$5,000 or more of consumer products<br>to a buyer<br>(recipient) for resale</td>\n      <td class=\"tbs rbs lbs\" colspan=\"2\">10 Crop insurance proceeds</td>\n    </tr>\n    <tr style=\"height:5mm\">\n      <td class=\"tbs rbs lbs bbs\" colspan=\"4\" rowspan=\"2\">City or town, state or province, country, and ZIP or foreign postal code<br>\n\t{{recipient_city_state if recipient_city_state else \"\"}}\n</td>\n      <td style=\"vertical-align:bottom\" class=\" rbs lbs bbs\" colspan=\"2\">$___________</td>\n    </tr>\n    <tr style=\"height:9mm\">\n      <td class=\"tbs rbs lbs bbs\" >11</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=2>12</td>\n    </tr>\n    <tr style=\"height:13mm\">\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">Account number (see instructions)</td>\n      <td class=\"tbs rbs lbs bbs\" style=\"width:16mm\">FACTA filing<br>requirement</td>\n      <td class=\"tbs rbs lbs bbs\" style=\"width:14mm\">2nd TIN not.</td>\n      <td class=\"tbs rbs lbs bbs\" >13 Excess golden parachute payments<br>$___________</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">14 Gross proceeds paid to an<br>attorney<br>$___________</td>\n    </tr>\n    <tr style=\"height:12mm\">\n      <td class=\"tbs rbs lbs \" >15a Section 409A deferrals</td>\n      <td class=\"tbs rbs lbs \" colspan=\"3\">15b Section 409 income</td>\n      <td class=\"tbs rbs lbs \" >16 State tax withheld</td>\n      <td class=\"tbs rbs lbs \" colspan=\"2\">17 State/Payer's state no.</td>\n      <td class=\"tbs lbs\" >18 State income</td>\n    </tr>\n    <tr>\n      <td class=\"lbs rbs bbs\">$</td>\n      <td class=\"lbs rbs bbs\" colspan=\"3\">$</td>\n      <td class=\"lbs rbs bbs tbd\">$</td>\n      <td class=\"lbs rbs bbs tbd\" colspan=\"2\"></td>\n      <td class=\"lbs bbs tbd\">$</td>\n    </tr>\n\n    <tr style=\"height:8mm\">\n      <td class=\"tbs\" colspan=\"8\">Form 1099-MISC Cat. No. 14425J www.irs.gov/Form1099MISC Department of the Treasury - Internal Revenue Service</td>\n    </tr>\n\n  </tbody>\n</table>\n</div>\n<div id=\"copy_1\" style=\"position: relative; top:0cm; width:17cm;height:28.0cm;\">\n  <table>\n  <tbody>\n    <tr style=\"height:12mm\">\n      <td class=\"tbs rbs lbs bbs\" style=\"width:86mm\" colspan=\"4\"; rowspan=\"3\">PAYER'S name, street address, city or town, state or province, country, ZIP<br>or foreign postal code, and telephone no.<br>\n      {{company if company else \"\"}}<br>\n    \t{{payer_street_address if payer_street_address else \"\"}}</td>\n      <td class=\"tbs rbs lbs bbs\" style=\"width:35mm\">1 Rents</td>\n      <td class=\"tbs rbs lbs bbs\" style=\"width:25mm\" rowspan=\"2\">OMB No. 1545-0115<br><yone>20</yone><ytwo>18</ytwo><br>Form 1099-MISC</td>\n      <td class=\"lbs bbs\" style=\"width:38mm\" colspan=\"2\" rowspan=\"2\">Miscellaneous Income</td>\n    </tr>\n    <tr style=\"height:12mm\">\n      <td class=\"tbs rbs lbs bbs\" style=\"width:35mm\">2 Royalties</td>\n    </tr>\n    <tr style=\"height:9mm\">\n      <td class=\"tbs rbs lbs bbs\" >3 Other Income<br>\n\t{{payments if payments else \"\"}}\n\t</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">4 Federal Income tax withheld</td>\n      <td class=\"tbs lbs bbs\" style=\"width:29mm\" rowspan=\"2\">Copy 1<br>For State Tax<br>Department</td>\n    </tr>\n    <tr style=\"height:16mm\">\n      <td class=\"tbs rbs lbs bbs\" style=\"width:43mm\">PAYER'S TIN<br>\n\t{{company_tin if company_tin else \"\"}}\n\t</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=\"3\">RECIPIENT'S TIN<br>\n\t{{tax_id if tax_id else \"\"}}\n\t</td>\n      <td class=\"tbs rbs lbs bbs\" >Fishing boat proceeds</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">6 Medical and health care payments</td>\n    </tr>\n    <tr style=\"height:12mm\">\n      <td class=\"tbs rbs lbs bbs\" colspan=\"4\">RECIPIENT'S name</td>\n      {{supplier if supplier else \"\"}}\n      <td class=\"tbs rbs lbs bbs\" >7 Nonemployee compensation<br>\n\t</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">Substitute payments in lieu of dividends or interest</td>\n      <td class=\"tbs lbs bbs\" rowspan=\"6\"></td>\n    </tr>\n    <tr style=\"height:6mm\">\n      <td class=\"tbs rbs lbs bbs\" colspan=\"4\" rowspan=\"2\">Street address (including apt. no.)<br>\n\t{{recipient_street_address if recipient_street_address else \"\"}}\n\t</td>\n      <td class=\"tbs rbs lbs bbs\" >$___________</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">$___________</td>\n    </tr>\n    <tr style=\"height:7mm\">\n      <td class=\"tbs rbs lbs bbs\" rowspan=\"2\">9 Payer made direct sales of<br>$5,000 or more of consumer products<br>to a buyer<br>(recipient) for resale</td>\n      <td class=\"tbs rbs lbs\" colspan=\"2\">10 Crop insurance proceeds</td>\n    </tr>\n    <tr style=\"height:5mm\">\n      <td class=\"tbs rbs lbs bbs\" colspan=\"4\" rowspan=\"2\">City or town, state or province, country, and ZIP or foreign postal code<br>\n\t{{recipient_city_state if recipient_city_state else \"\"}}\n\t</td>\n      <td style=\"vertical-align:bottom\" class=\" rbs lbs bbs\" colspan=\"2\">$___________</td>\n    </tr>\n    <tr style=\"height:9mm\">\n      <td class=\"tbs rbs lbs bbs\" >11</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=2>12</td>\n    </tr>\n    <tr style=\"height:13mm\">\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">Account number (see instructions)</td>\n      <td class=\"tbs rbs lbs bbs\" style=\"width:16mm\">FACTA filing<br>requirement</td>\n      <td class=\"tbs rbs lbs bbs\" style=\"width:14mm\">2nd TIN not.</td>\n      <td class=\"tbs rbs lbs bbs\" >13 Excess golden parachute payments<br>$___________</td>\n      <td class=\"tbs rbs lbs bbs\" colspan=\"2\">14 Gross proceeds paid to an<br>attorney<br>$___________</td>\n    </tr>\n    <tr style=\"height:12mm\">\n      <td class=\"tbs rbs lbs \" >15a Section 409A deferrals</td>\n      <td class=\"tbs rbs lbs \" colspan=\"3\">15b Section 409 income</td>\n      <td class=\"tbs rbs lbs \" >16 State tax withheld</td>\n      <td class=\"tbs rbs lbs \" colspan=\"2\">17 State/Payer's state no.</td>\n      <td class=\"tbs lbs\" >18 State income</td>\n    </tr>\n    <tr>\n      <td class=\"lbs rbs bbs\">$</td>\n      <td class=\"lbs rbs bbs\" colspan=\"3\">$</td>\n      <td class=\"lbs rbs bbs tbd\">$</td>\n      <td class=\"lbs rbs bbs tbd\" colspan=\"2\"></td>\n      <td class=\"lbs bbs tbd\">$</td>\n    </tr>\n\n    <tr style=\"height:8mm\">\n      <td class=\"tbs\" colspan=\"8\">Form 1099-MISC Cat. No. 14425J www.irs.gov/Form1099MISC Department of the Treasury - Internal Revenue Service</td>\n    </tr>\n\n  </tbody>\n</table>\n</div>\n<style>\nbody {\n  font-family: 'Helvetica', sans-serif;\n  font-size: 5.66pt;\n}\nyone {\n  font-family: 'Helvetica', sans-serif;\n  font-size: 14pt;\n  color: black;\n  -webkit-text-fill-color: white; /* Will override color (regardless of order) */\n  -webkit-text-stroke-width: 1px;\n  -webkit-text-stroke-color: black;\n}\nytwo {\n  font-family: 'Helvetica', sans-serif;\n  font-size: 14pt;\n  color: black;\n  -webkit-text-stroke-width: 1px;\n  -webkit-text-stroke-color: black;\n}\n\ntable, th, td {\n font-family: 'Helvetica', sans-serif;\n  font-size: 5.66pt;\n    border: none;\n}\n\n.tbs {\n    border-top: 1px solid black;\n}\n\n.bbs {\n    border-bottom: 1px solid black;\n}\n.lbs {\n    border-left: 1px solid black;\n}\n.rbs {\n    border-right: 1px solid black;\n}\n.allBorder {\n    border-top: 1px solid black;\n    border-right: 1px solid black;\n    border-left: 1px solid black;\n    borter-bottom: 1px solid black;\n}\n.bottomBorderOnlyDashed {\n\tborder-bottom: 1px dashed black;\n}\n.tbd {\n\tborder-top: 1px dashed black;\n}\n.address {\n\tvertical-align: bottom;\n}\n</style>",
-	"line_breaks": 0,
-	"modified": "2018-10-08 14:56:56.912851",
-	"module": "Regional",
-	"name": "IRS 1099 Form",
-	"print_format_builder": 1,
-	"print_format_type": "Server",
-	"show_section_headings": 0,
-	"standard": "No"
- }
-]
+{
+ "align_labels_right": 0,
+ "creation": "2020-11-09 16:01:26.096002",
+ "css": "",
+ "custom_format": 1,
+ "default_print_language": "en",
+ "disabled": 0,
+ "doc_type": "Supplier",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div class=\\\"print-heading\\\">\\t\\t\\t\\t<h2>TAX Invoice<br><small>{{ doc.name }}</small>\\t\\t\\t\\t</h2></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"customer_name\", \"label\": \"Customer Name\"}, {\"print_hide\": 0, \"fieldname\": \"customer_name_in_arabic\", \"label\": \"Customer Name in Arabic\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"posting_date\", \"label\": \"Date\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Address\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company\", \"label\": \"Company\"}, {\"print_hide\": 0, \"fieldname\": \"company_trn\", \"label\": \"Company TRN\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company_address_display\", \"label\": \"Company Address\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"item_code\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"200px\"}, {\"print_hide\": 0, \"fieldname\": \"uom\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_code\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"items\", \"label\": \"Items\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"total\", \"label\": \"Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"charge_type\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"row_id\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"account_head\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"cost_center\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"300px\"}, {\"print_hide\": 0, \"fieldname\": \"rate\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"total\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"tax_amount_after_discount_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_tax_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_total\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"base_tax_amount_after_discount_amount\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"item_wise_tax_detail\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"taxes\", \"label\": \"Sales Taxes and Charges\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"grand_total\", \"label\": \"Grand Total\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\", \"label\": \"Rounded Total\"}, {\"print_hide\": 0, \"fieldname\": \"in_words\", \"align\": \"left\", \"label\": \"In Words\"}]",
+ "html": "<div id=\"copy_a\" style=\"position: relative; top:0cm; width:17cm;height:28.0cm;\">\n  <table>\n    <tbody>\n      <tr style=\"height:12mm\">\n        <td class=\"tbs rbs lbs bbs\" style=\"width:86mm\" colspan=\"4\" ; rowspan=\"3\">PAYER'S name, street address,\n          city or town, state or province, country, ZIP<br>or foreign postal code, and telephone no.<br>\n          {{ company or \"\" }}<br>\n          {{ payer_street_address or \"\" }}\n        </td>\n        <td class=\"tbs rbs lbs bbs\" style=\"width:35mm\">1 Rents</td>\n        <td class=\"tbs rbs lbs bbs\" style=\"width:25mm\" rowspan=\"2\">OMB No. 1545-0115<br>\n          <yone>{{ fiscal_year[:2] }}</yone>\n          <ytwo>{{ fiscal_year[-2:] }}</ytwo><br>Form 1099-MISC\n        </td>\n        <td class=\"lbs bbs\" style=\"width:38mm\" colspan=\"2\" rowspan=\"2\">Miscellaneous Income</td>\n      </tr>\n      <tr style=\"height:12mm\">\n        <td class=\"tbs rbs lbs bbs\" style=\"width:35mm\">2 Royalties</td>\n      </tr>\n      <tr style=\"height:9mm\">\n        <td class=\"tbs rbs lbs bbs\">3 Other Income<br>{{ payments or \"\" }}</td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">4 Federal Income tax withheld</td>\n        <td class=\"tbs lbs bbs\" style=\"width:29mm\" rowspan=\"2\">Copy A<br>For<br>Internal Revenue<br>Service\n          Center<br><br>File with Form 1096</td>\n      </tr>\n      <tr style=\"height:16mm\">\n        <td class=\"tbs rbs lbs bbs\" style=\"width:43mm\">PAYER'S TIN<br>{{ company_tin or \"\" }}</td>\n\n        <td class=\"tbs rbs lbs bbs\" colspan=\"3\">RECIPIENT'S TIN<br><br>{{ tax_id or \"None\" }}</td>\n        <td class=\"tbs rbs lbs bbs\">Fishing boat proceeds</td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">6 Medical and health care payments</td>\n      </tr>\n      <tr style=\"height:12mm\">\n        <td class=\"tbs rbs lbs bbs\" colspan=\"4\">RECIPIENT'S name <br>{{ supplier or \"\" }}</td>\n        <td class=\"tbs rbs lbs bbs\">7 Nonemployee compensation<br>\n        </td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">Substitute payments in lieu of dividends or interest</td>\n        <td class=\"tbs lbs bbs\" rowspan=\"6\">For Privacy Act<br>and Paperwork<br>Reduction Act<br>Notice, see\n          the<br>2018 General<br>Instructions for<br>Certain<br>Information<br>Returns.</td>\n      </tr>\n      <tr style=\"height:6mm\">\n        <td class=\"tbs rbs lbs bbs\" colspan=\"4\" rowspan=\"2\">Street address (including apt. no.)<br>\n          {{ recipient_street_address or \"\" }}\n        </td>\n        <td class=\"tbs rbs lbs bbs\">$___________</td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">$___________</td>\n      </tr>\n      <tr style=\"height:7mm\">\n        <td class=\"tbs rbs lbs bbs\" rowspan=\"2\">9 Payer made direct sales of<br>$5,000 or more of consumer\n          products<br>to a buyer<br>(recipient) for resale</td>\n        <td class=\"tbs rbs lbs\" colspan=\"2\">10 Crop insurance proceeds</td>\n      </tr>\n      <tr style=\"height:5mm\">\n        <td class=\"tbs rbs lbs bbs\" colspan=\"4\" rowspan=\"2\">City or town, state or province, country, and ZIP or\n          foreign postal code<br>\n          {{ recipient_city_state or \"\" }}\n        </td>\n        <td style=\"vertical-align:bottom\" class=\" rbs lbs bbs\" colspan=\"2\">$___________</td>\n      </tr>\n      <tr style=\"height:9mm\">\n        <td class=\"tbs rbs lbs bbs\">11</td>\n        <td class=\"tbs rbs lbs bbs\" colspan=2>12</td>\n      </tr>\n      <tr style=\"height:13mm\">\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">Account number (see instructions)</td>\n        <td class=\"tbs rbs lbs bbs\" style=\"width:16mm\">FACTA filing<br>requirement</td>\n        <td class=\"tbs rbs lbs bbs\" style=\"width:14mm\">2nd TIN not.</td>\n        <td class=\"tbs rbs lbs bbs\">13 Excess golden parachute payments<br>$___________</td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">14 Gross proceeds paid to an<br>attorney<br>$___________</td>\n      </tr>\n      <tr style=\"height:12mm\">\n        <td class=\"tbs rbs lbs \">15a Section 409A deferrals</td>\n        <td class=\"tbs rbs lbs \" colspan=\"3\">15b Section 409 income</td>\n        <td class=\"tbs rbs lbs \">16 State tax withheld</td>\n        <td class=\"tbs rbs lbs \" colspan=\"2\">17 State/Payer's state no.</td>\n        <td class=\"tbs lbs\">18 State income</td>\n      </tr>\n      <tr>\n        <td class=\"lbs rbs bbs\">$</td>\n        <td class=\"lbs rbs bbs\" colspan=\"3\">$</td>\n        <td class=\"lbs rbs bbs tbd\">$</td>\n        <td class=\"lbs rbs bbs tbd\" colspan=\"2\"></td>\n        <td class=\"lbs bbs tbd\">$</td>\n      </tr>\n\n      <tr style=\"height:8mm\">\n        <td class=\"tbs\" colspan=\"8\">Form 1099-MISC Cat. No. 14425J www.irs.gov/Form1099MISC Department of the\n          Treasury - Internal Revenue Service</td>\n      </tr>\n\n    </tbody>\n  </table>\n</div>\n<div id=\"copy_1\" style=\"position: relative; top:0cm; width:17cm;height:28.0cm;\">\n  <table>\n    <tbody>\n      <tr style=\"height:12mm\">\n        <td class=\"tbs rbs lbs bbs\" style=\"width:86mm\" colspan=\"4\" ; rowspan=\"3\">PAYER'S name, street address,\n          city or town, state or province, country, ZIP<br>or foreign postal code, and telephone no.<br>\n          {{ company or \"\"}}<b r>\n          {{ payer_street_address or \"\" }}\n        </td>\n        <td class=\"tbs rbs lbs bbs\" style=\"width:35mm\">1 Rents</td>\n        <td class=\"tbs rbs lbs bbs\" style=\"width:25mm\" rowspan=\"2\">OMB No. 1545-0115<br>\n          <yone>{{ fiscal_year[:2] }}</yone>\n          <ytwo>{{ fiscal_year[-2:] }}</ytwo><br>Form 1099-MISC\n        </td>\n        <td class=\"lbs bbs\" style=\"width:38mm\" colspan=\"2\" rowspan=\"2\">Miscellaneous Income</td>\n      </tr>\n      <tr style=\"height:12mm\">\n        <td class=\"tbs rbs lbs bbs\" style=\"width:35mm\">2 Royalties</td>\n      </tr>\n      <tr style=\"height:9mm\">\n        <td class=\"tbs rbs lbs bbs\">3 Other Income<br>\n          {{ payments or \"\" }}\n        </td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">4 Federal Income tax withheld</td>\n        <td class=\"tbs lbs bbs\" style=\"width:29mm\" rowspan=\"2\">Copy 1<br>For State Tax<br>Department</td>\n      </tr>\n      <tr style=\"height:16mm\">\n        <td class=\"tbs rbs lbs bbs\" style=\"width:43mm\">PAYER'S TIN<br>\n          {{ company_tin or \"\" }}\n        </td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"3\">RECIPIENT'S TIN<br>\n          {{ tax_id or \"\" }}\n        </td>\n        <td class=\"tbs rbs lbs bbs\">Fishing boat proceeds</td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">6 Medical and health care payments</td>\n      </tr>\n      <tr style=\"height:12mm\">\n        <td class=\"tbs rbs lbs bbs\" colspan=\"4\">RECIPIENT'S name</td>\n        {{ supplier or \"\" }}\n        <td class=\"tbs rbs lbs bbs\">7 Nonemployee compensation<br>\n        </td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">Substitute payments in lieu of dividends or interest</td>\n        <td class=\"tbs lbs bbs\" rowspan=\"6\"></td>\n      </tr>\n      <tr style=\"height:6mm\">\n        <td class=\"tbs rbs lbs bbs\" colspan=\"4\" rowspan=\"2\">Street address (including apt. no.)<br>\n          {{ recipient_street_address or \"\" }}\n        </td>\n        <td class=\"tbs rbs lbs bbs\">$___________</td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">$___________</td>\n      </tr>\n      <tr style=\"height:7mm\">\n        <td class=\"tbs rbs lbs bbs\" rowspan=\"2\">9 Payer made direct sales of<br>$5,000 or more of consumer\n          products<br>to a buyer<br>(recipient) for resale</td>\n        <td class=\"tbs rbs lbs\" colspan=\"2\">10 Crop insurance proceeds</td>\n      </tr>\n      <tr style=\"height:5mm\">\n        <td class=\"tbs rbs lbs bbs\" colspan=\"4\" rowspan=\"2\">City or town, state or province, country, and ZIP or\n          foreign postal code<br>\n          {{ recipient_city_state or \"\" }}\n        </td>\n        <td style=\"vertical-align:bottom\" class=\" rbs lbs bbs\" colspan=\"2\">$___________</td>\n      </tr>\n      <tr style=\"height:9mm\">\n        <td class=\"tbs rbs lbs bbs\">11</td>\n        <td class=\"tbs rbs lbs bbs\" colspan=2>12</td>\n      </tr>\n      <tr style=\"height:13mm\">\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">Account number (see instructions)</td>\n        <td class=\"tbs rbs lbs bbs\" style=\"width:16mm\">FACTA filing<br>requirement</td>\n        <td class=\"tbs rbs lbs bbs\" style=\"width:14mm\">2nd TIN not.</td>\n        <td class=\"tbs rbs lbs bbs\">13 Excess golden parachute payments<br>$___________</td>\n        <td class=\"tbs rbs lbs bbs\" colspan=\"2\">14 Gross proceeds paid to an<br>attorney<br>$___________</td>\n      </tr>\n      <tr style=\"height:12mm\">\n        <td class=\"tbs rbs lbs \">15a Section 409A deferrals</td>\n        <td class=\"tbs rbs lbs \" colspan=\"3\">15b Section 409 income</td>\n        <td class=\"tbs rbs lbs \">16 State tax withheld</td>\n        <td class=\"tbs rbs lbs \" colspan=\"2\">17 State/Payer's state no.</td>\n        <td class=\"tbs lbs\">18 State income</td>\n      </tr>\n      <tr>\n        <td class=\"lbs rbs bbs\">$</td>\n        <td class=\"lbs rbs bbs\" colspan=\"3\">$</td>\n        <td class=\"lbs rbs bbs tbd\">$</td>\n        <td class=\"lbs rbs bbs tbd\" colspan=\"2\"></td>\n        <td class=\"lbs bbs tbd\">$</td>\n      </tr>\n\n      <tr style=\"height:8mm\">\n        <td class=\"tbs\" colspan=\"8\">Form 1099-MISC Cat. No. 14425J www.irs.gov/Form1099MISC Department of the\n          Treasury - Internal Revenue Service</td>\n      </tr>\n\n    </tbody>\n  </table>\n</div>\n<style>\n  body {\n    font-family: 'Helvetica', sans-serif;\n    font-size: 5.66pt;\n  }\n\n  yone {\n    font-family: 'Helvetica', sans-serif;\n    font-size: 14pt;\n    color: black;\n    -webkit-text-fill-color: white;\n    /* Will override color (regardless of order) */\n    -webkit-text-stroke-width: 1px;\n    -webkit-text-stroke-color: black;\n  }\n\n  ytwo {\n    font-family: 'Helvetica', sans-serif;\n    font-size: 14pt;\n    color: black;\n    -webkit-text-stroke-width: 1px;\n    -webkit-text-stroke-color: black;\n  }\n\n  table,\n  th,\n  td {\n    font-family: 'Helvetica', sans-serif;\n    font-size: 5.66pt;\n    border: none;\n  }\n\n  .tbs {\n    border-top: 1px solid black;\n  }\n\n  .bbs {\n    border-bottom: 1px solid black;\n  }\n\n  .lbs {\n    border-left: 1px solid black;\n  }\n\n  .rbs {\n    border-right: 1px solid black;\n  }\n\n  .allBorder {\n    border-top: 1px solid black;\n    border-right: 1px solid black;\n    border-left: 1px solid black;\n    border-bottom: 1px solid black;\n  }\n\n  .bottomBorderOnlyDashed {\n    border-bottom: 1px dashed black;\n  }\n\n  .tbd {\n    border-top: 1px dashed black;\n  }\n\n  .address {\n    vertical-align: bottom;\n  }\n</style>",
+ "idx": 0,
+ "line_breaks": 0,
+ "modified": "2021-01-19 07:25:16.333666",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "IRS 1099 Form",
+ "owner": "Administrator",
+ "print_format_builder": 1,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 0,
+ "standard": "No"
+}
\ No newline at end of file
diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py
index 1e39c57..cbc9478 100644
--- a/erpnext/regional/report/datev/datev.py
+++ b/erpnext/regional/report/datev/datev.py
@@ -96,6 +96,8 @@
 	"""Entry point for frappe."""
 	data = []
 	if filters and validate(filters):
+		fn = 'temporary_against_account_number'
+		filters[fn] = frappe.get_value('DATEV Settings', filters.get('company'), fn)
 		data = get_transactions(filters, as_dict=0)
 
 	return COLUMNS, data
@@ -156,11 +158,11 @@
 			case gl.debit when 0 then 'H' else 'S' end as 'Soll/Haben-Kennzeichen',
 
 			/* account number or, if empty, party account number */
-			coalesce(acc.account_number, acc_pa.account_number) as 'Konto',
+			acc.account_number as 'Konto',
 
 			/* against number or, if empty, party against number */
-			coalesce(acc_against.account_number, acc_against_pa.account_number) as 'Gegenkonto (ohne BU-Schlüssel)',
-			
+			%(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)',
+
 			gl.posting_date as 'Belegdatum',
 			gl.voucher_no as 'Belegfeld 1',
 			LEFT(gl.remarks, 60) as 'Buchungstext',
@@ -171,27 +173,10 @@
 
 		FROM `tabGL Entry` gl
 
-			/* Statistisches Konto (Debitoren/Kreditoren) */
-			left join `tabParty Account` pa
-			on gl.against = pa.parent
-			and gl.company = pa.company
-
 			/* Kontonummer */
 			left join `tabAccount` acc 
 			on gl.account = acc.name
 
-			/* Gegenkonto-Nummer */
-			left join `tabAccount` acc_against 
-			on gl.against = acc_against.name
-
-			/* Statistische Kontonummer */
-			left join `tabAccount` acc_pa
-			on pa.account = acc_pa.name
-
-			/* Statistische Gegenkonto-Nummer */
-			left join `tabAccount` acc_against_pa 
-			on pa.account = acc_against_pa.name
-
 		WHERE gl.company = %(company)s 
 		AND DATE(gl.posting_date) >= %(from_date)s
 		AND DATE(gl.posting_date) <= %(to_date)s
@@ -347,7 +332,9 @@
 	coa = frappe.get_value('Company', company, 'chart_of_accounts')
 	filters['skr'] = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '')
 
-	filters['account_number_length'] = frappe.get_value('DATEV Settings', company, 'account_number_length')
+	datev_settings = frappe.get_doc('DATEV Settings', company)
+	filters['account_number_length'] = datev_settings.account_number_length
+	filters['temporary_against_account_number'] = datev_settings.temporary_against_account_number
 
 	transactions = get_transactions(filters)
 	account_names = get_account_names(filters)
diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py
index 9529923..59b878e 100644
--- a/erpnext/regional/report/datev/test_datev.py
+++ b/erpnext/regional/report/datev/test_datev.py
@@ -126,7 +126,8 @@
 			"doctype": "DATEV Settings",
 			"client": company.name,
 			"client_number": "12345",
-			"consultant_number": "67890"
+			"consultant_number": "67890",
+			"temporary_against_account_number": "9999"
 		}).insert()
 
 
@@ -137,7 +138,8 @@
 		self.filters = {
 			"company": self.company.name,
 			"from_date": today(),
-			"to_date": today()
+			"to_date": today(),
+			"temporary_against_account_number": "9999"
 		}
 
 		make_datev_settings(self.company)
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index ad3de5f..96dc3f7 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -255,15 +255,16 @@
 
 						for item_code, tax_amounts in item_wise_tax_detail.items():
 							tax_rate = tax_amounts[0]
-							if cgst_or_sgst:
-								tax_rate *= 2
-								if parent not in self.cgst_sgst_invoices:
-									self.cgst_sgst_invoices.append(parent)
+							if tax_rate:
+								if cgst_or_sgst:
+									tax_rate *= 2
+									if parent not in self.cgst_sgst_invoices:
+										self.cgst_sgst_invoices.append(parent)
 
-							rate_based_dict = self.items_based_on_tax_rate\
-								.setdefault(parent, {}).setdefault(tax_rate, [])
-							if item_code not in rate_based_dict:
-								rate_based_dict.append(item_code)
+								rate_based_dict = self.items_based_on_tax_rate\
+									.setdefault(parent, {}).setdefault(tax_rate, [])
+								if item_code not in rate_based_dict:
+									rate_based_dict.append(item_code)
 					except ValueError:
 						continue
 		if unidentified_gst_accounts:
diff --git a/erpnext/regional/report/irs_1099/irs_1099.js b/erpnext/regional/report/irs_1099/irs_1099.js
index 2d74652..070ff43 100644
--- a/erpnext/regional/report/irs_1099/irs_1099.js
+++ b/erpnext/regional/report/irs_1099/irs_1099.js
@@ -4,7 +4,7 @@
 frappe.query_reports["IRS 1099"] = {
 	"filters": [
 		{
-			"fieldname":"company",
+			"fieldname": "company",
 			"label": __("Company"),
 			"fieldtype": "Link",
 			"options": "Company",
@@ -13,7 +13,7 @@
 			"width": 80,
 		},
 		{
-			"fieldname":"fiscal_year",
+			"fieldname": "fiscal_year",
 			"label": __("Fiscal Year"),
 			"fieldtype": "Link",
 			"options": "Fiscal Year",
@@ -22,7 +22,7 @@
 			"width": 80,
 		},
 		{
-			"fieldname":"supplier_group",
+			"fieldname": "supplier_group",
 			"label": __("Supplier Group"),
 			"fieldtype": "Link",
 			"options": "Supplier Group",
@@ -32,16 +32,16 @@
 		},
 	],
 
-	onload: function(query_report) {
+	onload: function (query_report) {
 		query_report.page.add_inner_button(__("Print IRS 1099 Forms"), () => {
 			build_1099_print(query_report);
 		});
 	}
 };
 
-function build_1099_print(query_report){
+function build_1099_print(query_report) {
 	let filters = JSON.stringify(query_report.get_values());
 	let w = window.open('/api/method/erpnext.regional.report.irs_1099.irs_1099.irs_1099_print?' +
-								'&filters=' + encodeURIComponent(filters));
+		'&filters=' + encodeURIComponent(filters));
 	// w.print();
 }
diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py
index d3509e5..c1c8aed 100644
--- a/erpnext/regional/report/irs_1099/irs_1099.py
+++ b/erpnext/regional/report/irs_1099/irs_1099.py
@@ -1,29 +1,34 @@
 # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
-from __future__ import unicode_literals
-import frappe
 import json
-from frappe import _, _dict
-from frappe.utils import nowdate
-from frappe.utils.data import fmt_money
-from erpnext.accounts.utils import get_fiscal_year
+
 from PyPDF2 import PdfFileWriter
+
+import frappe
+from erpnext.accounts.utils import get_fiscal_year
+from frappe import _
+from frappe.utils import cstr, nowdate
+from frappe.utils.data import fmt_money
+from frappe.utils.jinja import render_template
 from frappe.utils.pdf import get_pdf
 from frappe.utils.print_format import read_multi_pdf
-from frappe.utils.jinja import render_template
+
+IRS_1099_FORMS_FILE_EXTENSION = ".pdf"
 
 
 def execute(filters=None):
-	filters = filters if isinstance(filters, _dict) else _dict(filters)
-
+	filters = filters if isinstance(filters, frappe._dict) else frappe._dict(filters)
 	if not filters:
 		filters.setdefault('fiscal_year', get_fiscal_year(nowdate())[0])
 		filters.setdefault('company', frappe.db.get_default("company"))
 
-	region = frappe.db.get_value("Company", fieldname = ["country"], filters = { "name": filters.company })
+	region = frappe.db.get_value("Company",
+		filters={"name": filters.company},
+		fieldname=["country"])
+
 	if region != 'United States':
-		return [],[]
+		return [], []
 
 	data = []
 	columns = get_columns()
@@ -34,20 +39,23 @@
 			s.tax_id as "tax_id",
 			SUM(gl.debit_in_account_currency) AS "payments"
 		FROM
-			`tabGL Entry` gl INNER JOIN `tabSupplier` s
+			`tabGL Entry` gl
+				INNER JOIN `tabSupplier` s
 		WHERE
 			s.name = gl.party
-		AND	s.irs_1099 = 1
-		AND gl.fiscal_year = %(fiscal_year)s
-		AND gl.party_type = "Supplier"
-
+				AND s.irs_1099 = 1
+				AND gl.fiscal_year = %(fiscal_year)s
+				AND gl.party_type = "Supplier"
 		GROUP BY
 			gl.party
-
 		ORDER BY
-			gl.party DESC""", {"fiscal_year": filters.fiscal_year,
+			gl.party DESC
+	""", {
+		"fiscal_year": filters.fiscal_year,
 		"supplier_group": filters.supplier_group,
-		"company": filters.company}, as_dict=True)
+		"company": filters.company
+	}, as_dict=True)
+
 	return columns, data
 
 
@@ -74,7 +82,6 @@
 			"width": 120
 		},
 		{
-
 			"fieldname": "payments",
 			"label": _("Total Payments"),
 			"fieldtype": "Currency",
@@ -88,23 +95,32 @@
 	if not filters:
 		frappe._dict({
 			"company": frappe.db.get_default("Company"),
-			"fiscal_year": frappe.db.get_default("fiscal_year")})
+			"fiscal_year": frappe.db.get_default("Fiscal Year")
+		})
 	else:
 		filters = frappe._dict(json.loads(filters))
+
+	fiscal_year_doc = get_fiscal_year(fiscal_year=filters.fiscal_year, as_dict=True)
+	fiscal_year = cstr(fiscal_year_doc.year_start_date.year)
+
 	company_address = get_payer_address_html(filters.company)
 	company_tin = frappe.db.get_value("Company", filters.company, "tax_id")
+
 	columns, data = execute(filters)
 	template = frappe.get_doc("Print Format", "IRS 1099 Form").html
 	output = PdfFileWriter()
+
 	for row in data:
+		row["fiscal_year"] = fiscal_year
 		row["company"] = filters.company
 		row["company_tin"] = company_tin
 		row["payer_street_address"] = company_address
-		row["recipient_street_address"], row["recipient_city_state"] = get_street_address_html("Supplier", row.supplier)
+		row["recipient_street_address"], row["recipient_city_state"] = get_street_address_html(
+			"Supplier", row.supplier)
 		row["payments"] = fmt_money(row["payments"], precision=0, currency="USD")
-		frappe._dict(row)
 		pdf = get_pdf(render_template(template, row), output=output if output else None)
-	frappe.local.response.filename = filters.fiscal_year + " " + filters.company + " IRS 1099 Forms"
+
+	frappe.local.response.filename = f"{filters.fiscal_year} {filters.company} IRS 1099 Forms{IRS_1099_FORMS_FILE_EXTENSION}"
 	frappe.local.response.filecontent = read_multi_pdf(output)
 	frappe.local.response.type = "download"
 
@@ -120,36 +136,45 @@
 		ORDER BY
 			address_type="Postal" DESC, address_type="Billing" DESC
 		LIMIT 1
-		""", {"company": company}, as_dict=True)
+	""", {"company": company}, as_dict=True)
+
+	address_display = ""
 	if address_list:
 		company_address = address_list[0]["name"]
-		return frappe.get_doc("Address", company_address).get_display()
-	else:
-		return ""
+		address_display = frappe.get_doc("Address", company_address).get_display()
+
+	return address_display
 
 
 def get_street_address_html(party_type, party):
 	address_list = frappe.db.sql("""
 		SELECT
 			link.parent
-		FROM `tabDynamic Link` link, `tabAddress` address
-		WHERE link.parenttype = "Address"
-		AND link.link_name = %(party)s
-		ORDER BY address.address_type="Postal" DESC,
+		FROM
+			`tabDynamic Link` link,
+			`tabAddress` address
+		WHERE
+			link.parenttype = "Address"
+				AND link.link_name = %(party)s
+		ORDER BY
+			address.address_type="Postal" DESC,
 			address.address_type="Billing" DESC
 		LIMIT 1
-		""", {"party": party}, as_dict=True)
+	""", {"party": party}, as_dict=True)
+
+	street_address = city_state = ""
 	if address_list:
 		supplier_address = address_list[0]["parent"]
 		doc = frappe.get_doc("Address", supplier_address)
+
 		if doc.address_line2:
-			street = doc.address_line1 + "<br>\n" + doc.address_line2 + "<br>\n"
+			street_address = doc.address_line1 + "<br>\n" + doc.address_line2 + "<br>\n"
 		else:
-			street = doc.address_line1 + "<br>\n"
-		city = doc.city + ", " if doc.city else ""
-		city = city + doc.state + " " if doc.state else city
-		city = city + doc.pincode if doc.pincode else city
-		city += "<br>\n"
-		return street, city
-	else:
-		return "", ""
+			street_address = doc.address_line1 + "<br>\n"
+
+		city_state = doc.city + ", " if doc.city else ""
+		city_state = city_state + doc.state + " " if doc.state else city_state
+		city_state = city_state + doc.pincode if doc.pincode else city_state
+		city_state += "<br>\n"
+
+	return street_address, city_state
diff --git a/erpnext/regional/united_states/test_united_states.py b/erpnext/regional/united_states/test_united_states.py
index ad95010..513570e 100644
--- a/erpnext/regional/united_states/test_united_states.py
+++ b/erpnext/regional/united_states/test_united_states.py
@@ -26,7 +26,6 @@
         make_payment_entry_to_irs_1099_supplier()
         filters = frappe._dict({"fiscal_year": "_Test Fiscal Year 2016", "company": "_Test Company 1"})
         columns, data = execute_1099_report(filters)
-        print(columns, data)
         expected_row = {'supplier': '_US 1099 Test Supplier',
                         'supplier_group': 'Services',
                         'payments': 100.0,
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 29214ee..bf8b7fc 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -84,7 +84,10 @@
 				frappe.throw(_("{0} is not a company bank account").format(frappe.bold(self.default_bank_account)))
 
 	def validate_internal_customer(self):
-		if self.is_internal_customer and frappe.db.get_value('Customer', {"represents_company": self.represents_company}, "name"):
+		internal_customer = frappe.db.get_value("Customer",
+			{"is_internal_customer": 1, "represents_company": self.represents_company, "name": ("!=", self.name)}, "name")
+
+		if internal_customer:
 			frappe.throw(_("Internal Customer for company {0} already exists").format(
 				frappe.bold(self.represents_company)))
 
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 661e107..5a0d9c9 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -7,7 +7,7 @@
 frappe.ui.form.on('Quotation', {
 	setup: function(frm) {
 		frm.custom_make_buttons = {
-			'Sales Order': 'Make Sales Order'
+			'Sales Order': 'Sales Order'
 		},
 
 		frm.set_query("quotation_to", function() {
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 20ae19f..36b584d 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -25,7 +25,6 @@
 	def validate(self):
 		super(Quotation, self).validate()
 		self.set_status()
-		self.update_opportunity()
 		self.validate_uom_is_integer("stock_uom", "qty")
 		self.validate_valid_till()
 		self.set_customer_name()
@@ -50,21 +49,20 @@
 			lead_name, company_name = frappe.db.get_value("Lead", self.party_name, ["lead_name", "company_name"])
 			self.customer_name = company_name or lead_name
 
-	def update_opportunity(self):
+	def update_opportunity(self, status):
 		for opportunity in list(set([d.prevdoc_docname for d in self.get("items")])):
 			if opportunity:
-				self.update_opportunity_status(opportunity)
+				self.update_opportunity_status(status, opportunity)
 
 		if self.opportunity:
-			self.update_opportunity_status()
+			self.update_opportunity_status(status)
 
-	def update_opportunity_status(self, opportunity=None):
+	def update_opportunity_status(self, status, opportunity=None):
 		if not opportunity:
 			opportunity = self.opportunity
 
 		opp = frappe.get_doc("Opportunity", opportunity)
-		opp.status = None
-		opp.set_status(update=True)
+		opp.set_status(status=status, update=True)
 
 	def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
 		if not self.has_sales_order():
@@ -82,7 +80,7 @@
 				else:
 					frappe.throw(_("Invalid lost reason {0}, please create a new lost reason").format(frappe.bold(reason.get('lost_reason'))))
 
-			self.update_opportunity()
+			self.update_opportunity('Lost')
 			self.update_lead()
 			self.save()
 
@@ -95,7 +93,7 @@
 			self.company, self.base_grand_total, self)
 
 		#update enquiry status
-		self.update_opportunity()
+		self.update_opportunity('Quotation')
 		self.update_lead()
 
 	def on_cancel(self):
@@ -105,7 +103,7 @@
 
 		#update enquiry status
 		self.set_status(update=True)
-		self.update_opportunity()
+		self.update_opportunity('Open')
 		self.update_lead()
 
 	def print_other_charges(self,docname):
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index d4fb07c..78f9df9 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -171,8 +171,10 @@
 						this.frm.add_custom_button(__('Request for Raw Materials'), () => this.make_raw_material_request(), __('Create'));
 					}
 
-					// make purchase order
+					// Make Purchase Order
+					if (!this.frm.doc.is_internal_customer) {
 						this.frm.add_custom_button(__('Purchase Order'), () => this.make_purchase_order(), __('Create'));
+					}
 
 					// maintenance
 					if(flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) {
@@ -193,16 +195,15 @@
 
 					if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
 						let me = this;
-						frappe.model.with_doc("Customer", me.frm.doc.customer, () => {
-							let customer = frappe.model.get_doc("Customer", me.frm.doc.customer);
-							let internal = customer.is_internal_customer;
-							let disabled = customer.disabled;
-							if (internal === 1 && disabled === 0) {
-								me.frm.add_custom_button("Inter Company Order", function() {
-									me.make_inter_company_order();
-								}, __('Create'));
-							}
-						});
+						let internal = me.frm.doc.is_internal_customer;
+						if (internal) {
+							let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Order" :
+								"Inter Company Purchase Order";
+
+							me.frm.add_custom_button(button_label, function() {
+								me.make_inter_company_order();
+							}, __('Create'));
+						}
 					}
 				}
 				// payment request
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 3d64ac3..0a5c665 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -107,6 +107,8 @@
   "tc_name",
   "terms",
   "more_info",
+  "is_internal_customer",
+  "represents_company",
   "inter_company_order_reference",
   "project",
   "party_account_currency",
@@ -1103,7 +1105,8 @@
    "hide_days": 1,
    "hide_seconds": 1,
    "label": "Inter Company Order Reference",
-   "options": "Purchase Order"
+   "options": "Purchase Order",
+   "read_only": 1
   },
   {
    "description": "Track this Sales Order against any Project",
@@ -1455,13 +1458,29 @@
    "hide_seconds": 1,
    "label": "Skip Delivery Note",
    "print_hide": 1
+  },
+  {
+   "default": "0",
+   "fetch_from": "customer.is_internal_customer",
+   "fieldname": "is_internal_customer",
+   "fieldtype": "Check",
+   "label": "Is Internal Customer",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "customer.represents_company",
+   "fieldname": "represents_company",
+   "fieldtype": "Link",
+   "label": "Represents Company",
+   "options": "Company",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-30 13:59:18.628077",
+ "modified": "2021-01-20 23:40:39.929296",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 04d85e5..1516dd6 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -14,7 +14,6 @@
 from frappe.desk.notifications import clear_doctype_notifications
 from frappe.contacts.doctype.address.address import get_company_address
 from erpnext.controllers.selling_controller import SellingController
-from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
 from erpnext.selling.doctype.customer.customer import check_credit_limit
 from erpnext.stock.doctype.item.item import get_item_defaults
 from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
@@ -159,7 +158,6 @@
 					frappe.throw(_("Quotation {0} is cancelled").format(quotation))
 
 				doc.set_status(update=True)
-				doc.update_opportunity()
 
 	def validate_drop_ship(self):
 		for d in self.get('items'):
@@ -418,8 +416,7 @@
 	def on_recurring(self, reference_doc, auto_repeat_doc):
 
 		def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
-			delivery_date = get_next_schedule_date(ref_doc_delivery_date,
-				auto_repeat_doc.frequency, auto_repeat_doc.start_date, cint(auto_repeat_doc.repeat_on_day))
+			delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
 
 			if delivery_date <= transaction_date:
 				delivery_date_diff = frappe.utils.date_diff(ref_doc_delivery_date, red_doc_transaction_date)
@@ -832,56 +829,49 @@
 		frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order."))
 
 	for supplier in suppliers:
-		po = frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")})
-		if len(po) == 0:
-			doc = get_mapped_doc("Sales Order", source_name, {
-				"Sales Order": {
-					"doctype": "Purchase Order",
-					"field_no_map": [
-						"address_display",
-						"contact_display",
-						"contact_mobile",
-						"contact_email",
-						"contact_person",
-						"taxes_and_charges",
-						"shipping_address",
-						"terms"
-					],
-					"validation": {
-						"docstatus": ["=", 1]
-					}
-				},
-				"Sales Order Item": {
-					"doctype": "Purchase Order Item",
-					"field_map":  [
-						["name", "sales_order_item"],
-						["parent", "sales_order"],
-						["stock_uom", "stock_uom"],
-						["uom", "uom"],
-						["conversion_factor", "conversion_factor"],
-						["delivery_date", "schedule_date"]
-			 		],
-					"field_no_map": [
-						"rate",
-						"price_list_rate",
-						"item_tax_template",
-						"discount_percentage",
-						"discount_amount",
-						"pricing_rules"
-					],
-					"postprocess": update_item,
-					"condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.supplier == supplier and doc.item_code in items_to_map
+		doc = get_mapped_doc("Sales Order", source_name, {
+			"Sales Order": {
+				"doctype": "Purchase Order",
+				"field_no_map": [
+					"address_display",
+					"contact_display",
+					"contact_mobile",
+					"contact_email",
+					"contact_person",
+					"taxes_and_charges",
+					"shipping_address",
+					"terms"
+				],
+				"validation": {
+					"docstatus": ["=", 1]
 				}
-			}, target_doc, set_missing_values)
+			},
+			"Sales Order Item": {
+				"doctype": "Purchase Order Item",
+				"field_map":  [
+					["name", "sales_order_item"],
+					["parent", "sales_order"],
+					["stock_uom", "stock_uom"],
+					["uom", "uom"],
+					["conversion_factor", "conversion_factor"],
+					["delivery_date", "schedule_date"]
+				],
+				"field_no_map": [
+					"rate",
+					"price_list_rate",
+					"item_tax_template",
+					"discount_percentage",
+					"discount_amount",
+					"pricing_rules"
+				],
+				"postprocess": update_item,
+				"condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.supplier == supplier and doc.item_code in items_to_map
+			}
+		}, target_doc, set_missing_values)
 
-			doc.insert()
-		else:
-			suppliers =[]
-	if suppliers:
+		doc.insert()
 		frappe.db.commit()
 		return doc
-	else:
-		frappe.msgprint(_("Purchase Order already created for all Sales Order items"))
 
 @frappe.whitelist()
 def make_purchase_order(source_name, selected_items=None, target_doc=None):
@@ -1096,4 +1086,4 @@
 
 	if not total_produced_qty and frappe.flags.in_patch: return
 
-	frappe.db.set_value('Sales Order Item', sales_order_item, 'produced_qty', total_produced_qty)
\ No newline at end of file
+	frappe.db.set_value('Sales Order Item', sales_order_item, 'produced_qty', total_produced_qty)
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 643e7cf..e259367 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -772,6 +772,59 @@
 		so.load_from_db()
 		so.cancel()
 
+	def test_drop_shipping_partial_order(self):
+		from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier, \
+			update_status as so_update_status
+
+		# make items
+		po_item1 = make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1})
+		po_item2 = make_item("_Test Item for Drop Shipping 2", {"is_stock_item": 1, "delivered_by_supplier": 1})
+
+		so_items = [
+			{
+				"item_code": po_item1.item_code,
+				"warehouse": "",
+				"qty": 2,
+				"rate": 400,
+				"delivered_by_supplier": 1,
+				"supplier": '_Test Supplier'
+			},
+			{
+				"item_code": po_item2.item_code,
+				"warehouse": "",
+				"qty": 2,
+				"rate": 400,
+				"delivered_by_supplier": 1,
+				"supplier": '_Test Supplier'
+			}
+		]
+
+		# create so and po
+		so = make_sales_order(item_list=so_items, do_not_submit=True)
+		so.submit()
+
+		# create po for only one item
+		po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
+		po1.submit()
+
+		self.assertEqual(so.customer, po1.customer)
+		self.assertEqual(po1.items[0].sales_order, so.name)
+		self.assertEqual(po1.items[0].item_code, po_item1.item_code)
+		#test po item length
+		self.assertEqual(len(po1.items), 1)
+
+		# create po for remaining item
+		po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])
+		po2.submit()
+
+		# teardown
+		so_update_status("Draft", so.name)
+
+		po1.cancel()
+		po2.cancel()
+		so.load_from_db()
+		so.cancel()
+
 	def test_reserved_qty_for_closing_so(self):
 		bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
 			fields=["reserved_qty"])
diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.js b/erpnext/selling/report/sales_analytics/sales_analytics.js
index 0e565a3..9089b53 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.js
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.js
@@ -74,67 +74,71 @@
 		return Object.assign(options, {
 			checkboxColumn: true,
 			events: {
-				onCheckRow: function(data) {
+				onCheckRow: function (data) {
+					if (!data) return;
+					const data_doctype = $(
+						data[2].html
+					)[0].attributes.getNamedItem("data-doctype").value;
+					const tree_type = frappe.query_report.filters[0].value;
+					if (data_doctype != tree_type) return;
+
 					row_name = data[2].content;
 					length = data.length;
 
-					var tree_type = frappe.query_report.filters[0].value;
-
-					if(tree_type == "Customer") {
-						row_values = data.slice(4,length-1).map(function (column) {
-							return column.content;
-						})
+					if (tree_type == "Customer") {
+						row_values = data
+							.slice(4, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
 					} else if (tree_type == "Item") {
-						row_values = data.slice(5,length-1).map(function (column) {
-							return column.content;
-						})
-					}
-					else {
-						row_values = data.slice(3,length-1).map(function (column) {
-							return column.content;
-						})
+						row_values = data
+							.slice(5, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
+					} else {
+						row_values = data
+							.slice(3, length - 1)
+							.map(function (column) {
+								return column.content;
+							});
 					}
 
 					entry = {
-						'name':row_name,
-						'values':row_values
-					}
+						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;
+					let element_found = new_datasets.some((element, index, array)=>{
+						if(element.name == row_name){
+							array.splice(index, 1)
+							return true
 						}
-					}
+						return false
+					})
 
-					if(!found){
+					if (!element_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)
+						datasets: new_datasets,
+					};
+					chart_options = {
+						data: new_data,
+						type: "line",
+					};
+					frappe.query_report.render_chart(chart_options);
 
 					frappe.query_report.raw_chart_data = new_data;
 				},
-			}
-		})
+			},
+		});
 	},
 }
 
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 7f00fca..ce08464 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -399,6 +399,10 @@
 			}
 	},
 
+	batch_no: function(doc, cdt, cdn) {
+		this._super(doc, cdt, cdn);
+	},
+
 	qty: function(doc, cdt, cdn) {
 		this._super(doc, cdt, cdn);
 
diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py
index 566f20c..7a72fe3 100644
--- a/erpnext/setup/doctype/company/delete_company_transactions.py
+++ b/erpnext/setup/doctype/company/delete_company_transactions.py
@@ -28,7 +28,7 @@
 			"Party Account", "Employee", "Sales Taxes and Charges Template",
 			"Purchase Taxes and Charges Template", "POS Profile", "BOM",
 			"Company", "Bank Account", "Item Tax Template", "Mode Of Payment",
-			"Item Default"):
+			"Item Default", "Customer", "Supplier"):
 				delete_for_doctype(doctype, company_name)
 
 	# reset company values
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index 21ecaa1..9f50aba 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -178,6 +178,13 @@
 	lead_doc.update(lead)
 	lead_doc.set('lead_owner', '')
 
+	if not frappe.db.exists('Lead Source', 'Product Inquiry'):
+		frappe.get_doc({
+			'doctype': 'Lead Source',
+			'source_name' : 'Product Inquiry'
+		}).insert(ignore_permissions=True)
+	lead_doc.set('source', 'Product Inquiry')
+
 	try:
 		lead_doc.save(ignore_permissions=True)
 	except frappe.exceptions.DuplicateEntryError:
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 9bd03d4..d3c442d 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -24,6 +24,16 @@
 			handle_move_add($(this), "Add")
 		});
 
+		this.content.on('click', '.btn-edit', function() {
+			let item = unescape($(this).attr('data-item'));
+			let warehouse = unescape($(this).attr('data-warehouse'));
+			let company = unescape($(this).attr('data-company'));
+			frappe.db.get_value('Putaway Rule',
+				{'item_code': item, 'warehouse': warehouse, 'company': company}, 'name', (r) => {
+					frappe.set_route("Form", "Putaway Rule", r.name);
+				});
+		});
+
 		function handle_move_add(element, action) {
 			let item = unescape(element.attr('data-item'));
 			let warehouse = unescape(element.attr('data-warehouse'));
@@ -59,7 +69,7 @@
 
 		// more
 		this.content.find('.btn-more').on('click', function() {
-			me.start += 20;
+			me.start += me.page_length;
 			me.refresh();
 		});
 
@@ -69,33 +79,43 @@
 			this.before_refresh();
 		}
 
+		let args = {
+			item_code: this.item_code,
+			warehouse: this.warehouse,
+			parent_warehouse: this.parent_warehouse,
+			item_group: this.item_group,
+			company: this.company,
+			start: this.start,
+			sort_by: this.sort_by,
+			sort_order: this.sort_order
+		};
+
 		var me = this;
 		frappe.call({
-			method: 'erpnext.stock.dashboard.item_dashboard.get_data',
-			args: {
-				item_code: this.item_code,
-				warehouse: this.warehouse,
-				item_group: this.item_group,
-				start: this.start,
-				sort_by: this.sort_by,
-				sort_order: this.sort_order,
-			},
+			method: this.method,
+			args: args,
 			callback: function(r) {
 				me.render(r.message);
 			}
 		});
 	},
 	render: function(data) {
-		if(this.start===0) {
+		if (this.start===0) {
 			this.max_count = 0;
 			this.result.empty();
 		}
 
-		var context = this.get_item_dashboard_data(data, this.max_count, true);
+		let context = "";
+		if (this.page_name === "warehouse-capacity-summary") {
+			context = this.get_capacity_dashboard_data(data);
+		} else {
+			context = this.get_item_dashboard_data(data, this.max_count, true);
+		}
+
 		this.max_count = this.max_count;
 
 		// show more button
-		if(data && data.length===21) {
+		if (data && data.length===(this.page_length + 1)) {
 			this.content.find('.more').removeClass('hidden');
 
 			// remove the last element
@@ -106,12 +126,17 @@
 
 		// If not any stock in any warehouses provide a message to end user
 		if (context.data.length > 0) {
-			$(frappe.render_template('item_dashboard_list', context)).appendTo(this.result);
+			this.content.find('.result').css('text-align', 'unset');
+			$(frappe.render_template(this.template, context)).appendTo(this.result);
 		} else {
-			var message = __("Currently no stock available in any warehouse");
-			$(`<span class='text-muted small'>  ${message} </span>`).appendTo(this.result);
+			var message = __("No Stock Available Currently");
+			this.content.find('.result').css('text-align', 'center');
+
+			$(`<div class='text-muted' style='margin: 20px 5px; font-weight: lighter;'>
+				${message} </div>`).appendTo(this.result);
 		}
 	},
+
 	get_item_dashboard_data: function(data, max_count, show_item) {
 		if(!max_count) max_count = 0;
 		if(!data) data = [];
@@ -128,8 +153,8 @@
 				d.total_reserved, max_count);
 		});
 
-		var can_write = 0;
-		if(frappe.boot.user.can_write.indexOf("Stock Entry")>=0){
+		let can_write = 0;
+		if (frappe.boot.user.can_write.indexOf("Stock Entry") >= 0) {
 			can_write = 1;
 		}
 
@@ -138,9 +163,27 @@
 			max_count: max_count,
 			can_write:can_write,
 			show_item: show_item || false
+		};
+	},
+
+	get_capacity_dashboard_data: function(data) {
+		if (!data) data = [];
+
+		data.forEach(function(d) {
+			d.color =  d.percent_occupied >=80 ? "#f8814f" : "#2490ef";
+		});
+
+		let can_write = 0;
+		if (frappe.boot.user.can_write.indexOf("Putaway Rule") >= 0) {
+			can_write = 1;
 		}
+
+		return {
+			data: data,
+			can_write: can_write,
+		};
 	}
-})
+});
 
 erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) {
 	var dialog = new frappe.ui.Dialog({
diff --git a/erpnext/stock/dashboard/warehouse_capacity_dashboard.py b/erpnext/stock/dashboard/warehouse_capacity_dashboard.py
new file mode 100644
index 0000000..ab573e5
--- /dev/null
+++ b/erpnext/stock/dashboard/warehouse_capacity_dashboard.py
@@ -0,0 +1,69 @@
+from __future__ import unicode_literals
+
+import frappe
+from frappe.model.db_query import DatabaseQuery
+from frappe.utils import nowdate
+from frappe.utils import flt
+from erpnext.stock.utils import get_stock_balance
+
+@frappe.whitelist()
+def get_data(item_code=None, warehouse=None, parent_warehouse=None,
+	company=None, start=0, sort_by="stock_capacity", sort_order="desc"):
+	"""Return data to render the warehouse capacity dashboard."""
+	filters = get_filters(item_code, warehouse, parent_warehouse, company)
+
+	no_permission, filters = get_warehouse_filter_based_on_permissions(filters)
+	if no_permission:
+		return []
+
+	capacity_data = get_warehouse_capacity_data(filters, start)
+
+	asc_desc = -1 if sort_order == "desc" else 1
+	capacity_data = sorted(capacity_data, key = lambda i: (i[sort_by] * asc_desc))
+
+	return capacity_data
+
+def get_filters(item_code=None, warehouse=None, parent_warehouse=None,
+	company=None):
+	filters = [['disable', '=', 0]]
+	if item_code:
+		filters.append(['item_code', '=', item_code])
+	if warehouse:
+		filters.append(['warehouse', '=', warehouse])
+	if company:
+		filters.append(['company', '=', company])
+	if parent_warehouse:
+		lft, rgt = frappe.db.get_value("Warehouse", parent_warehouse, ["lft", "rgt"])
+		warehouses = frappe.db.sql_list("""
+			select name from `tabWarehouse`
+			where lft >=%s and rgt<=%s
+		""", (lft, rgt))
+		filters.append(['warehouse', 'in', warehouses])
+	return filters
+
+def get_warehouse_filter_based_on_permissions(filters):
+	try:
+		# check if user has any restrictions based on user permissions on warehouse
+		if DatabaseQuery('Warehouse', user=frappe.session.user).build_match_conditions():
+			filters.append(['warehouse', 'in', [w.name for w in frappe.get_list('Warehouse')]])
+		return False, filters
+	except frappe.PermissionError:
+		# user does not have access on warehouse
+		return True, []
+
+def get_warehouse_capacity_data(filters, start):
+	capacity_data = frappe.db.get_all('Putaway Rule',
+		fields=['item_code', 'warehouse','stock_capacity', 'company'],
+		filters=filters,
+		limit_start=start,
+		limit_page_length='11'
+	)
+
+	for entry in capacity_data:
+		balance_qty = get_stock_balance(entry.item_code, entry.warehouse, nowdate()) or 0
+		entry.update({
+			'actual_qty': balance_qty,
+			'percent_occupied': flt((flt(balance_qty) / flt(entry.stock_capacity)) * 100, 0)
+		})
+
+	return capacity_data
\ No newline at end of file
diff --git a/erpnext/stock/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json
index 9068e33..74cc42d 100644
--- a/erpnext/stock/desk_page/stock/stock.json
+++ b/erpnext/stock/desk_page/stock/stock.json
@@ -8,12 +8,12 @@
   {
    "hidden": 0,
    "label": "Stock Transactions",
-   "links": "[\n     {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Material Request\",\n        \"name\": \"Material Request\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Stock Entry\",\n        \"name\": \"Stock Entry\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\",\n            \"Customer\"\n        ],\n        \"label\": \"Delivery Note\",\n        \"name\": \"Delivery Note\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\",\n            \"Supplier\"\n        ],\n        \"label\": \"Purchase Receipt\",\n        \"name\": \"Purchase Receipt\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Pick List\",\n        \"name\": \"Pick List\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Shipment\",\n        \"name\": \"Shipment\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Delivery Trip\",\n        \"name\": \"Delivery Trip\",\n        \"type\": \"doctype\"\n    }\n]"
+   "links": "[\n     {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Material Request\",\n        \"name\": \"Material Request\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Stock Entry\",\n        \"name\": \"Stock Entry\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\",\n            \"Customer\"\n        ],\n        \"label\": \"Delivery Note\",\n        \"name\": \"Delivery Note\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\",\n            \"Supplier\"\n        ],\n        \"label\": \"Purchase Receipt\",\n        \"name\": \"Purchase Receipt\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Pick List\",\n        \"name\": \"Pick List\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Putaway Rule\",\n        \"name\": \"Putaway Rule\",\n        \"onboard\": 1,\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Shipment\",\n        \"name\": \"Shipment\",\n        \"type\": \"doctype\"\n    },\n    {\n        \"label\": \"Delivery Trip\",\n        \"name\": \"Delivery Trip\",\n        \"type\": \"doctype\"\n    }\n]"
   },
   {
    "hidden": 0,
    "label": "Stock Reports",
-   "links": "[\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Stock Ledger Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Ledger\",\n        \"name\": \"Stock Ledger\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Stock Ledger Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Balance\",\n        \"name\": \"Stock Balance\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Item\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Projected Qty\",\n        \"name\": \"Stock Projected Qty\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Stock Summary\",\n        \"name\": \"stock-balance\",\n        \"type\": \"page\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Item\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Ageing\",\n        \"name\": \"Stock Ageing\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Item\",\n        \"is_query_report\": true,\n        \"label\": \"Item Price Stock\",\n        \"name\": \"Item Price Stock\",\n        \"type\": \"report\"\n    }\n]"
+   "links": "[\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Stock Ledger Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Ledger\",\n        \"name\": \"Stock Ledger\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Stock Ledger Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Balance\",\n        \"name\": \"Stock Balance\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Item\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Projected Qty\",\n        \"name\": \"Stock Projected Qty\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"label\": \"Stock Summary\",\n        \"name\": \"stock-balance\",\n        \"type\": \"page\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Item\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Ageing\",\n        \"name\": \"Stock Ageing\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Item\",\n        \"is_query_report\": true,\n        \"label\": \"Item Price Stock\",\n        \"name\": \"Item Price Stock\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\",\n            \"Putaway Rule\"\n        ],\n        \"label\": \"Warehouse Capacity Summary\",\n        \"name\": \"warehouse-capacity-summary\",\n        \"type\": \"page\"\n    }\n]"
   },
   {
    "hidden": 0,
@@ -58,7 +58,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Stock",
- "modified": "2020-12-02 15:47:41.532942",
+ "modified": "2020-12-08 15:47:41.532942",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock",
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index e41f1a8..97f85ba 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -8,6 +8,8 @@
 
 from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError, get_batch_no
 from frappe.utils import cint, flt
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+from erpnext.stock.get_item_details import get_item_details
 
 class TestBatch(unittest.TestCase):
 	def test_item_has_batch_enabled(self):
@@ -182,7 +184,7 @@
 		stock_entry.cancel()
 		current_batch_qty = flt(frappe.db.get_value("Batch", "B100", "batch_qty"))
 		self.assertEqual(current_batch_qty, existing_batch_qty)
-		
+
 	@classmethod
 	def make_new_batch_and_entry(cls, item_name, batch_name, warehouse):
 		'''Make a new stock entry for given target warehouse and batch name of item'''
@@ -252,6 +254,72 @@
 
 		return batch
 
+	def test_batch_wise_item_price(self):
+		if not frappe.db.get_value('Item', '_Test Batch Price Item'):
+			frappe.get_doc({
+				'doctype': 'Item',
+				'is_stock_item': 1,
+				'item_code': '_Test Batch Price Item',
+				'item_group': 'Products',
+				'has_batch_no': 1,
+				'create_new_batch': 1
+			}).insert(ignore_permissions=True)
+
+		batch1 = create_batch('_Test Batch Price Item', 200, 1)
+		batch2 = create_batch('_Test Batch Price Item', 300, 1)
+		batch3 = create_batch('_Test Batch Price Item', 400, 0)
+
+		args = frappe._dict({
+			"item_code": "_Test Batch Price Item",
+			"company": "_Test Company with perpetual inventory",
+			"price_list": "_Test Price List",
+			"currency": "_Test Currency",
+			"doctype": "Sales Invoice",
+			"conversion_rate": 1,
+			"price_list_currency": "_Test Currency",
+			"plc_conversion_rate": 1,
+			"customer": "_Test Customer",
+			"name": None
+		})
+
+		#test price for batch1
+		args.update({'batch_no': batch1})
+		details = get_item_details(args)
+		self.assertEqual(details.get('price_list_rate'), 200)
+
+		#test price for batch2
+		args.update({'batch_no': batch2})
+		details = get_item_details(args)
+		self.assertEqual(details.get('price_list_rate'), 300)
+
+		#test price for batch3
+		args.update({'batch_no': batch3})
+		details = get_item_details(args)
+		self.assertEqual(details.get('price_list_rate'), 400)
+
+def create_batch(item_code, rate, create_item_price_for_batch):
+	pi = make_purchase_invoice(company="_Test Company with perpetual inventory",
+		warehouse= "Stores - TCP1", cost_center = "Main - TCP1", update_stock=1,
+		expense_account ="_Test Account Cost for Goods Sold - TCP1", item_code=item_code)
+
+	batch = frappe.db.get_value('Batch', {'item': item_code, 'reference_name': pi.name})
+
+	if not create_item_price_for_batch:
+		create_price_list_for_batch(item_code, None, rate)
+	else:
+		create_price_list_for_batch(item_code, batch, rate)
+
+	return batch
+
+def create_price_list_for_batch(item_code, batch, rate):
+	frappe.get_doc({
+		'doctype': 'Item Price',
+		'item_code': '_Test Batch Price Item',
+		'price_list': '_Test Price List',
+		'batch_no': batch,
+		'price_list_rate': rate
+	}).insert()
+
 def make_new_batch(**args):
 	args = frappe._dict(args)
 
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index ab19b77..1088b41 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -17,7 +17,7 @@
 		'''Called from erpnext.stock.utils.update_bin'''
 		self.update_qty(args)
 		if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
-			from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle
+			from erpnext.stock.stock_ledger import update_entries_after, validate_negative_qty_in_future_sle
 
 			if not args.get("posting_date"):
 				args["posting_date"] = nowdate()
@@ -37,8 +37,8 @@
 				"sle_id": args.name
 			}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
 
-			# Update qty_after_transaction in future SLEs of this item and warehouse
-			update_qty_in_future_sle(args)
+			# Validate negative qty in future transactions
+			validate_negative_qty_in_future_sle(args)
 
 	def update_qty(self, args):
 		# update the stock values (for current quantities)
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 5f2658c..334bdea 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -7,13 +7,14 @@
 
 frappe.provide("erpnext.stock");
 frappe.provide("erpnext.stock.delivery_note");
+frappe.provide("erpnext.accounts.dimensions");
 
 frappe.ui.form.on("Delivery Note", {
 	setup: function(frm) {
 		frm.custom_make_buttons = {
 			'Packing Slip': 'Packing Slip',
 			'Installation Note': 'Installation Note',
-			'Sales Invoice': 'Invoice',
+			'Sales Invoice': 'Sales Invoice',
 			'Stock Entry': 'Return',
 			'Shipment': 'Shipment'
 		},
@@ -76,7 +77,7 @@
 			}
 		});
 
-
+		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
 	},
 
 	print_without_amount: function(frm) {
@@ -94,13 +95,19 @@
 			frm.page.set_inner_btn_group_as_primary(__('Create'));
 		}
 
-		if (frm.doc.docstatus === 1 && frm.doc.is_internal_customer && !frm.doc.inter_company_reference) {
-			frm.add_custom_button(__('Purchase Receipt'), function() {
-				frappe.model.open_mapped_doc({
-					method: 'erpnext.stock.doctype.delivery_note.delivery_note.make_inter_company_purchase_receipt',
-					frm: frm,
-				})
-			}, __('Create'));
+		if (frm.doc.docstatus == 1 && !frm.doc.inter_company_reference) {
+			let internal = me.frm.doc.is_internal_customer;
+			if (internal) {
+				let button_label = (me.frm.doc.company === me.frm.doc.represents_company) ? "Internal Purchase Receipt" :
+					"Inter Company Purchase Receipt";
+
+				me.frm.add_custom_button(button_label, function() {
+					frappe.model.open_mapped_doc({
+						method: 'erpnext.stock.doctype.delivery_note.delivery_note.make_inter_company_purchase_receipt',
+						frm: frm,
+					});
+				}, __('Create'));
+			}
 		}
 	}
 });
@@ -296,15 +303,6 @@
 			}
 		})
 	},
-
-	to_warehouse: function() {
-		let packed_items_table = this.frm.doc["packed_items"];
-		this.autofill_warehouse(this.frm.doc["items"], "target_warehouse", this.frm.doc.to_warehouse);
-		if (packed_items_table && packed_items_table.length) {
-			this.autofill_warehouse(packed_items_table, "target_warehouse", this.frm.doc.to_warehouse);
-		}
-	}
-
 });
 
 $.extend(cur_frm.cscript, new erpnext.stock.DeliveryNoteController({frm: cur_frm}));
@@ -318,6 +316,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/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index c9f8d08..f595aad 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -53,7 +53,7 @@
   "sec_warehouse",
   "set_warehouse",
   "col_break_warehouse",
-  "to_warehouse",
+  "set_target_warehouse",
   "items_section",
   "scan_barcode",
   "items",
@@ -117,6 +117,7 @@
   "source",
   "column_break5",
   "is_internal_customer",
+  "represents_company",
   "inter_company_reference",
   "per_billed",
   "customer_group",
@@ -503,18 +504,6 @@
    "fieldtype": "Column Break"
   },
   {
-   "description": "Required only for sample item.",
-   "fieldname": "to_warehouse",
-   "fieldtype": "Link",
-   "in_standard_filter": 1,
-   "label": "To Warehouse",
-   "no_copy": 1,
-   "oldfieldname": "to_warehouse",
-   "oldfieldtype": "Link",
-   "options": "Warehouse",
-   "print_hide": 1
-  },
-  {
    "fieldname": "items_section",
    "fieldtype": "Section Break",
    "oldfieldtype": "Section Break",
@@ -1261,13 +1250,34 @@
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "depends_on": "eval: doc.is_internal_customer",
+   "fieldname": "set_target_warehouse",
+   "fieldtype": "Link",
+   "in_standard_filter": 1,
+   "label": "Set Target Warehouse",
+   "no_copy": 1,
+   "oldfieldname": "to_warehouse",
+   "oldfieldtype": "Link",
+   "options": "Warehouse",
+   "print_hide": 1
+  },
+  {
+   "description": "Company which internal customer represents.",
+   "fetch_from": "customer.represents_company",
+   "fieldname": "represents_company",
+   "fieldtype": "Link",
+   "label": "Represents Company",
+   "options": "Company",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-truck",
  "idx": 146,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-30 12:54:45.407289",
+ "modified": "2020-12-26 17:07:59.194403",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index a30cadf..fa5a7fb 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -664,7 +664,8 @@
 	return make_inter_company_transaction("Delivery Note", source_name, target_doc)
 
 def make_inter_company_transaction(doctype, source_name, target_doc=None):
-	from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_transaction, get_inter_company_details
+	from erpnext.accounts.doctype.sales_invoice.sales_invoice import (validate_inter_company_transaction,
+		get_inter_company_details, update_address, update_taxes, set_purchase_references)
 
 	if doctype == 'Delivery Note':
 		source_doc = frappe.get_doc(doctype, source_name)
@@ -682,6 +683,7 @@
 
 	def set_missing_values(source, target):
 		target.run_method("set_missing_values")
+		set_purchase_references(target)
 
 		if target.doctype == 'Purchase Receipt':
 			master_doctype = 'Purchase Taxes and Charges Template'
@@ -697,21 +699,35 @@
 		if target_doc.doctype == 'Purchase Receipt':
 			target_doc.company = details.get("company")
 			target_doc.supplier = details.get("party")
-			target_doc.supplier_address = source_doc.company_address
-			target_doc.shipping_address = source_doc.shipping_address_name or source_doc.customer_address
 			target_doc.buying_price_list = source_doc.selling_price_list
 			target_doc.is_internal_supplier = 1
 			target_doc.inter_company_reference = source_doc.name
+
+			# Invert the address on target doc creation
+			update_address(target_doc, 'supplier_address', 'address_display', source_doc.company_address)
+			update_address(target_doc, 'shipping_address', 'shipping_address_display', source_doc.customer_address)
+
+			update_taxes(target_doc, party=target_doc.supplier, party_type='Supplier', company=target_doc.company,
+				doctype=target_doc.doctype, party_address=target_doc.supplier_address,
+				company_address=target_doc.shipping_address)
 		else:
 			target_doc.company = details.get("company")
 			target_doc.customer = details.get("party")
 			target_doc.company_address = source_doc.supplier_address
-			target_doc.shipping_address_name = source_doc.shipping_address
 			target_doc.selling_price_list = source_doc.buying_price_list
 			target_doc.is_internal_customer = 1
 			target_doc.inter_company_reference = source_doc.name
 
-	doclist = get_mapped_doc(doctype, source_name,	{
+			# Invert the address on target doc creation
+			update_address(target_doc, 'company_address', 'company_address_display', source_doc.supplier_address)
+			update_address(target_doc, 'shipping_address_name', 'shipping_address', source_doc.shipping_address)
+			update_address(target_doc, 'customer_address', 'address_display', source_doc.shipping_address)
+
+			update_taxes(target_doc, party=target_doc.customer, party_type='Customer', company=target_doc.company,
+				doctype=target_doc.doctype, party_address=target_doc.customer_address,
+				company_address=target_doc.company_address, shipping_address_name=target_doc.shipping_address_name)
+
+	doclist = get_mapped_doc(doctype, source_name, {
 		doctype: {
 			"doctype": target_doctype,
 			"postprocess": update_details,
@@ -722,7 +738,10 @@
 		doctype +" Item": {
 			"doctype": target_doctype + " Item",
 			"field_map": {
-				source_document_warehouse_field: target_document_warehouse_field
+				source_document_warehouse_field: target_document_warehouse_field,
+				'name': 'delivery_note_item',
+				'batch_no': 'batch_no',
+				'serial_no': 'serial_no'
 			},
 			"field_no_map": [
 				"warehouse"
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 4bbf3de..9de088d 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -458,7 +458,7 @@
    "fieldname": "warehouse",
    "fieldtype": "Link",
    "in_list_view": 1,
-   "label": "From Warehouse",
+   "label": "Warehouse",
    "oldfieldname": "warehouse",
    "oldfieldtype": "Link",
    "options": "Warehouse",
@@ -467,11 +467,12 @@
    "width": "100px"
   },
   {
+   "depends_on": "eval:parent.is_internal_customer",
    "fieldname": "target_warehouse",
    "fieldtype": "Link",
    "hidden": 1,
    "ignore_user_permissions": 1,
-   "label": "Customer Warehouse (Optional)",
+   "label": "Target Warehouse",
    "no_copy": 1,
    "options": "Warehouse",
    "print_hide": 1
@@ -748,7 +749,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-12-07 19:59:27.119856",
+ "modified": "2020-12-26 17:31:27.029803",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index faeeb57..ec32b0f 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -384,7 +384,10 @@
 					<a href="#stock-balance">' + __("Stock Levels") + '</a></h5>');
 				erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({
 					parent: section,
-					item_code: frm.doc.name
+					item_code: frm.doc.name,
+					page_length: 20,
+					method: 'erpnext.stock.dashboard.item_dashboard.get_data',
+					template: 'item_dashboard_list'
 				});
 				erpnext.item.item_dashboard.refresh();
 			});
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index d07b3dc..fcf7c26 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -106,9 +106,9 @@
   "item_tax_section_break",
   "taxes",
   "inspection_criteria",
+  "quality_inspection_template",
   "inspection_required_before_purchase",
   "inspection_required_before_delivery",
-  "quality_inspection_template",
   "manufacturing",
   "default_bom",
   "is_sub_contracted_item",
@@ -814,7 +814,6 @@
    "label": "Inspection Required before Delivery"
   },
   {
-   "depends_on": "eval:(doc.inspection_required_before_purchase || doc.inspection_required_before_delivery)",
    "fieldname": "quality_inspection_template",
    "fieldtype": "Link",
    "label": "Quality Inspection Template",
@@ -1069,7 +1068,7 @@
  "index_web_pages_for_search": 1,
  "links": [],
  "max_attachments": 1,
- "modified": "2020-08-07 14:24:58.384992",
+ "modified": "2021-01-25 20:49:50.222976",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item",
@@ -1131,4 +1130,4 @@
  "sort_order": "DESC",
  "title_field": "item_name",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item_price/item_price.js b/erpnext/stock/doctype/item_price/item_price.js
index 2729f4b..e4db048 100644
--- a/erpnext/stock/doctype/item_price/item_price.js
+++ b/erpnext/stock/doctype/item_price/item_price.js
@@ -15,5 +15,13 @@
 
 		frm.set_df_property("bulk_import_help", "options",
 			'<a href="#data-import-tool/Item Price">' + __("Import in Bulk") + '</a>');
+
+		frm.set_query('batch_no', function() {
+			return {
+				filters: {
+					'item': frm.doc.item_code
+				}
+			}
+		});
 	}
 });
diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json
index 5f62381..83177b3 100644
--- a/erpnext/stock/doctype/item_price/item_price.json
+++ b/erpnext/stock/doctype/item_price/item_price.json
@@ -18,6 +18,7 @@
   "price_list",
   "customer",
   "supplier",
+  "batch_no",
   "column_break_3",
   "buying",
   "selling",
@@ -47,31 +48,41 @@
    "oldfieldtype": "Select",
    "options": "Item",
    "reqd": 1,
-   "search_index": 1
+   "search_index": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "uom",
    "fieldtype": "Link",
    "label": "UOM",
-   "options": "UOM"
+   "options": "UOM",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "default": "0",
    "description": "Quantity  that must be bought or sold per UOM",
    "fieldname": "packing_unit",
    "fieldtype": "Int",
-   "label": "Packing Unit"
+   "label": "Packing Unit",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "column_break_17",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "item_name",
    "fieldtype": "Data",
    "in_list_view": 1,
    "label": "Item Name",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fetch_from": "item_code.brand",
@@ -79,19 +90,25 @@
    "fieldtype": "Read Only",
    "in_list_view": 1,
    "label": "Brand",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "item_description",
    "fieldtype": "Text",
    "label": "Item Description",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "price_list_details",
    "fieldtype": "Section Break",
    "label": "Price List",
-   "options": "fa fa-tags"
+   "options": "fa fa-tags",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "price_list",
@@ -100,7 +117,9 @@
    "in_standard_filter": 1,
    "label": "Price List",
    "options": "Price List",
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "bold": 1,
@@ -108,37 +127,49 @@
    "fieldname": "customer",
    "fieldtype": "Link",
    "label": "Customer",
-   "options": "Customer"
+   "options": "Customer",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "depends_on": "eval:doc.buying == 1",
    "fieldname": "supplier",
    "fieldtype": "Link",
    "label": "Supplier",
-   "options": "Supplier"
+   "options": "Supplier",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "column_break_3",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "default": "0",
    "fieldname": "buying",
    "fieldtype": "Check",
    "label": "Buying",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "default": "0",
    "fieldname": "selling",
    "fieldtype": "Check",
    "label": "Selling",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "item_details",
    "fieldtype": "Section Break",
-   "options": "fa fa-tag"
+   "options": "fa fa-tag",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "bold": 1,
@@ -146,11 +177,15 @@
    "fieldtype": "Link",
    "label": "Currency",
    "options": "Currency",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "col_br_1",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "price_list_rate",
@@ -162,53 +197,80 @@
    "oldfieldname": "ref_rate",
    "oldfieldtype": "Currency",
    "options": "currency",
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "section_break_15",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "default": "Today",
    "fieldname": "valid_from",
    "fieldtype": "Date",
-   "label": "Valid From"
+   "label": "Valid From",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "default": "0",
    "fieldname": "lead_time_days",
    "fieldtype": "Int",
-   "label": "Lead Time in days"
+   "label": "Lead Time in days",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "column_break_18",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "valid_upto",
    "fieldtype": "Date",
-   "label": "Valid Upto"
+   "label": "Valid Upto",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "section_break_24",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "note",
    "fieldtype": "Text",
-   "label": "Note"
+   "label": "Note",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "reference",
    "fieldtype": "Data",
    "in_list_view": 1,
-   "label": "Reference"
+   "label": "Reference",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "batch_no",
+   "fieldtype": "Link",
+   "label": "Batch No",
+   "options": "Batch",
+   "show_days": 1,
+   "show_seconds": 1
   }
  ],
  "icon": "fa fa-flag",
  "idx": 1,
+ "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-07-06 22:31:32.943475",
+ "modified": "2020-12-08 18:12:15.395772",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item Price",
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index bed5ea9..e82a19b 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -54,7 +54,8 @@
 			"valid_upto",
 			"packing_unit",
 			"customer",
-			"supplier",]:
+			"supplier",
+			"batch_no"]:
 			if self.get(field):
 				conditions += " and {0} = %({0})s ".format(field)
 			else:
@@ -68,7 +69,7 @@
 			self.as_dict(),)
 
 		if price_list_rate:
-			frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty, and Dates."), ItemPriceDuplicateItem,)
+			frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, Batch, UOM, Qty, and Dates."), ItemPriceDuplicateItem,)
 
 	def before_save(self):
 		if self.selling:
diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
index 888bc2d..471e685 100644
--- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
+++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
@@ -8,26 +8,32 @@
  "field_order": [
   "specification",
   "value",
+  "numeric",
   "column_break_3",
+  "min_value",
+  "max_value",
+  "formula_based_criteria",
   "acceptance_formula"
  ],
  "fields": [
   {
    "fieldname": "specification",
-   "fieldtype": "Data",
+   "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Parameter",
    "oldfieldname": "specification",
    "oldfieldtype": "Data",
+   "options": "Quality Inspection Parameter",
    "print_width": "200px",
    "reqd": 1,
-   "width": "200px"
+   "width": "100px"
   },
   {
+   "depends_on": "eval:(!doc.formula_based_criteria && !doc.numeric)",
    "fieldname": "value",
    "fieldtype": "Data",
    "in_list_view": 1,
-   "label": "Acceptance Criteria",
+   "label": "Acceptance Criteria Value",
    "oldfieldname": "value",
    "oldfieldtype": "Data"
   },
@@ -36,17 +42,45 @@
    "fieldtype": "Column Break"
   },
   {
-   "description": "Simple Python formula based on numeric Readings.<br> Example 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nExample 2: <b>(reading_1 + reading_2) / 2 &lt; 10</b>",
+   "depends_on": "formula_based_criteria",
+   "description": "Simple Python formula applied on Reading fields.<br> Numeric eg. 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nNumeric eg. 2: <b>mean &gt; 3.5</b> (mean of populated fields)<br>\nValue based eg.:  <b>reading_value in (\"A\", \"B\", \"C\")</b>",
    "fieldname": "acceptance_formula",
    "fieldtype": "Code",
-   "in_list_view": 1,
    "label": "Acceptance Criteria Formula"
+  },
+  {
+   "default": "0",
+   "fieldname": "formula_based_criteria",
+   "fieldtype": "Check",
+   "label": "Formula Based Criteria"
+  },
+  {
+   "depends_on": "eval:(!doc.formula_based_criteria && doc.numeric)",
+   "fieldname": "min_value",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Minimum Value"
+  },
+  {
+   "depends_on": "eval:(!doc.formula_based_criteria && doc.numeric)",
+   "fieldname": "max_value",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Maximum Value"
+  },
+  {
+   "default": "1",
+   "fieldname": "numeric",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "Numeric",
+   "width": "80px"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-11-16 16:33:42.421842",
+ "modified": "2021-02-01 19:18:46.924399",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item Quality Inspection Parameter",
diff --git a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json
index b24d621..c77b993 100644
--- a/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json
+++ b/erpnext/stock/doctype/landed_cost_item/landed_cost_item.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2013-02-22 01:28:02",
  "doctype": "DocType",
  "document_type": "Document",
@@ -29,6 +30,8 @@
    "options": "Item",
    "read_only": 1,
    "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1,
    "width": "100px"
   },
   {
@@ -41,6 +44,8 @@
    "print_width": "300px",
    "read_only": 1,
    "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1,
    "width": "120px"
   },
   {
@@ -50,7 +55,9 @@
    "no_copy": 1,
    "options": "Purchase Invoice\nPurchase Receipt",
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "receipt_document",
@@ -59,25 +66,33 @@
    "no_copy": 1,
    "options": "receipt_document_type",
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "col_break2",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "qty",
    "fieldtype": "Float",
    "in_list_view": 1,
    "label": "Qty",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "rate",
    "fieldtype": "Currency",
    "label": "Rate",
    "options": "Company:company:default_currency",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "amount",
@@ -88,14 +103,19 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "read_only": 1,
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "applicable_charges",
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Applicable Charges",
-   "options": "Company:company:default_currency"
+   "options": "Company:company:default_currency",
+   "read_only_depends_on": "eval:parent.distribute_charges_based_on != 'Distribute Manually'",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "purchase_receipt_item",
@@ -104,22 +124,30 @@
    "label": "Purchase Receipt Item",
    "no_copy": 1,
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "cost_center",
    "fieldtype": "Link",
    "label": "Cost Center",
-   "options": "Cost Center"
+   "options": "Cost Center",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "accounting_dimensions_section",
    "fieldtype": "Section Break",
-   "label": "Accounting Dimensions"
+   "label": "Accounting Dimensions",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "dimension_col_break",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "default": "0",
@@ -128,12 +156,15 @@
    "fieldtype": "Check",
    "hidden": 1,
    "label": "Is Fixed Asset",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   }
  ],
  "idx": 1,
  "istable": 1,
- "modified": "2020-09-18 17:26:09.703215",
+ "links": [],
+ "modified": "2021-01-25 23:09:23.322282",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Landed Cost Item",
diff --git a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
index 64331c7..4fcdb4c 100644
--- a/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
+++ b/erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
@@ -6,8 +6,11 @@
  "engine": "InnoDB",
  "field_order": [
   "expense_account",
+  "account_currency",
+  "exchange_rate",
   "description",
   "col_break3",
+  "base_amount",
   "amount"
  ],
  "fields": [
@@ -28,7 +31,7 @@
    "fieldtype": "Currency",
    "in_list_view": 1,
    "label": "Amount",
-   "options": "Company:company:default_currency",
+   "options": "account_currency",
    "reqd": 1
   },
   {
@@ -38,13 +41,33 @@
    "in_list_view": 1,
    "label": "Expense Account",
    "mandatory_depends_on": "eval:cint(erpnext.is_perpetual_inventory_enabled(parent.company))",
-   "options": "Account",
-   "print_hide": 1
+   "options": "Account"
+  },
+  {
+   "fieldname": "account_currency",
+   "fieldtype": "Link",
+   "label": "Account Currency",
+   "options": "Currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "exchange_rate",
+   "fieldtype": "Float",
+   "label": "Exchange Rate",
+   "precision": "9"
+  },
+  {
+   "fieldname": "base_amount",
+   "fieldtype": "Currency",
+   "label": "Base Amount",
+   "options": "Company:company:default_currency",
+   "read_only": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-12-04 00:22:14.373312",
+ "modified": "2020-12-26 01:07:23.233604",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Landed Cost Taxes and Charges",
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js
index 5de1352..1abbc35 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js
@@ -1,6 +1,7 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
+{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %};
 
 frappe.provide("erpnext.stock");
 
@@ -29,20 +30,9 @@
 		this.frm.add_fetch("receipt_document", "supplier", "supplier");
 		this.frm.add_fetch("receipt_document", "posting_date", "posting_date");
 		this.frm.add_fetch("receipt_document", "base_grand_total", "grand_total");
-
-		this.frm.set_query("expense_account", "taxes", function() {
-			return {
-				query: "erpnext.controllers.queries.tax_account_query",
-				filters: {
-					"account_type": ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"],
-					"company": me.frm.doc.company
-				}
-			};
-		});
-
 	},
 
-	refresh: function(frm) {
+	refresh: function() {
 		var help_content =
 			`<br><br>
 			<table class="table table-bordered" style="background-color: #f9f9f9;">
@@ -72,6 +62,11 @@
 			</table>`;
 
 		set_field_options("landed_cost_help", help_content);
+
+		if (this.frm.doc.company) {
+			let company_currency = frappe.get_doc(":Company", this.frm.doc.company).default_currency;
+			this.frm.set_currency_labels(["total_taxes_and_charges"], company_currency);
+		}
 	},
 
 	get_items_from_purchase_receipts: function() {
@@ -97,34 +92,36 @@
 	set_total_taxes_and_charges: function() {
 		var total_taxes_and_charges = 0.0;
 		$.each(this.frm.doc.taxes || [], function(i, d) {
-			total_taxes_and_charges += flt(d.amount)
+			total_taxes_and_charges += flt(d.base_amount);
 		});
-		cur_frm.set_value("total_taxes_and_charges", total_taxes_and_charges);
+		this.frm.set_value("total_taxes_and_charges", total_taxes_and_charges);
 	},
 
 	set_applicable_charges_for_item: function() {
 		var me = this;
 
 		if(this.frm.doc.taxes.length) {
-
 			var total_item_cost = 0.0;
 			var based_on = this.frm.doc.distribute_charges_based_on.toLowerCase();
-			$.each(this.frm.doc.items || [], function(i, d) {
-				total_item_cost += flt(d[based_on])
-			});
 
-			var total_charges = 0.0;
-			$.each(this.frm.doc.items || [], function(i, item) {
-				item.applicable_charges = flt(item[based_on]) * flt(me.frm.doc.total_taxes_and_charges) / flt(total_item_cost)
-				item.applicable_charges = flt(item.applicable_charges, precision("applicable_charges", item))
-				total_charges += item.applicable_charges
-			});
+			if (based_on != 'distribute manually') {
+				$.each(this.frm.doc.items || [], function(i, d) {
+					total_item_cost += flt(d[based_on])
+				});
 
-			if (total_charges != this.frm.doc.total_taxes_and_charges){
-				var diff = this.frm.doc.total_taxes_and_charges - flt(total_charges)
-				this.frm.doc.items.slice(-1)[0].applicable_charges += diff
+				var total_charges = 0.0;
+				$.each(this.frm.doc.items || [], function(i, item) {
+					item.applicable_charges = flt(item[based_on]) * flt(me.frm.doc.total_taxes_and_charges) / flt(total_item_cost)
+					item.applicable_charges = flt(item.applicable_charges, precision("applicable_charges", item))
+					total_charges += item.applicable_charges
+				});
+
+				if (total_charges != this.frm.doc.total_taxes_and_charges){
+					var diff = this.frm.doc.total_taxes_and_charges - flt(total_charges)
+					this.frm.doc.items.slice(-1)[0].applicable_charges += diff
+				}
+				refresh_field("items");
 			}
-			refresh_field("items");
 		}
 	},
 	distribute_charges_based_on: function (frm) {
@@ -134,7 +131,16 @@
 	items_remove: () => {
 		this.trigger('set_applicable_charges_for_item');
 	}
-
 });
 
 cur_frm.script_manager.make(erpnext.stock.LandedCostVoucher);
+
+frappe.ui.form.on('Landed Cost Taxes and Charges', {
+	expense_account: function(frm, cdt, cdn) {
+		frm.events.set_account_currency(frm, cdt, cdn);
+	},
+
+	amount: function(frm, cdt, cdn) {
+		frm.events.set_base_amount(frm, cdt, cdn);
+	}
+});
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json
index 0149280..059f925 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "naming_series:",
  "creation": "2014-07-11 11:33:42.547339",
  "doctype": "DocType",
@@ -7,6 +8,9 @@
  "field_order": [
   "naming_series",
   "company",
+  "column_break_2",
+  "posting_date",
+  "section_break_5",
   "purchase_receipts",
   "purchase_receipt_items",
   "get_items_from_purchase_receipts",
@@ -30,7 +34,9 @@
    "options": "MAT-LCV-.YYYY.-",
    "print_hide": 1,
    "reqd": 1,
-   "set_only_once": 1
+   "set_only_once": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "company",
@@ -40,24 +46,32 @@
    "label": "Company",
    "options": "Company",
    "remember_last_selected_value": 1,
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "purchase_receipts",
    "fieldtype": "Table",
    "label": "Purchase Receipts",
    "options": "Landed Cost Purchase Receipt",
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "purchase_receipt_items",
    "fieldtype": "Section Break",
-   "label": "Purchase Receipt Items"
+   "label": "Purchase Receipt Items",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "get_items_from_purchase_receipts",
    "fieldtype": "Button",
-   "label": "Get Items From Purchase Receipts"
+   "label": "Get Items From Purchase Receipts",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "items",
@@ -65,42 +79,56 @@
    "label": "Purchase Receipt Items",
    "no_copy": 1,
    "options": "Landed Cost Item",
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "sec_break1",
    "fieldtype": "Section Break",
-   "label": "Applicable Charges"
+   "label": "Applicable Charges",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "taxes",
    "fieldtype": "Table",
    "label": "Taxes and Charges",
    "options": "Landed Cost Taxes and Charges",
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "section_break_9",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "total_taxes_and_charges",
    "fieldtype": "Currency",
-   "label": "Total Taxes and Charges",
+   "label": "Total Taxes and Charges (Company Currency)",
    "options": "Company:company:default_currency",
    "read_only": 1,
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "col_break1",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "distribute_charges_based_on",
    "fieldtype": "Select",
    "label": "Distribute Charges Based On",
-   "options": "Qty\nAmount",
-   "reqd": 1
+   "options": "Qty\nAmount\nDistribute Manually",
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "amended_from",
@@ -109,21 +137,51 @@
    "no_copy": 1,
    "options": "Landed Cost Voucher",
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "sec_break2",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "landed_cost_help",
    "fieldtype": "HTML",
-   "label": "Landed Cost Help"
+   "label": "Landed Cost Help",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "default": "Today",
+   "fieldname": "posting_date",
+   "fieldtype": "Date",
+   "label": "Posting Date",
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "section_break_5",
+   "fieldtype": "Section Break",
+   "hide_border": 1,
+   "show_days": 1,
+   "show_seconds": 1
   }
  ],
  "icon": "icon-usd",
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
- "modified": "2019-11-21 15:34:10.846093",
+ "links": [],
+ "modified": "2021-01-25 23:07:30.468423",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Landed Cost Voucher",
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index 9ec6b89..69a8bf1 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -9,6 +9,7 @@
 from frappe.model.document import Document
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 from erpnext.accounts.doctype.account.account import get_account_currency
+from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
 
 class LandedCostVoucher(Document):
 	def get_items_from_purchase_receipts(self):
@@ -39,13 +40,15 @@
 
 	def validate(self):
 		self.check_mandatory()
+		self.validate_purchase_receipts()
+		init_landed_taxes_and_totals(self)
+		self.set_total_taxes_and_charges()
 		if not self.get("items"):
 			self.get_items_from_purchase_receipts()
-		else:
-			self.validate_applicable_charges_for_item()
-		self.validate_purchase_receipts()
-		self.validate_expense_accounts()
-		self.set_total_taxes_and_charges()
+
+		self.set_applicable_charges_on_item()
+		self.validate_applicable_charges_for_item()
+
 
 	def check_mandatory(self):
 		if not self.get("purchase_receipts"):
@@ -73,21 +76,37 @@
 				frappe.throw(_("Row {0}: Cost center is required for an item {1}")
 					.format(item.idx, item.item_code))
 
-	def validate_expense_accounts(self):
-		company_currency = erpnext.get_company_currency(self.company)
-		for account in self.taxes:
-			if get_account_currency(account.expense_account) != company_currency:
-				frappe.throw(_("Row {}: Expense account currency should be same as company's default currency.").format(account.idx)
-					+ _("Please select expense account with account currency as {}.").format(frappe.bold(company_currency)),
-					title=_("Invalid Account Currency"))
-
 	def set_total_taxes_and_charges(self):
-		self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")])
+		self.total_taxes_and_charges = sum([flt(d.base_amount) for d in self.get("taxes")])
+
+	def set_applicable_charges_on_item(self):
+		if self.get('taxes') and self.distribute_charges_based_on != 'Distribute Manually':
+			total_item_cost = 0.0
+			total_charges = 0.0
+			item_count = 0
+			based_on_field = frappe.scrub(self.distribute_charges_based_on)
+
+			for item in self.get('items'):
+				total_item_cost += item.get(based_on_field)
+
+			for item in self.get('items'):
+				item.applicable_charges = flt(flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)),
+					item.precision('applicable_charges'))
+				total_charges += item.applicable_charges
+				item_count += 1
+
+			if total_charges != self.total_taxes_and_charges:
+				diff = self.total_taxes_and_charges - total_charges
+				self.get('items')[item_count - 1].applicable_charges += diff
 
 	def validate_applicable_charges_for_item(self):
 		based_on = self.distribute_charges_based_on.lower()
 
-		total = sum([flt(d.get(based_on)) for d in self.get("items")])
+		if based_on != 'distribute manually':
+			total = sum([flt(d.get(based_on)) for d in self.get("items")])
+		else:
+			# consider for proportion while distributing manually
+			total = sum([flt(d.get('applicable_charges')) for d in self.get("items")])
 
 		if not total:
 			frappe.throw(_("Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'").format(based_on))
@@ -153,13 +172,13 @@
 				docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document,
 					'item_code': item.item_code }, fields=['name', 'docstatus'])
 				if not docs or len(docs) != item.qty:
-					frappe.throw(_('There are not enough asset created or linked to {0}.').format(item.receipt_document)
-						+ _('Please create or link {0} Assets with respective document.').format(item.qty))
+					frappe.throw(_('There are not enough asset created or linked to {0}. Please create or link {1} Assets with respective document.').format(
+						item.receipt_document, item.qty))
 				if docs:
 					for d in docs:
 						if d.docstatus == 1:
-							frappe.throw(_('{0} {1} has submitted Assets. Remove Item {2} from table to continue.')
-								.format(item.receipt_document_type, frappe.bold(item.receipt_document), frappe.bold(item.item_code)))
+							frappe.throw(_('{2} <b>{0}</b> has submitted Assets. Remove Item <b>{1}</b> from table to continue.').format(
+									item.receipt_document, item.item_code, item.receipt_document_type))
 
 	def update_rate_in_serial_no_for_non_asset_items(self, receipt_document):
 		for item in receipt_document.get("items"):
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index b97213e..144101c 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -10,6 +10,7 @@
 	import get_gl_entries, test_records as pr_test_records, make_purchase_receipt
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.accounts.doctype.account.test_account import get_inventory_account
+from erpnext.accounts.doctype.account.test_account import create_account
 
 class TestLandedCostVoucher(unittest.TestCase):
 	def test_landed_cost_voucher(self):
@@ -162,8 +163,8 @@
 
 		lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22)
 
-		self.assertEqual(lcv.items[0].applicable_charges, 41.07)
-		self.assertEqual(lcv.items[2].applicable_charges, 41.08)
+		self.assertEqual(flt(lcv.items[0].applicable_charges, 2), 41.07)
+		self.assertEqual(flt(lcv.items[2].applicable_charges, 2), 41.08)
 
 	def test_multiple_landed_cost_voucher_against_pr(self):
 		pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
@@ -206,6 +207,46 @@
 		self.assertEqual(pr.items[0].landed_cost_voucher_amount, 100)
 		self.assertEqual(pr.items[1].landed_cost_voucher_amount, 100)
 
+	def test_multi_currency_lcv(self):
+		## Create USD Shipping charges_account
+		usd_shipping = create_account(account_name="Shipping Charges USD",
+			parent_account="Duties and Taxes - TCP1", company="_Test Company with perpetual inventory",
+			account_currency="USD")
+
+		pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
+			supplier_warehouse = "Stores - TCP1")
+		pr.submit()
+
+		lcv = make_landed_cost_voucher(company = pr.company, receipt_document_type = "Purchase Receipt",
+			receipt_document=pr.name, charges=100, do_not_save=True)
+
+		lcv.append("taxes", {
+			"description": "Shipping Charges",
+			"expense_account": usd_shipping,
+			"amount": 10
+		})
+
+		lcv.save()
+		lcv.submit()
+		pr.load_from_db()
+
+		# Considering exchange rate from USD to INR as 62.9
+		self.assertEqual(lcv.total_taxes_and_charges, 729)
+		self.assertEqual(pr.items[0].landed_cost_voucher_amount, 729)
+
+		gl_entries = frappe.get_all("GL Entry", fields=["account", "credit", "credit_in_account_currency"],
+			filters={"voucher_no": pr.name, "account": ("in", ["Shipping Charges USD - TCP1", "Expenses Included In Valuation - TCP1"])})
+
+		expected_gl_entries = {
+			"Shipping Charges USD - TCP1": [629, 10],
+			"Expenses Included In Valuation - TCP1": [100, 100]
+		}
+
+		for entry in gl_entries:
+			amounts = expected_gl_entries.get(entry.account)
+			self.assertEqual(entry.credit, amounts[0])
+			self.assertEqual(entry.credit_in_account_currency, amounts[1])
+
 def make_landed_cost_voucher(** args):
 	args = frappe._dict(args)
 	ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document)
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/material_request/material_request_list.js b/erpnext/stock/doctype/material_request/material_request_list.js
index 0d70958..de7a3d0 100644
--- a/erpnext/stock/doctype/material_request/material_request_list.js
+++ b/erpnext/stock/doctype/material_request/material_request_list.js
@@ -1,9 +1,10 @@
 frappe.listview_settings['Material Request'] = {
 	add_fields: ["material_request_type", "status", "per_ordered", "per_received", "transfer_status"],
 	get_indicator: function(doc) {
-		if(doc.status=="Stopped") {
+		var precision = frappe.defaults.get_default("float_precision");
+		if (doc.status=="Stopped") {
 			return [__("Stopped"), "red", "status,=,Stopped"];
-		} else if(doc.transfer_status && doc.docstatus != 2) {
+		} else if (doc.transfer_status && doc.docstatus != 2) {
 			if (doc.transfer_status == "Not Started") {
 				return [__("Not Started"), "orange"];
 			} else if (doc.transfer_status == "In Transit") {
@@ -11,14 +12,14 @@
 			} else if (doc.transfer_status == "Completed") {
 				return [__("Completed"), "green"];
 			}
-		} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) == 0) {
+		} else if (doc.docstatus==1 && flt(doc.per_ordered, precision) == 0) {
 			return [__("Pending"), "orange", "per_ordered,=,0"];
-		}  else if(doc.docstatus==1 && flt(doc.per_ordered, 2) < 100) {
+		}  else if (doc.docstatus==1 && flt(doc.per_ordered, precision) < 100) {
 			return [__("Partially ordered"), "yellow", "per_ordered,<,100"];
-		} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) == 100) {
-			if (doc.material_request_type == "Purchase" && flt(doc.per_received, 2) < 100 && flt(doc.per_received, 2) > 0) {
+		} else if (doc.docstatus==1 && flt(doc.per_ordered, precision) == 100) {
+			if (doc.material_request_type == "Purchase" && flt(doc.per_received, precision) < 100 && flt(doc.per_received, precision) > 0) {
 				return [__("Partially Received"), "yellow", "per_received,<,100"];
-			} else if (doc.material_request_type == "Purchase" && flt(doc.per_received, 2) == 100) {
+			} else if (doc.material_request_type == "Purchase" && flt(doc.per_received, precision) == 100) {
 				return [__("Received"), "green", "per_received,=,100"];
 			} else if (doc.material_request_type == "Purchase") {
 				return [__("Ordered"), "green", "per_ordered,=,100"];
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 0a29fa0..72a3a5e 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -424,6 +424,7 @@
 			"basic_rate": 1.0
 		})
 		se_doc.get("items")[1].update({
+			"item_code": "_Test Item Home Desktop 100",
 			"qty": 3.0,
 			"transfer_qty": 3.0,
 			"s_warehouse": "_Test Warehouse 1 - _TC",
@@ -534,7 +535,7 @@
 
 		mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture',
 			uom="_Test UOM 1", conversion_factor=12)
-		
+
 		requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
 
 		self.assertEqual(requested_qty, existing_requested_qty + 120)
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index bc1d81d..57cc350 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) {
@@ -213,6 +216,10 @@
 		});
 	},
 
+	apply_putaway_rule: function() {
+		if (this.frm.doc.apply_putaway_rule) erpnext.apply_putaway_rule(this.frm);
+	}
+
 });
 
 // for backward compatibility: combine new and previous states
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 5bb3095..32d349f 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -21,6 +21,7 @@
   "posting_date",
   "posting_time",
   "set_posting_time",
+  "apply_putaway_rule",
   "is_return",
   "return_against",
   "section_addresses",
@@ -47,6 +48,7 @@
   "set_warehouse",
   "rejected_warehouse",
   "col_break_warehouse",
+  "set_from_warehouse",
   "is_subcontracted",
   "supplier_warehouse",
   "items_section",
@@ -114,6 +116,7 @@
   "per_returned",
   "is_internal_supplier",
   "inter_company_reference",
+  "represents_company",
   "subscription_detail",
   "auto_repeat",
   "printing_settings",
@@ -1086,7 +1089,9 @@
    "fieldname": "inter_company_reference",
    "fieldtype": "Link",
    "label": "Inter Company Reference",
+   "no_copy": 1,
    "options": "Delivery Note",
+   "print_hide": 1,
    "read_only": 1
   },
   {
@@ -1107,6 +1112,12 @@
    "read_only": 1
   },
   {
+   "default": "0",
+   "fieldname": "apply_putaway_rule",
+   "fieldtype": "Check",
+   "label": "Apply Putaway Rule"
+  },
+  {
    "depends_on": "eval:!doc.__islocal",
    "fieldname": "per_returned",
    "fieldtype": "Percent",
@@ -1114,13 +1125,29 @@
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "depends_on": "eval: doc.is_internal_supplier",
+   "description": "Sets 'From Warehouse' in each row of the items table.",
+   "fieldname": "set_from_warehouse",
+   "fieldtype": "Link",
+   "label": "Set From Warehouse",
+   "options": "Warehouse"
+  },
+  {
+   "fetch_from": "supplier.represents_company",
+   "fieldname": "represents_company",
+   "fieldtype": "Link",
+   "label": "Represents Company",
+   "options": "Company",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-truck",
  "idx": 261,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-30 12:54:23.278500",
+ "modified": "2020-12-26 20:49:39.106049",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 159a608..550c849 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -83,6 +83,12 @@
 				}
 			])
 
+	def before_validate(self):
+		from erpnext.stock.doctype.putaway_rule.putaway_rule import apply_putaway_rule
+
+		if self.get("items") and self.apply_putaway_rule and not self.get("is_return"):
+			apply_putaway_rule(self.doctype, self.get("items"), self.company)
+
 	def validate(self):
 		self.validate_posting_time()
 		super(PurchaseReceipt, self).validate()
@@ -103,6 +109,7 @@
 		if getdate(self.posting_date) > getdate(nowdate()):
 			throw(_("Posting Date cannot be future date"))
 
+
 	def validate_cwip_accounts(self):
 		for item in self.get('items'):
 			if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category):
@@ -281,12 +288,15 @@
 					# Amount added through landed-cost-voucher
 					if d.landed_cost_voucher_amount and landed_cost_entries:
 						for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]):
+							account_currency = get_account_currency(account)
 							gl_entries.append(self.get_gl_dict({
 								"account": account,
+								"account_currency": account_currency,
 								"against": warehouse_account[d.warehouse]["account"],
 								"cost_center": d.cost_center,
 								"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
-								"credit": flt(amount),
+								"credit": flt(amount["base_amount"]),
+								"credit_in_account_currency": flt(amount["amount"]),
 								"project": d.project
 							}, item=d))
 
@@ -325,7 +335,7 @@
 				elif d.warehouse not in warehouse_with_no_account or \
 					d.rejected_warehouse not in warehouse_with_no_account:
 						warehouse_with_no_account.append(d.warehouse)
-			elif d.item_code not in stock_items and flt(d.qty) and auto_accounting_for_non_stock_items:
+			elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items:
 
 				service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
 				credit_currency = get_account_currency(service_received_but_not_billed_account)
@@ -408,7 +418,7 @@
 		if warehouse_with_no_account:
 			frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
 				"\n".join(warehouse_with_no_account))
-		
+
 		return process_gl_map(gl_entries)
 
 	def get_asset_gl_entry(self, gl_entries):
@@ -721,7 +731,13 @@
 
 	for lcv in landed_cost_vouchers:
 		landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent)
-		based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
+
+		#Use amount field for total item cost for manually cost distributed LCVs
+		if landed_cost_voucher_doc.distribute_charges_based_on == 'Distribute Manually':
+			based_on_field = 'amount'
+		else:
+			based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
+
 		total_item_cost = 0
 
 		for item in landed_cost_voucher_doc.items:
@@ -731,9 +747,16 @@
 			if item.receipt_document == purchase_document:
 				for account in landed_cost_voucher_doc.taxes:
 					item_account_wise_cost.setdefault((item.item_code, item.purchase_receipt_item), {})
-					item_account_wise_cost[(item.item_code, item.purchase_receipt_item)].setdefault(account.expense_account, 0.0)
-					item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account] += \
+					item_account_wise_cost[(item.item_code, item.purchase_receipt_item)].setdefault(account.expense_account, {
+						"amount": 0.0,
+						"base_amount": 0.0
+					})
+
+					item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account]["amount"] += \
 						account.amount * item.get(based_on_field) / total_item_cost
 
+					item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account]["base_amount"] += \
+						account.base_amount * item.get(based_on_field) / total_item_cost
+
 	return item_account_wise_cost
 
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 83012d3..ca58ab2 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -575,7 +575,7 @@
 
 		se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1,
 			serial_no=serial_no, basic_rate=100, do_not_submit=True)
-		self.assertRaises(SerialNoDuplicateError, se.submit)
+		se.submit()
 
 		dn.cancel()
 		pr1.cancel()
@@ -1011,6 +1011,7 @@
 	pr.currency = args.currency or "INR"
 	pr.is_return = args.is_return
 	pr.return_against = args.return_against
+	pr.apply_putaway_rule = args.apply_putaway_rule
 	qty = args.qty or 5
 	received_qty = args.received_qty or qty
 	rejected_qty = args.rejected_qty or flt(received_qty) - flt(qty)
@@ -1026,6 +1027,7 @@
 		"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "",
 		"rate": args.rate if args.rate != None else 50,
 		"conversion_factor": args.conversion_factor or 1.0,
+		"stock_qty": flt(qty) * (flt(args.conversion_factor) or 1.0),
 		"serial_no": args.serial_no,
 		"stock_uom": args.stock_uom or "_Test UOM",
 		"uom": uom,
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 871b255..e991192 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -76,6 +76,8 @@
   "purchase_order_item",
   "material_request_item",
   "purchase_receipt_item",
+  "delivery_note_item",
+  "putaway_rule",
   "section_break_45",
   "allow_zero_valuation_rate",
   "bom",
@@ -818,11 +820,12 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:parent.is_internal_supplier",
    "fieldname": "from_warehouse",
    "fieldtype": "Link",
    "hidden": 1,
    "ignore_user_permissions": 1,
-   "label": "Supplier Warehouse",
+   "label": "From Warehouse",
    "options": "Warehouse"
   },
   {
@@ -840,6 +843,15 @@
    "fieldtype": "Column Break"
   },
   {
+   "fieldname": "putaway_rule",
+   "fieldtype": "Link",
+   "label": "Putaway Rule",
+   "no_copy": 1,
+   "options": "Putaway Rule",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
    "fieldname": "tracking_section",
    "fieldtype": "Section Break"
   },
@@ -861,12 +873,20 @@
    "fieldtype": "Float",
    "label": "Received Qty in Stock UOM",
    "print_hide": 1
+  },
+  {
+   "fieldname": "delivery_note_item",
+   "fieldtype": "Data",
+   "label": "Delivery Note Item",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-12-07 10:00:38.204294",
+ "modified": "2020-12-26 16:50:56.479347",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/putaway_rule/__init__.py b/erpnext/stock/doctype/putaway_rule/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/__init__.py
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.js b/erpnext/stock/doctype/putaway_rule/putaway_rule.js
new file mode 100644
index 0000000..e056920
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.js
@@ -0,0 +1,43 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Putaway Rule', {
+	setup: function(frm) {
+		frm.set_query("warehouse", function() {
+			return {
+				"filters": {
+					"company": frm.doc.company,
+					"is_group": 0
+				}
+			};
+		});
+	},
+
+	uom: function(frm) {
+		if (frm.doc.item_code && frm.doc.uom) {
+			return frm.call({
+				method: "erpnext.stock.get_item_details.get_conversion_factor",
+				args: {
+					item_code: frm.doc.item_code,
+					uom: frm.doc.uom
+				},
+				callback: function(r) {
+					if (!r.exc) {
+						let stock_capacity = flt(frm.doc.capacity) * flt(r.message.conversion_factor);
+						frm.set_value('conversion_factor', r.message.conversion_factor);
+						frm.set_value('stock_capacity', stock_capacity);
+					}
+				}
+			});
+		}
+	},
+
+	capacity: function(frm) {
+		let stock_capacity = flt(frm.doc.capacity) * flt(frm.doc.conversion_factor);
+		frm.set_value('stock_capacity', stock_capacity);
+	}
+
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.json b/erpnext/stock/doctype/putaway_rule/putaway_rule.json
new file mode 100644
index 0000000..a003f49
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.json
@@ -0,0 +1,160 @@
+{
+ "actions": [],
+ "autoname": "PUT-.####",
+ "creation": "2020-11-09 11:39:46.489501",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "disable",
+  "item_code",
+  "item_name",
+  "warehouse",
+  "priority",
+  "col_break_capacity",
+  "company",
+  "capacity",
+  "uom",
+  "conversion_factor",
+  "stock_uom",
+  "stock_capacity"
+ ],
+ "fields": [
+  {
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "in_standard_filter": 1,
+   "label": "Item",
+   "options": "Item",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "item_code.item_name",
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "label": "Item Name",
+   "read_only": 1
+  },
+  {
+   "fieldname": "warehouse",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Warehouse",
+   "options": "Warehouse",
+   "reqd": 1
+  },
+  {
+   "fieldname": "col_break_capacity",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "capacity",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Capacity",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "item_code.stock_uom",
+   "fieldname": "stock_uom",
+   "fieldtype": "Link",
+   "label": "Stock UOM",
+   "options": "UOM",
+   "read_only": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "priority",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Priority"
+  },
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_standard_filter": 1,
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "disable",
+   "fieldtype": "Check",
+   "label": "Disable"
+  },
+  {
+   "fieldname": "uom",
+   "fieldtype": "Link",
+   "label": "UOM",
+   "no_copy": 1,
+   "options": "UOM"
+  },
+  {
+   "fieldname": "stock_capacity",
+   "fieldtype": "Float",
+   "label": "Capacity in Stock UOM",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "conversion_factor",
+   "fieldtype": "Float",
+   "label": "Conversion Factor",
+   "no_copy": 1,
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-11-25 20:39:19.973437",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Putaway Rule",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock User",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "permlevel": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "item_code",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
new file mode 100644
index 0000000..ea26cac
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
@@ -0,0 +1,235 @@
+# -*- 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
+import copy
+import json
+from collections import defaultdict
+from six import string_types
+from frappe import _
+from frappe.utils import flt, floor, nowdate, cint
+from frappe.model.document import Document
+from erpnext.stock.utils import get_stock_balance
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+class PutawayRule(Document):
+	def validate(self):
+		self.validate_duplicate_rule()
+		self.validate_warehouse_and_company()
+		self.validate_capacity()
+		self.validate_priority()
+		self.set_stock_capacity()
+
+	def validate_duplicate_rule(self):
+		existing_rule = frappe.db.exists("Putaway Rule", {"item_code": self.item_code, "warehouse": self.warehouse})
+		if existing_rule and existing_rule != self.name:
+			frappe.throw(_("Putaway Rule already exists for Item {0} in Warehouse {1}.")
+				.format(frappe.bold(self.item_code), frappe.bold(self.warehouse)),
+				title=_("Duplicate"))
+
+	def validate_priority(self):
+		if self.priority < 1:
+			frappe.throw(_("Priority cannot be lesser than 1."), title=_("Invalid Priority"))
+
+	def validate_warehouse_and_company(self):
+		company = frappe.db.get_value("Warehouse", self.warehouse, "company")
+		if company != self.company:
+			frappe.throw(_("Warehouse {0} does not belong to Company {1}.")
+				.format(frappe.bold(self.warehouse), frappe.bold(self.company)),
+				title=_("Invalid Warehouse"))
+
+	def validate_capacity(self):
+		stock_uom = frappe.db.get_value("Item", self.item_code, "stock_uom")
+		balance_qty = get_stock_balance(self.item_code, self.warehouse, nowdate())
+
+		if flt(self.stock_capacity) < flt(balance_qty):
+			frappe.throw(_("Warehouse Capacity for Item '{0}' must be greater than the existing stock level of {1} {2}.")
+				.format(self.item_code, frappe.bold(balance_qty), stock_uom),
+				title=_("Insufficient Capacity"))
+
+		if not self.capacity:
+			frappe.throw(_("Capacity must be greater than 0"), title=_("Invalid"))
+
+	def set_stock_capacity(self):
+		self.stock_capacity = (flt(self.conversion_factor) or 1) * flt(self.capacity)
+
+@frappe.whitelist()
+def get_available_putaway_capacity(rule):
+	stock_capacity, item_code, warehouse = frappe.db.get_value("Putaway Rule", rule,
+		["stock_capacity", "item_code", "warehouse"])
+	balance_qty = get_stock_balance(item_code, warehouse, nowdate())
+	free_space = flt(stock_capacity) - flt(balance_qty)
+	return free_space if free_space > 0 else 0
+
+@frappe.whitelist()
+def apply_putaway_rule(doctype, items, company, sync=None, purpose=None):
+	""" Applies Putaway Rule on line items.
+
+		items: List of Purchase Receipt/Stock Entry Items
+		company: Company in the Purchase Receipt/Stock Entry
+		doctype: Doctype to apply rule on
+		purpose: Purpose of Stock Entry
+		sync (optional): Sync with client side only for client side calls
+	"""
+	if isinstance(items, string_types):
+		items = json.loads(items)
+
+	items_not_accomodated, updated_table = [], []
+	item_wise_rules = defaultdict(list)
+
+	for item in items:
+		if isinstance(item, dict):
+			item = frappe._dict(item)
+
+		source_warehouse = item.get("s_warehouse")
+		serial_nos = get_serial_nos(item.get("serial_no"))
+		item.conversion_factor = flt(item.conversion_factor) or 1.0
+		pending_qty, item_code = flt(item.qty), item.item_code
+		pending_stock_qty = flt(item.transfer_qty) if doctype == "Stock Entry" else flt(item.stock_qty)
+		uom_must_be_whole_number = frappe.db.get_value('UOM', item.uom, 'must_be_whole_number')
+
+		if not pending_qty or not item_code:
+			updated_table = add_row(item, pending_qty, source_warehouse or item.warehouse, updated_table)
+			continue
+
+		at_capacity, rules = get_ordered_putaway_rules(item_code, company, source_warehouse=source_warehouse)
+
+		if not rules:
+			warehouse = source_warehouse or item.warehouse
+			if at_capacity:
+				# rules available, but no free space
+				items_not_accomodated.append([item_code, pending_qty])
+			else:
+				updated_table = add_row(item, pending_qty, warehouse, updated_table)
+			continue
+
+		# maintain item/item-warehouse wise rules, to handle if item is entered twice
+		# in the table, due to different price, etc.
+		key = item_code
+		if doctype == "Stock Entry" and purpose == "Material Transfer" and source_warehouse:
+			key = (item_code, source_warehouse)
+
+		if not item_wise_rules[key]:
+			item_wise_rules[key] = rules
+
+		for rule in item_wise_rules[key]:
+			if pending_stock_qty > 0 and rule.free_space:
+				stock_qty_to_allocate = flt(rule.free_space) if pending_stock_qty >= flt(rule.free_space) else pending_stock_qty
+				qty_to_allocate = stock_qty_to_allocate / item.conversion_factor
+
+				if uom_must_be_whole_number:
+					qty_to_allocate = floor(qty_to_allocate)
+					stock_qty_to_allocate = qty_to_allocate * item.conversion_factor
+
+				if not qty_to_allocate: break
+
+				updated_table = add_row(item, qty_to_allocate, rule.warehouse, updated_table,
+					rule.name, serial_nos=serial_nos)
+
+				pending_stock_qty -= stock_qty_to_allocate
+				pending_qty -= qty_to_allocate
+				rule["free_space"] -= stock_qty_to_allocate
+
+				if not pending_stock_qty > 0: break
+
+		# if pending qty after applying all rules, add row without warehouse
+		if pending_stock_qty > 0:
+			items_not_accomodated.append([item.item_code, pending_qty])
+
+	if items_not_accomodated:
+		show_unassigned_items_message(items_not_accomodated)
+
+	items[:] = updated_table if updated_table else items # modify items table
+
+	if sync and json.loads(sync): # sync with client side
+		return items
+
+def get_ordered_putaway_rules(item_code, company, source_warehouse=None):
+	"""Returns an ordered list of putaway rules to apply on an item."""
+	filters = {
+		"item_code": item_code,
+		"company": company,
+		"disable": 0
+	}
+	if source_warehouse:
+		filters.update({"warehouse": ["!=", source_warehouse]})
+
+	rules = frappe.get_all("Putaway Rule",
+		fields=["name", "item_code", "stock_capacity", "priority", "warehouse"],
+		filters=filters,
+		order_by="priority asc, capacity desc")
+
+	if not rules:
+		return False, None
+
+	vacant_rules = []
+	for rule in rules:
+		balance_qty = get_stock_balance(rule.item_code, rule.warehouse, nowdate())
+		free_space = flt(rule.stock_capacity) - flt(balance_qty)
+		if free_space > 0:
+			rule["free_space"] = free_space
+			vacant_rules.append(rule)
+
+	if not vacant_rules:
+		# After iterating through rules, if no rules are left
+		# then there is not enough space left in any rule
+		return True, None
+
+	vacant_rules = sorted(vacant_rules, key = lambda i: (i['priority'], -i['free_space']))
+
+	return False, vacant_rules
+
+def add_row(item, to_allocate, warehouse, updated_table, rule=None, serial_nos=None):
+	new_updated_table_row = copy.deepcopy(item)
+	new_updated_table_row.idx = 1 if not updated_table else cint(updated_table[-1].idx) + 1
+	new_updated_table_row.name = None
+	new_updated_table_row.qty = to_allocate
+
+	if item.doctype == "Stock Entry Detail":
+		new_updated_table_row.t_warehouse = warehouse
+		new_updated_table_row.transfer_qty = flt(to_allocate) * flt(new_updated_table_row.conversion_factor)
+	else:
+		new_updated_table_row.stock_qty = flt(to_allocate) * flt(new_updated_table_row.conversion_factor)
+		new_updated_table_row.warehouse = warehouse
+		new_updated_table_row.rejected_qty = 0
+		new_updated_table_row.received_qty = to_allocate
+
+	if rule:
+		new_updated_table_row.putaway_rule = rule
+	if serial_nos:
+		new_updated_table_row.serial_no = get_serial_nos_to_allocate(serial_nos, to_allocate)
+
+	updated_table.append(new_updated_table_row)
+	return updated_table
+
+def show_unassigned_items_message(items_not_accomodated):
+	msg = _("The following Items, having Putaway Rules, could not be accomodated:") + "<br><br>"
+	formatted_item_rows = ""
+
+	for entry in items_not_accomodated:
+		item_link = frappe.utils.get_link_to_form("Item", entry[0])
+		formatted_item_rows += """
+			<td>{0}</td>
+			<td>{1}</td>
+		</tr>""".format(item_link, frappe.bold(entry[1]))
+
+	msg += """
+		<table class="table">
+			<thead>
+				<td>{0}</td>
+				<td>{1}</td>
+			</thead>
+			{2}
+		</table>
+	""".format(_("Item"), _("Unassigned Qty"), formatted_item_rows)
+
+	frappe.msgprint(msg, title=_("Insufficient Capacity"), is_minimizable=True, wide=True)
+
+def get_serial_nos_to_allocate(serial_nos, to_allocate):
+	if serial_nos:
+		allocated_serial_nos = serial_nos[0: cint(to_allocate)]
+		serial_nos[:] = serial_nos[cint(to_allocate):] # pop out allocated serial nos and modify list
+		return "\n".join(allocated_serial_nos) if allocated_serial_nos else ""
+	else: return ""
\ No newline at end of file
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule_list.js b/erpnext/stock/doctype/putaway_rule/putaway_rule_list.js
new file mode 100644
index 0000000..725e91e
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule_list.js
@@ -0,0 +1,18 @@
+frappe.listview_settings['Putaway Rule'] = {
+	add_fields: ["disable"],
+	get_indicator: (doc) => {
+		if (doc.disable) {
+			return [__("Disabled"), "darkgrey", "disable,=,1"];
+		} else {
+			return [__("Active"), "blue", "disable,=,0"];
+		}
+	},
+
+	reports: [
+		{
+			name: 'Warehouse Capacity Summary',
+			report_type: 'Page',
+			route: 'warehouse-capacity-summary'
+		}
+	]
+};
diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
new file mode 100644
index 0000000..86f7dc3
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
@@ -0,0 +1,389 @@
+# -*- 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.stock.doctype.item.test_item import make_item
+from erpnext.stock.get_item_details import get_conversion_factor
+from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+from erpnext.stock.doctype.batch.test_batch import make_new_batch
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+
+class TestPutawayRule(unittest.TestCase):
+	def setUp(self):
+		if not frappe.db.exists("Item", "_Rice"):
+			make_item("_Rice", {
+				'is_stock_item': 1,
+				'has_batch_no' : 1,
+				'create_new_batch': 1,
+				'stock_uom': 'Kg'
+			})
+
+		if not frappe.db.exists("Warehouse", {"warehouse_name": "Rack 1"}):
+			create_warehouse("Rack 1")
+		if not frappe.db.exists("Warehouse", {"warehouse_name": "Rack 2"}):
+			create_warehouse("Rack 2")
+
+		self.warehouse_1 = frappe.db.get_value("Warehouse", {"warehouse_name": "Rack 1"})
+		self.warehouse_2 = frappe.db.get_value("Warehouse", {"warehouse_name": "Rack 2"})
+
+		if not frappe.db.exists("UOM", "Bag"):
+			new_uom = frappe.new_doc("UOM")
+			new_uom.uom_name = "Bag"
+			new_uom.save()
+
+	def test_putaway_rules_priority(self):
+		"""Test if rule is applied by priority, irrespective of free space."""
+		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
+			uom="Kg")
+		rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=300,
+			uom="Kg", priority=2)
+
+		pr = make_purchase_receipt(item_code="_Rice", qty=300, apply_putaway_rule=1,
+			do_not_submit=1)
+		self.assertEqual(len(pr.items), 2)
+		self.assertEqual(pr.items[0].qty, 200)
+		self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
+		self.assertEqual(pr.items[1].qty, 100)
+		self.assertEqual(pr.items[1].warehouse, self.warehouse_2)
+
+		pr.delete()
+		rule_1.delete()
+		rule_2.delete()
+
+	def test_putaway_rules_with_same_priority(self):
+		"""Test if rule with more free space is applied,
+		among two rules with same priority and capacity."""
+		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=500,
+			uom="Kg")
+		rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=500,
+			uom="Kg")
+
+		# out of 500 kg capacity, occupy 100 kg in warehouse_1
+		stock_receipt = make_stock_entry(item_code="_Rice", target=self.warehouse_1, qty=100, basic_rate=50)
+
+		pr = make_purchase_receipt(item_code="_Rice", qty=700, apply_putaway_rule=1,
+			do_not_submit=1)
+		self.assertEqual(len(pr.items), 2)
+		self.assertEqual(pr.items[0].qty, 500)
+		# warehouse_2 has 500 kg free space, it is given priority
+		self.assertEqual(pr.items[0].warehouse, self.warehouse_2)
+		self.assertEqual(pr.items[1].qty, 200)
+		# warehouse_1 has 400 kg free space, it is given less priority
+		self.assertEqual(pr.items[1].warehouse, self.warehouse_1)
+
+		stock_receipt.cancel()
+		pr.delete()
+		rule_1.delete()
+		rule_2.delete()
+
+	def test_putaway_rules_with_insufficient_capacity(self):
+		"""Test if qty exceeding capacity, is handled."""
+		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=100,
+			uom="Kg")
+		rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=200,
+			uom="Kg")
+
+		pr = make_purchase_receipt(item_code="_Rice", qty=350, apply_putaway_rule=1,
+			do_not_submit=1)
+		self.assertEqual(len(pr.items), 2)
+		self.assertEqual(pr.items[0].qty, 200)
+		self.assertEqual(pr.items[0].warehouse, self.warehouse_2)
+		self.assertEqual(pr.items[1].qty, 100)
+		self.assertEqual(pr.items[1].warehouse, self.warehouse_1)
+		# total 300 assigned, 50 unassigned
+
+		pr.delete()
+		rule_1.delete()
+		rule_2.delete()
+
+	def test_putaway_rules_multi_uom(self):
+		"""Test rules applied on uom other than stock uom."""
+		item = frappe.get_doc("Item", "_Rice")
+		if not frappe.db.get_value("UOM Conversion Detail", {"parent": "_Rice", "uom": "Bag"}):
+			item.append("uoms", {
+				"uom": "Bag",
+				"conversion_factor": 1000
+			})
+			item.save()
+
+		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=3,
+			uom="Bag")
+		self.assertEqual(rule_1.stock_capacity, 3000)
+		rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=4,
+			uom="Bag")
+		self.assertEqual(rule_2.stock_capacity, 4000)
+
+		# populate 'Rack 1' with 1 Bag, making the free space 2 Bags
+		stock_receipt = make_stock_entry(item_code="_Rice", target=self.warehouse_1, qty=1000, basic_rate=50)
+
+		pr = make_purchase_receipt(item_code="_Rice", qty=6, uom="Bag", stock_uom="Kg",
+			conversion_factor=1000, apply_putaway_rule=1, do_not_submit=1)
+		self.assertEqual(len(pr.items), 2)
+		self.assertEqual(pr.items[0].qty, 4)
+		self.assertEqual(pr.items[0].warehouse, self.warehouse_2)
+		self.assertEqual(pr.items[1].qty, 2)
+		self.assertEqual(pr.items[1].warehouse, self.warehouse_1)
+
+		stock_receipt.cancel()
+		pr.delete()
+		rule_1.delete()
+		rule_2.delete()
+
+	def test_putaway_rules_multi_uom_whole_uom(self):
+		"""Test if whole UOMs are handled."""
+		item = frappe.get_doc("Item", "_Rice")
+		if not frappe.db.get_value("UOM Conversion Detail", {"parent": "_Rice", "uom": "Bag"}):
+			item.append("uoms", {
+				"uom": "Bag",
+				"conversion_factor": 1000
+			})
+			item.save()
+
+		frappe.db.set_value("UOM", "Bag", "must_be_whole_number", 1)
+
+		# Putaway Rule in different UOM
+		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=1,
+			uom="Bag")
+		self.assertEqual(rule_1.stock_capacity, 1000)
+		# Putaway Rule in Stock UOM
+		rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=500)
+		self.assertEqual(rule_2.stock_capacity, 500)
+		# total capacity is 1500 Kg
+
+		pr = make_purchase_receipt(item_code="_Rice", qty=2, uom="Bag", stock_uom="Kg",
+			conversion_factor=1000, apply_putaway_rule=1, do_not_submit=1)
+		self.assertEqual(len(pr.items), 1)
+		self.assertEqual(pr.items[0].qty, 1)
+		self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
+		# leftover space was for 500 kg (0.5 Bag)
+		# Since Bag is a whole UOM, 1(out of 2) Bag will be unassigned
+
+		pr.delete()
+		rule_1.delete()
+		rule_2.delete()
+
+	def test_putaway_rules_with_reoccurring_item(self):
+		"""Test rules on same item entered multiple times with different rate."""
+		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
+			uom="Kg")
+		# total capacity is 200 Kg
+
+		pr = make_purchase_receipt(item_code="_Rice", qty=100, apply_putaway_rule=1,
+			do_not_submit=1)
+		pr.append("items", {
+			"item_code": "_Rice",
+			"warehouse": "_Test Warehouse - _TC",
+			"qty": 200,
+			"uom": "Kg",
+			"stock_uom": "Kg",
+			"stock_qty": 200,
+			"received_qty": 200,
+			"rate": 100,
+			"conversion_factor": 1.0,
+		}) # same item entered again in PR but with different rate
+		pr.save()
+		self.assertEqual(len(pr.items), 2)
+		self.assertEqual(pr.items[0].qty, 100)
+		self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
+		self.assertEqual(pr.items[0].putaway_rule, rule_1.name)
+		# same rule applied to second item row
+		# with previous assignment considered
+		self.assertEqual(pr.items[1].qty, 100) # 100 unassigned in second row from 200
+		self.assertEqual(pr.items[1].warehouse, self.warehouse_1)
+		self.assertEqual(pr.items[1].putaway_rule, rule_1.name)
+
+		pr.delete()
+		rule_1.delete()
+
+	def test_validate_over_receipt_in_warehouse(self):
+		"""Test if overreceipt is blocked in the presence of putaway rules."""
+		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
+			uom="Kg")
+
+		pr = make_purchase_receipt(item_code="_Rice", qty=300, apply_putaway_rule=1,
+			do_not_submit=1)
+		self.assertEqual(len(pr.items), 1)
+		self.assertEqual(pr.items[0].qty, 200) # 100 is unassigned fro 300 Kg
+		self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
+		self.assertEqual(pr.items[0].putaway_rule, rule_1.name)
+
+		# force overreceipt and disable apply putaway rule in PR
+		pr.items[0].qty = 300
+		pr.items[0].stock_qty = 300
+		pr.apply_putaway_rule = 0
+		self.assertRaises(frappe.ValidationError, pr.save)
+
+		pr.delete()
+		rule_1.delete()
+
+	def test_putaway_rule_on_stock_entry_material_transfer(self):
+		"""Test if source warehouse is considered while applying rules."""
+		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
+			uom="Kg") # higher priority
+		rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=100,
+			uom="Kg", priority=2)
+
+		stock_entry = make_stock_entry(item_code="_Rice", source=self.warehouse_1, qty=200,
+			target="_Test Warehouse - _TC", purpose="Material Transfer",
+			apply_putaway_rule=1, do_not_submit=1)
+
+		stock_entry_item = stock_entry.get("items")[0]
+
+		# since source warehouse is Rack 1, rule 1 (for Rack 1) will be avoided
+		# even though it has more free space and higher priority
+		self.assertEqual(stock_entry_item.t_warehouse, self.warehouse_2)
+		self.assertEqual(stock_entry_item.qty, 100) # unassigned 100 out of 200 Kg
+		self.assertEqual(stock_entry_item.putaway_rule, rule_2.name)
+
+		stock_entry.delete()
+		rule_1.delete()
+		rule_2.delete()
+
+	def test_putaway_rule_on_stock_entry_material_transfer_reoccuring_item(self):
+		"""Test if reoccuring item is correctly considered."""
+		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=300,
+			uom="Kg")
+		rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=600,
+			uom="Kg", priority=2)
+
+		# create SE with first row having source warehouse as Rack 2
+		stock_entry = make_stock_entry(item_code="_Rice", source=self.warehouse_2, qty=200,
+			target="_Test Warehouse - _TC", purpose="Material Transfer",
+			apply_putaway_rule=1, do_not_submit=1)
+
+		# Add rows with source warehouse as Rack 1
+		stock_entry.extend("items", [
+			{
+				"item_code": "_Rice",
+				"s_warehouse": self.warehouse_1,
+				"t_warehouse": "_Test Warehouse - _TC",
+				"qty": 100,
+				"basic_rate": 50,
+				"conversion_factor": 1.0,
+				"transfer_qty": 100
+			},
+			{
+				"item_code": "_Rice",
+				"s_warehouse": self.warehouse_1,
+				"t_warehouse": "_Test Warehouse - _TC",
+				"qty": 200,
+				"basic_rate": 60,
+				"conversion_factor": 1.0,
+				"transfer_qty": 200
+			}
+		])
+
+		stock_entry.save()
+
+		# since source warehouse was Rack 2, exclude rule_2
+		self.assertEqual(stock_entry.items[0].t_warehouse, self.warehouse_1)
+		self.assertEqual(stock_entry.items[0].qty, 200)
+		self.assertEqual(stock_entry.items[0].putaway_rule, rule_1.name)
+
+		# since source warehouse was Rack 1, exclude rule_1 even though it has
+		# higher priority
+		self.assertEqual(stock_entry.items[1].t_warehouse, self.warehouse_2)
+		self.assertEqual(stock_entry.items[1].qty, 100)
+		self.assertEqual(stock_entry.items[1].putaway_rule, rule_2.name)
+
+		self.assertEqual(stock_entry.items[2].t_warehouse, self.warehouse_2)
+		self.assertEqual(stock_entry.items[2].qty, 200)
+		self.assertEqual(stock_entry.items[2].putaway_rule, rule_2.name)
+
+		stock_entry.delete()
+		rule_1.delete()
+		rule_2.delete()
+
+	def test_putaway_rule_on_stock_entry_material_transfer_batch_serial_item(self):
+		"""Test if batch and serial items are split correctly."""
+		if not frappe.db.exists("Item", "Water Bottle"):
+			make_item("Water Bottle", {
+				"is_stock_item": 1,
+				"has_batch_no" : 1,
+				"create_new_batch": 1,
+				"has_serial_no": 1,
+				"serial_no_series": "BOTTL-.####",
+				"stock_uom": "Nos"
+			})
+
+		rule_1 = create_putaway_rule(item_code="Water Bottle", warehouse=self.warehouse_1, capacity=3,
+			uom="Nos")
+		rule_2 = create_putaway_rule(item_code="Water Bottle", warehouse=self.warehouse_2, capacity=2,
+		uom="Nos")
+
+		make_new_batch(batch_id="BOTTL-BATCH-1", item_code="Water Bottle")
+
+		pr = make_purchase_receipt(item_code="Water Bottle", qty=5, do_not_submit=1)
+		pr.items[0].batch_no = "BOTTL-BATCH-1"
+		pr.save()
+		pr.submit()
+
+		serial_nos = frappe.get_list("Serial No", filters={"purchase_document_no": pr.name, "status": "Active"})
+		serial_nos = [d.name for d in serial_nos]
+
+		stock_entry = make_stock_entry(item_code="Water Bottle", source="_Test Warehouse - _TC", qty=5,
+			target="Finished Goods - _TC", purpose="Material Transfer",
+			apply_putaway_rule=1, do_not_save=1)
+		stock_entry.items[0].batch_no = "BOTTL-BATCH-1"
+		stock_entry.items[0].serial_no = "\n".join(serial_nos)
+		stock_entry.save()
+
+		self.assertEqual(stock_entry.items[0].t_warehouse, self.warehouse_1)
+		self.assertEqual(stock_entry.items[0].qty, 3)
+		self.assertEqual(stock_entry.items[0].putaway_rule, rule_1.name)
+		self.assertEqual(stock_entry.items[0].serial_no, "\n".join(serial_nos[:3]))
+		self.assertEqual(stock_entry.items[0].batch_no, "BOTTL-BATCH-1")
+
+		self.assertEqual(stock_entry.items[1].t_warehouse, self.warehouse_2)
+		self.assertEqual(stock_entry.items[1].qty, 2)
+		self.assertEqual(stock_entry.items[1].putaway_rule, rule_2.name)
+		self.assertEqual(stock_entry.items[1].serial_no, "\n".join(serial_nos[3:]))
+		self.assertEqual(stock_entry.items[1].batch_no, "BOTTL-BATCH-1")
+
+		stock_entry.delete()
+		pr.cancel()
+		rule_1.delete()
+		rule_2.delete()
+
+	def test_putaway_rule_on_stock_entry_material_receipt(self):
+		"""Test if rules are applied in Stock Entry of type Receipt."""
+		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
+			uom="Kg") # more capacity
+		rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=100,
+			uom="Kg")
+
+		stock_entry = make_stock_entry(item_code="_Rice", qty=100,
+			target="_Test Warehouse - _TC", purpose="Material Receipt",
+			apply_putaway_rule=1, do_not_submit=1)
+
+		stock_entry_item = stock_entry.get("items")[0]
+
+		self.assertEqual(stock_entry_item.t_warehouse, self.warehouse_1)
+		self.assertEqual(stock_entry_item.qty, 100)
+		self.assertEqual(stock_entry_item.putaway_rule, rule_1.name)
+
+		stock_entry.delete()
+		rule_1.delete()
+		rule_2.delete()
+
+def create_putaway_rule(**args):
+	args = frappe._dict(args)
+	putaway = frappe.new_doc("Putaway Rule")
+
+	putaway.disable = args.disable or 0
+	putaway.company = args.company or "_Test Company"
+	putaway.item_code = args.item or args.item_code or "_Test Item"
+	putaway.warehouse = args.warehouse
+	putaway.priority = args.priority or 1
+	putaway.capacity = args.capacity or 1
+	putaway.stock_uom = frappe.db.get_value("Item", putaway.item_code, "stock_uom")
+	putaway.uom = args.uom or putaway.stock_uom
+	putaway.conversion_factor = get_conversion_factor(putaway.item_code, putaway.uom)['conversion_factor']
+
+	if not args.do_not_save:
+		putaway.save()
+
+	return putaway
\ No newline at end of file
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js
index 03e3de1..f7565fd 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js
@@ -4,6 +4,55 @@
 cur_frm.cscript.refresh = cur_frm.cscript.inspection_type;
 
 frappe.ui.form.on("Quality Inspection", {
+
+	setup: function(frm) {
+		frm.set_query("batch_no", function() {
+			return {
+				filters: {
+					"item": frm.doc.item_code
+				}
+			};
+		});
+
+		// Serial No based on item_code
+		frm.set_query("item_serial_no", function() {
+			let filters = {};
+			if (frm.doc.item_code) {
+				filters = {
+					'item_code': frm.doc.item_code
+				};
+			}
+			return { filters: filters };
+		});
+
+		// item code based on GRN/DN
+		frm.set_query("item_code", function(doc) {
+			let doctype = doc.reference_type;
+
+			if (doc.reference_type !== "Job Card") {
+				doctype = (doc.reference_type == "Stock Entry") ?
+					"Stock Entry Detail" : doc.reference_type + " Item";
+			}
+
+			if (doc.reference_type && doc.reference_name) {
+				let filters = {
+					"from": doctype,
+					"inspection_type": doc.inspection_type
+				};
+
+				if (doc.reference_type == doctype)
+					filters["reference_name"] = doc.reference_name;
+				else
+					filters["parent"] = doc.reference_name;
+
+				return {
+					query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
+					filters: filters
+				};
+			}
+		});
+	},
+
 	refresh: function(frm) {
 		// Ignore cancellation of reference doctype on cancel all.
 		frm.ignore_doctypes_on_cancel_all = [frm.doc.reference_type];
@@ -31,55 +80,5 @@
 				}
 			});
 		}
-	}
-})
-
-// item code based on GRN/DN
-cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) {
-	let doctype = doc.reference_type;
-
-	if (doc.reference_type !== "Job Card") {
-		doctype = (doc.reference_type == "Stock Entry") ?
-			"Stock Entry Detail" : doc.reference_type + " Item";
-	}
-
-	if (doc.reference_type && doc.reference_name) {
-		let filters = {
-			"from": doctype,
-			"inspection_type": doc.inspection_type
-		};
-
-		if (doc.reference_type == doctype)
-			filters["reference_name"] = doc.reference_name;
-		else
-			filters["parent"] = doc.reference_name;
-
-		return {
-			query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
-			filters: filters
-		};
-	}
-},
-
-// Serial No based on item_code
-cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) {
-	var filters = {};
-	if (doc.item_code) {
-		filters = {
-			'item_code': doc.item_code
-		}
-	}
-	return { filters: filters }
-}
-
-cur_frm.set_query("batch_no", function(doc) {
-	return {
-		filters: {
-			"item": doc.item_code
-		}
-	}
-})
-
-cur_frm.add_fetch('item_code', 'item_name', 'item_name');
-cur_frm.add_fetch('item_code', 'description', 'description');
-
+	},
+});
\ No newline at end of file
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
index f6d7619..edfe7e9 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
@@ -136,6 +136,7 @@
    "width": "50%"
   },
   {
+   "fetch_from": "item_code.item_name",
    "fieldname": "item_name",
    "fieldtype": "Data",
    "in_global_search": 1,
@@ -143,6 +144,7 @@
    "read_only": 1
   },
   {
+   "fetch_from": "item_code.description",
    "fieldname": "description",
    "fieldtype": "Small Text",
    "label": "Description",
@@ -236,7 +238,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-19 17:06:05.409963",
+ "modified": "2020-12-18 19:59:55.710300",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Quality Inspection",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index ae4eb9b..58b1eca 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -6,7 +6,7 @@
 from frappe.model.document import Document
 from frappe.model.mapper import get_mapped_doc
 from frappe import _
-from frappe.utils import flt
+from frappe.utils import flt, cint
 from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template \
 	import get_template_details
 
@@ -16,7 +16,7 @@
 			self.get_item_specification_details()
 
 		if self.readings:
-			self.set_status_based_on_acceptance_formula()
+			self.inspect_and_set_status()
 
 	def get_item_specification_details(self):
 		if not self.quality_inspection_template:
@@ -29,9 +29,7 @@
 		parameters = get_template_details(self.quality_inspection_template)
 		for d in parameters:
 			child = self.append('readings', {})
-			child.specification = d.specification
-			child.value = d.value
-			child.acceptance_formula = d.acceptance_formula
+			child.update(d)
 			child.status = "Accepted"
 
 	def get_quality_inspection_template(self):
@@ -69,35 +67,98 @@
 				doctype = 'Stock Entry Detail'
 
 			if self.reference_type and self.reference_name:
+				conditions = ""
+				if self.batch_no and self.docstatus == 1:
+					conditions += " and t1.batch_no = '%s'"%(self.batch_no)
+
+				if self.docstatus == 2: # if cancel, then remove qi link wherever same name
+					conditions += " and t1.quality_inspection = '%s'"%(self.name)
+
 				frappe.db.sql("""
-					UPDATE `tab{child_doc}` t1, `tab{parent_doc}` t2
-					SET t1.quality_inspection = %s, t2.modified = %s
-					WHERE t1.parent = %s and t1.item_code = %s and t1.parent = t2.name
-				""".format(parent_doc=self.reference_type, child_doc=doctype),
+					UPDATE
+						`tab{child_doc}` t1, `tab{parent_doc}` t2
+					SET
+						t1.quality_inspection = %s, t2.modified = %s
+					WHERE
+						t1.parent = %s
+						and t1.item_code = %s
+						and t1.parent = t2.name
+						{conditions}
+				""".format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions),
 					(quality_inspection, self.modified, self.reference_name, self.item_code))
 
-	def set_status_based_on_acceptance_formula(self):
+	def inspect_and_set_status(self):
 		for reading in self.readings:
-			if not reading.acceptance_formula: continue
+			if not reading.manual_inspection: # dont auto set status if manual
+				if reading.formula_based_criteria:
+					self.set_status_based_on_acceptance_formula(reading)
+				else:
+					# if not formula based check acceptance values set
+					self.set_status_based_on_acceptance_values(reading)
 
-			condition = reading.acceptance_formula
-			data = {}
+	def set_status_based_on_acceptance_values(self, reading):
+		if not cint(reading.numeric):
+			result = reading.get("reading_value") == reading.get("value")
+		else:
+			# numeric readings
+			result = self.min_max_criteria_passed(reading)
+
+		reading.status = "Accepted" if result else "Rejected"
+
+	def min_max_criteria_passed(self, reading):
+		"""Determine whether all readings fall in the acceptable range."""
+		for i in range(1, 11):
+			reading_value = reading.get("reading_" + str(i))
+			if reading_value is not None and reading_value.strip():
+				result = flt(reading.get("min_value")) <= flt(reading_value) <= flt(reading.get("max_value"))
+				if not result: return False
+		return True
+
+	def set_status_based_on_acceptance_formula(self, reading):
+		if not reading.acceptance_formula:
+			frappe.throw(_("Row #{0}: Acceptance Criteria Formula is required.").format(reading.idx),
+				title=_("Missing Formula"))
+
+		condition = reading.acceptance_formula
+		data = self.get_formula_evaluation_data(reading)
+
+		try:
+			result = frappe.safe_eval(condition, None, data)
+			reading.status = "Accepted" if result else "Rejected"
+		except NameError as e:
+			field = frappe.bold(e.args[0].split()[1])
+			frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.")
+				.format(reading.idx, field),
+				title=_("Invalid Formula"))
+		except Exception:
+			frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx),
+				title=_("Invalid Formula"))
+
+	def get_formula_evaluation_data(self, reading):
+		data = {}
+		if not cint(reading.numeric):
+			data = {"reading_value": reading.get("reading_value")}
+		else:
+			# numeric readings
 			for i in range(1, 11):
 				field = "reading_" + str(i)
-				data[field] = flt(reading.get(field)) or 0
+				data[field] = flt(reading.get(field))
+			data["mean"] = self.calculate_mean(reading)
 
-			try:
-				result = frappe.safe_eval(condition, None, data)
-				reading.status = "Accepted" if result else "Rejected"
-			except SyntaxError:
-				frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx),
-					title=_("Invalid Formula"))
-			except NameError as e:
-				field = frappe.bold(e.args[0].split()[1])
-				frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.")
-					.format(reading.idx, field),
-					title=_("Invalid Formula"))
+		return data
 
+	def calculate_mean(self, reading):
+		"""Calculate mean of all non-empty readings."""
+		from statistics import mean
+		readings_list = []
+
+		for i in range(1, 11):
+			reading_value = reading.get("reading_" + str(i))
+			if reading_value is not None and reading_value.strip():
+				readings_list.append(flt(reading_value))
+
+		actual_mean = mean(readings_list) if readings_list else 0
+		return actual_mean
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
index 2c40009..a7dfc9e 100644
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
@@ -44,24 +44,61 @@
 		qa.delete()
 		dn.delete()
 
+	def test_value_based_qi_readings(self):
+		# Test QI based on acceptance values (Non formula)
+		dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
+		readings = [{
+			"specification": "Iron Content", # numeric reading
+			"min_value": 0.1,
+			"max_value": 0.9,
+			"reading_1": "0.4"
+		},
+		{
+			"specification": "Particle Inspection Needed", # non-numeric reading
+			"numeric": 0,
+			"value": "Yes",
+			"reading_value": "Yes"
+		}]
+
+		qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name,
+			readings=readings, do_not_save=True)
+		qa.save()
+
+		# status must be auto set as per formula
+		self.assertEqual(qa.readings[0].status, "Accepted")
+		self.assertEqual(qa.readings[1].status, "Accepted")
+
+		qa.delete()
+		dn.delete()
+
 	def test_formula_based_qi_readings(self):
 		dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
 		readings = [{
-			"specification": "Iron Content",
+			"specification": "Iron Content", # numeric reading
+			"formula_based_criteria": 1,
 			"acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50",
-			"reading_1": 0.4
+			"reading_1": "0.4"
 		},
 		{
-			"specification": "Calcium Content",
+			"specification": "Calcium Content", # numeric reading
+			"formula_based_criteria": 1,
 			"acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50",
-			"reading_1": 0.7
+			"reading_1": "0.7"
 		},
 		{
-			"specification": "Mg Content",
-			"acceptance_formula": "(reading_1 + reading_2 + reading_3) / 3 < 0.9",
-			"reading_1": 0.5,
-			"reading_2": 0.7,
+			"specification": "Mg Content", # numeric reading
+			"formula_based_criteria": 1,
+			"acceptance_formula": "mean < 0.9",
+			"reading_1": "0.5",
+			"reading_2": "0.7",
 			"reading_3": "random text" # check if random string input causes issues
+		},
+		{
+			"specification": "Calcium Content", # non-numeric reading
+			"formula_based_criteria": 1,
+			"numeric": 0,
+			"acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')",
+			"reading_value": "Grade B"
 		}]
 
 		qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name,
@@ -72,6 +109,7 @@
 		self.assertEqual(qa.readings[0].status, "Accepted")
 		self.assertEqual(qa.readings[1].status, "Rejected")
 		self.assertEqual(qa.readings[2].status, "Accepted")
+		self.assertEqual(qa.readings[3].status, "Accepted")
 
 		qa.delete()
 		dn.delete()
@@ -86,11 +124,20 @@
 	qa.item_code = args.item_code or "_Test Item with QA"
 	qa.sample_size = 1
 	qa.inspected_by = frappe.session.user
+	qa.status = args.status or "Accepted"
 
-	readings = args.readings or {"specification": "Size", "status": args.status}
+	if not args.readings:
+		create_quality_inspection_parameter("Size")
+		readings = {"specification": "Size", "min_value": 0, "max_value": 10}
+	else:
+		readings = args.readings
+
+	if args.status == "Rejected":
+		readings["reading_1"] = "12" # status is auto set in child on save
 
 	if isinstance(readings, list):
 		for entry in readings:
+			create_quality_inspection_parameter(entry["specification"])
 			qa.append("readings", entry)
 	else:
 		qa.append("readings", readings)
@@ -101,3 +148,11 @@
 			qa.submit()
 
 	return qa
+
+def create_quality_inspection_parameter(parameter):
+	if not frappe.db.exists("Quality Inspection Parameter", parameter):
+		frappe.get_doc({
+			"doctype": "Quality Inspection Parameter",
+			"parameter": parameter,
+			"description": parameter
+		}).insert()
\ No newline at end of file
diff --git a/erpnext/stock/doctype/quality_inspection_parameter/__init__.py b/erpnext/stock/doctype/quality_inspection_parameter/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_parameter/__init__.py
diff --git a/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.js b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.js
new file mode 100644
index 0000000..47c7e11
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Quality Inspection Parameter', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json
new file mode 100644
index 0000000..0b5a9b5
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json
@@ -0,0 +1,86 @@
+{
+ "actions": [],
+ "autoname": "field:parameter",
+ "creation": "2020-12-28 17:06:00.254129",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "parameter",
+  "description"
+ ],
+ "fields": [
+  {
+   "fieldname": "parameter",
+   "fieldtype": "Data",
+   "label": "Parameter",
+   "unique": 1
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Text Editor",
+   "label": "Description"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-12-28 18:06:54.897317",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Quality Inspection Parameter",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock User",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Quality Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Manufacturing User",
+   "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/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.py b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.py
new file mode 100644
index 0000000..8678422
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.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 QualityInspectionParameter(Document):
+	pass
diff --git a/erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.py b/erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.py
new file mode 100644
index 0000000..cefdc08
--- /dev/null
+++ b/erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestQualityInspectionParameter(unittest.TestCase):
+	pass
diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
index c1976dd..35d58ef 100644
--- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
+++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
@@ -7,21 +7,28 @@
  "engine": "InnoDB",
  "field_order": [
   "specification",
-  "value",
   "status",
+  "value",
+  "numeric",
+  "manual_inspection",
   "column_break_4",
+  "min_value",
+  "max_value",
+  "formula_based_criteria",
   "acceptance_formula",
   "section_break_3",
+  "reading_value",
+  "section_break_14",
   "reading_1",
   "reading_2",
   "reading_3",
-  "column_break_10",
   "reading_4",
+  "column_break_10",
   "reading_5",
   "reading_6",
-  "column_break_14",
   "reading_7",
   "reading_8",
+  "column_break_14",
   "reading_9",
   "reading_10"
  ],
@@ -29,24 +36,25 @@
   {
    "columns": 3,
    "fieldname": "specification",
-   "fieldtype": "Data",
+   "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Parameter",
    "oldfieldname": "specification",
    "oldfieldtype": "Data",
+   "options": "Quality Inspection Parameter",
    "reqd": 1
   },
   {
    "columns": 2,
+   "depends_on": "eval:(!doc.formula_based_criteria && !doc.numeric)",
    "fieldname": "value",
    "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Acceptance Criteria",
+   "label": "Acceptance Criteria Value",
    "oldfieldname": "value",
    "oldfieldtype": "Data"
   },
   {
-   "columns": 1,
+   "columns": 2,
    "fieldname": "reading_1",
    "fieldtype": "Data",
    "in_list_view": 1,
@@ -58,7 +66,6 @@
    "columns": 1,
    "fieldname": "reading_2",
    "fieldtype": "Data",
-   "in_list_view": 1,
    "label": "Reading 2",
    "oldfieldname": "reading_2",
    "oldfieldtype": "Data"
@@ -67,7 +74,6 @@
    "columns": 1,
    "fieldname": "reading_3",
    "fieldtype": "Data",
-   "in_list_view": 1,
    "label": "Reading 3",
    "oldfieldname": "reading_3",
    "oldfieldtype": "Data"
@@ -130,18 +136,21 @@
    "label": "Status",
    "oldfieldname": "status",
    "oldfieldtype": "Select",
-   "options": "Accepted\nRejected"
+   "options": "\nAccepted\nRejected"
   },
   {
+   "depends_on": "eval:!doc.numeric",
    "fieldname": "section_break_3",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "Value Based Inspection"
   },
   {
    "fieldname": "column_break_4",
    "fieldtype": "Column Break"
   },
   {
-   "description": "Simple Python formula based on numeric Readings.<br> Example 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nExample 2: <b>(reading_1 + reading_2) / 2 &lt; 10</b>",
+   "depends_on": "formula_based_criteria",
+   "description": "Simple Python formula applied on Reading fields.<br> Numeric eg. 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nNumeric eg. 2: <b>mean &gt; 3.5</b> (mean of populated fields)<br>\nValue based eg.:  <b>reading_value in (\"A\", \"B\", \"C\")</b>",
    "fieldname": "acceptance_formula",
    "fieldtype": "Code",
    "label": "Acceptance Criteria Formula"
@@ -153,12 +162,60 @@
   {
    "fieldname": "column_break_14",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "formula_based_criteria",
+   "fieldtype": "Check",
+   "label": "Formula Based Criteria"
+  },
+  {
+   "depends_on": "eval:(!doc.formula_based_criteria && doc.numeric)",
+   "description": "Applied on each reading.",
+   "fieldname": "min_value",
+   "fieldtype": "Float",
+   "label": "Minimum Value"
+  },
+  {
+   "depends_on": "eval:(!doc.formula_based_criteria && doc.numeric)",
+   "description": "Applied on each reading.",
+   "fieldname": "max_value",
+   "fieldtype": "Float",
+   "label": "Maximum Value"
+  },
+  {
+   "columns": 2,
+   "depends_on": "eval:!doc.numeric",
+   "fieldname": "reading_value",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Reading Value"
+  },
+  {
+   "depends_on": "numeric",
+   "fieldname": "section_break_14",
+   "fieldtype": "Section Break",
+   "label": "Numeric Inspection"
+  },
+  {
+   "default": "0",
+   "description": "Set the status manually.",
+   "fieldname": "manual_inspection",
+   "fieldtype": "Check",
+   "label": "Manual Inspection"
+  },
+  {
+   "default": "1",
+   "fieldname": "numeric",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "Numeric"
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-11-16 16:34:29.947856",
+ "modified": "2021-02-01 19:46:22.138018",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Quality Inspection Reading",
diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
index e284846..01d2031 100644
--- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
+++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
@@ -13,6 +13,7 @@
 	if not template: return []
 
 	return frappe.get_all('Item Quality Inspection Parameter',
-		fields=["specification", "value", "acceptance_formula"],
+		fields=["specification", "value", "acceptance_formula",
+			"numeric", "formula_based_criteria", "min_value", "max_value"],
 		filters={'parenttype': 'Quality Inspection Template', 'parent': template},
 		order_by="idx")
\ No newline at end of file
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
index e429cd5..b3e4286 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
@@ -31,7 +31,7 @@
 		}
 	},
 	refresh: function(frm) {
-		if (frm.doc.status == "Failed") {
+		if (frm.doc.status == "Failed" && frm.doc.docstatus==1) {
 			frm.add_custom_button(__('Restart'), function () {
 				frm.trigger("restart_reposting");
 			}).addClass("btn-primary");
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 2d89996..e65913b 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -6,7 +6,7 @@
 import json
 
 from frappe.model.naming import make_autoname
-from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate
+from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate, get_link_to_form
 from erpnext.stock.get_item_details import get_reserved_qty_for_so
 
 from frappe import _, ValidationError
@@ -241,7 +241,7 @@
 			for serial_no in serial_nos:
 				if frappe.db.exists("Serial No", serial_no):
 					sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
-						"delivery_document_no", "delivery_document_type", "warehouse",
+						"delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type",
 						"purchase_document_no", "company"], as_dict=1)
 
 					if sr.item_code!=sle.item_code:
@@ -249,9 +249,10 @@
 							frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no,
 								sle.item_code), SerialNoItemError)
 
-					if cint(sle.actual_qty) > 0 and has_duplicate_serial_no(sr, sle):
-						frappe.throw(_("Serial No {0} has already been received").format(serial_no),
-							SerialNoDuplicateError)
+					if cint(sle.actual_qty) > 0 and has_serial_no_exists(sr, sle):
+						doc_name = frappe.bold(get_link_to_form(sr.purchase_document_type, sr.purchase_document_no))
+						frappe.throw(_("Serial No {0} has already been received in the {1} #{2}")
+							.format(frappe.bold(serial_no), sr.purchase_document_type, doc_name), SerialNoDuplicateError)
 
 					if (sr.delivery_document_no and sle.voucher_type not in ['Stock Entry', 'Stock Reconciliation']
 						and sle.voucher_type == sr.delivery_document_type):
@@ -348,7 +349,7 @@
 		frappe.throw(_("""{0} Serial No {1} cannot be delivered""")
 			.format(msg, sr.name))
 
-def has_duplicate_serial_no(sn, sle):
+def has_serial_no_exists(sn, sle):
 	if (sn.warehouse and not sle.skip_serial_no_validaiton
 		and sle.voucher_type != 'Stock Reconciliation'):
 		return True
@@ -358,12 +359,13 @@
 
 	status = False
 	if sn.purchase_document_no:
-		if sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and \
-			sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]:
+		if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and
+			sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]):
 			status = True
 
-		if status and sle.voucher_type == 'Stock Entry' and \
-			frappe.db.get_value('Stock Entry', sle.voucher_no, 'purpose') != 'Material Receipt':
+		# If status is receipt then system will allow to in-ward the delivered serial no
+		if (status and sle.voucher_type == "Stock Entry" and frappe.db.get_value("Stock Entry",
+			sle.voucher_no, "purpose") in ("Material Receipt", "Material Transfer")):
 			status = False
 
 	return status
@@ -419,7 +421,7 @@
 		if is_new:
 			created_numbers.append(sr.name)
 
-	form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers))
+	form_links = list(map(lambda d: get_link_to_form('Serial No', d), created_numbers))
 
 	# Setting up tranlated title field for all cases
 	singular_title = _("Serial Number Created")
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 98116ec..274bbc2 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -1,9 +1,20 @@
 // 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");
+
+{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %};
 
 frappe.ui.form.on('Stock Entry', {
 	setup: function(frm) {
+		frm.set_indicator_formatter('item_code', function(doc) {
+			if (!doc.s_warehouse) {
+				return 'blue';
+			} else {
+				return (doc.qty<=doc.actual_qty) ? 'green' : 'orange';
+			}
+		});
+
 		frm.set_query('work_order', function() {
 			return {
 				filters: [
@@ -86,17 +97,9 @@
 			}
 		});
 
-		frm.set_query("expense_account", "additional_costs", function() {
-			return {
-				query: "erpnext.controllers.queries.tax_account_query",
-				filters: {
-					"account_type": ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"],
-					"company": frm.doc.company
-				}
-			};
-		});
 
 		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 +315,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);
 		}
 	},
 
@@ -524,7 +529,6 @@
 				})
 			);
 		}
-		
 		for (let i in frm.doc.items) {
 			let item = frm.doc.items[i];
 
@@ -547,7 +551,7 @@
 
 	calculate_total_additional_costs: function(frm) {
 		const total_additional_costs = frappe.utils.sum(
-			(frm.doc.additional_costs || []).map(function(c) { return flt(c.amount); })
+			(frm.doc.additional_costs || []).map(function(c) { return flt(c.base_amount); })
 		);
 
 		frm.set_value("total_additional_costs",
@@ -580,8 +584,12 @@
 				}
 			});
 		}
+	},
+
+	apply_putaway_rule: function (frm) {
+		if (frm.doc.apply_putaway_rule) erpnext.apply_putaway_rule(frm, frm.doc.purpose);
 	}
-})
+});
 
 frappe.ui.form.on('Stock Entry Detail', {
 	qty: function(frm, cdt, cdn) {
@@ -675,7 +683,13 @@
 						});
 						refresh_field("items");
 
-						if (!d.serial_no) {
+						let no_batch_serial_number_value = !d.serial_no;
+						if (d.has_batch_no && !d.has_serial_no) {
+							// check only batch_no for batched item
+							no_batch_serial_number_value = !d.batch_no;
+						}
+
+						if (no_batch_serial_number_value) {
 							erpnext.stock.select_batch_and_serial_no(frm, d);
 						}
 					}
@@ -716,8 +730,18 @@
 };
 
 frappe.ui.form.on('Landed Cost Taxes and Charges', {
-	amount: function(frm) {
-		frm.events.calculate_amount(frm);
+	amount: function(frm, cdt, cdn) {
+		frm.events.set_base_amount(frm, cdt, cdn);
+
+		// Adding this check because same table in used in LCV
+		// This causes an error if you try to post an LCV immediately after a Stock Entry
+		if (frm.doc.doctype == 'Stock Entry') {
+			frm.events.calculate_amount(frm);
+		}
+	},
+
+	expense_account: function(frm, cdt, cdn) {
+		frm.events.set_account_currency(frm, cdt, cdn);
 	}
 });
 
@@ -765,15 +789,6 @@
 			}
 		}
 
-		this.frm.set_indicator_formatter('item_code',
-			function(doc) {
-				if (!doc.s_warehouse) {
-					return 'blue';
-				} else {
-					return (doc.qty<=doc.actual_qty) ? "green" : "orange"
-				}
-			})
-
 		this.frm.add_fetch("purchase_order", "supplier", "supplier");
 
 		frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'supplier', doctype: 'Supplier' }
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index 5aed081..98c047a 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -27,6 +27,7 @@
   "set_posting_time",
   "inspection_required",
   "from_bom",
+  "apply_putaway_rule",
   "sb1",
   "bom_no",
   "fg_completed_qty",
@@ -640,6 +641,13 @@
    "fieldtype": "Check",
    "label": "Add to Transit",
    "no_copy": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:in_list([\"Material Transfer\", \"Material Receipt\"], doc.purpose)",
+   "fieldname": "apply_putaway_rule",
+   "fieldtype": "Check",
+   "label": "Apply Putaway Rule"
   }
  ],
  "icon": "fa fa-file-text",
@@ -647,7 +655,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-09-09 12:59:02.508943",
+ "modified": "2020-12-09 14:58:13.267321",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 6a0a363..d77b70f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -19,6 +19,7 @@
 from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit, get_serial_nos
 from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError
 from erpnext.accounts.general_ledger import process_gl_map
+from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
 import json
 
 from six import string_types, itervalues, iteritems
@@ -42,6 +43,14 @@
 		for item in self.get("items"):
 			item.update(get_bin_details(item.item_code, item.s_warehouse))
 
+	def before_validate(self):
+		from erpnext.stock.doctype.putaway_rule.putaway_rule import apply_putaway_rule
+		apply_rule = self.apply_putaway_rule and (self.purpose in ["Material Transfer", "Material Receipt"])
+
+		if self.get("items") and apply_rule:
+			apply_putaway_rule(self.doctype, self.get("items"), self.company,
+				purpose=self.purpose)
+
 	def validate(self):
 		self.pro_doc = frappe._dict()
 		if self.work_order:
@@ -79,6 +88,7 @@
 		self.validate_serialized_batch()
 		self.set_actual_qty()
 		self.calculate_rate_and_amount()
+		self.validate_putaway_capacity()
 
 	def on_submit(self):
 		self.update_stock_ledger()
@@ -186,7 +196,7 @@
 					and (sed.t_warehouse is null or sed.t_warehouse = '')""", self.project, as_list=1)
 
 			amount = amount[0][0] if amount else 0
-			additional_costs = frappe.db.sql(""" select ifnull(sum(sed.amount), 0)
+			additional_costs = frappe.db.sql(""" select ifnull(sum(sed.base_amount), 0)
 				from
 					`tabStock Entry` se, `tabLanded Cost Taxes and Charges` sed
 				where
@@ -259,11 +269,16 @@
 								item_code.append(item.item_code)
 
 	def validate_fg_completed_qty(self):
+		item_wise_qty = {}
 		if self.purpose == "Manufacture" and self.work_order:
 			for d in self.items:
-				if d.is_finished_item and d.qty != self.fg_completed_qty:
-					frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different")
-						.format(d.qty, self.fg_completed_qty))
+				if d.is_finished_item:
+					item_wise_qty.setdefault(d.item_code, []).append(d.qty)
+
+		for item_code, qty_list in iteritems(item_wise_qty):
+			if self.fg_completed_qty != sum(qty_list):
+				frappe.throw(_("The finished product {0} quantity {1} and For Quantity {2} cannot be different")
+					.format(frappe.bold(item_code), frappe.bold(sum(qty_list)), frappe.bold(self.fg_completed_qty)))
 
 	def validate_difference_account(self):
 		if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
@@ -319,7 +334,7 @@
 
 			if self.purpose == "Manufacture":
 				if validate_for_manufacture:
-					if d.bom_no:
+					if d.is_finished_item or d.is_scrap_item:
 						d.s_warehouse = None
 						if not d.t_warehouse:
 							frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
@@ -431,6 +446,7 @@
 
 	def calculate_rate_and_amount(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
 		self.set_basic_rate(reset_outgoing_rate, raise_error_if_no_rate)
+		init_landed_taxes_and_totals(self)
 		self.distribute_additional_costs()
 		self.update_valuation_rate()
 		self.set_total_incoming_outgoing_value()
@@ -519,7 +535,7 @@
 		if not any([d.item_code for d in self.items if d.t_warehouse]):
 			self.additional_costs = []
 
-		self.total_additional_costs = sum([flt(t.amount) for t in self.get("additional_costs")])
+		self.total_additional_costs = sum([flt(t.base_amount) for t in self.get("additional_costs")])
 
 		if self.purpose in ("Repack", "Manufacture"):
 			incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.is_finished_item])
@@ -759,13 +775,19 @@
 			for d in self.get("items"):
 				if d.t_warehouse:
 					item_account_wise_additional_cost.setdefault((d.item_code, d.name), {})
-					item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, 0.0)
+					item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, {
+						"amount": 0.0,
+						"base_amount": 0.0
+					})
 
 					multiply_based_on = d.basic_amount if total_basic_amount else d.qty
 
-					item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \
+					item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \
 						flt(t.amount * multiply_based_on) / divide_based_on
 
+					item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \
+						flt(t.base_amount * multiply_based_on) / divide_based_on
+
 		if item_account_wise_additional_cost:
 			for d in self.get("items"):
 				for account, amount in iteritems(item_account_wise_additional_cost.get((d.item_code, d.name), {})):
@@ -776,7 +798,8 @@
 						"against": d.expense_account,
 						"cost_center": d.cost_center,
 						"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
-						"credit": amount
+						"credit_in_account_currency": flt(amount["amount"]),
+						"credit": flt(amount["base_amount"])
 					}, item=d))
 
 					gl_entries.append(self.get_gl_dict({
@@ -784,7 +807,7 @@
 						"against": account,
 						"cost_center": d.cost_center,
 						"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
-						"credit": -1 * amount # put it as negative credit instead of debit purposefully
+						"credit": -1 * amount['base_amount'] # put it as negative credit instead of debit purposefully
 					}, item=d))
 
 		return process_gl_map(gl_entries)
@@ -1177,7 +1200,10 @@
 							else:
 								qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
 					else:
-						qty = req_qty_each * flt(self.fg_completed_qty)
+						if self.flags.backflush_based_on == "Material Transferred for Manufacture":
+							qty = (item.qty/trans_qty) * flt(self.fg_completed_qty)
+						else:
+							qty = req_qty_each * flt(self.fg_completed_qty)
 
 			elif backflushed_materials.get(item.item_code):
 				for d in backflushed_materials.get(item.item_code):
@@ -1185,7 +1211,8 @@
 						if (qty > req_qty):
 							qty = (qty/trans_qty) * flt(self.fg_completed_qty)
 
-						if consumed_qty:
+						if consumed_qty and frappe.db.get_single_value("Manufacturing Settings",
+							"material_consumption"):
 							qty -= consumed_qty
 
 			if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
@@ -1328,9 +1355,6 @@
 						frappe.MappingMismatchError)
 				elif self.purpose == "Material Transfer" and self.add_to_transit:
 					continue
-				elif mreq_item.warehouse != (item.s_warehouse if self.purpose == "Material Issue" else item.t_warehouse):
-					frappe.throw(_("Warehouse for row {0} does not match Material Request").format(item.idx),
-						frappe.MappingMismatchError)
 
 	def validate_batch(self):
 		if self.purpose in ["Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor"]:
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
index b78c6be..b12a854 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
@@ -53,6 +53,8 @@
 		args.target = args.to_warehouse
 	if args.item_code:
 		args.item = args.item_code
+	if args.apply_putaway_rule:
+		s.apply_putaway_rule = args.apply_putaway_rule
 
 	if isinstance(args.qty, string_types):
 		if '.' in args.qty:
@@ -118,7 +120,8 @@
 		"t_warehouse": args.target,
 		"qty": args.qty,
 		"basic_rate": args.rate or args.basic_rate,
-		"conversion_factor": 1.0,
+		"conversion_factor": args.conversion_factor or 1.0,
+		"transfer_qty": flt(args.qty) * (flt(args.conversion_factor) or 1.0),
 		"serial_no": args.serial_no,
 		'batch_no': args.batch_no,
 		'cost_center': args.cost_center,
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index b78ae6d..988ae92 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "hash",
  "creation": "2013-03-29 18:22:12",
  "doctype": "DocType",
@@ -65,6 +66,7 @@
   "against_stock_entry",
   "ste_detail",
   "po_detail",
+  "putaway_rule",
   "column_break_51",
   "reference_purchase_receipt",
   "quality_inspection"
@@ -496,6 +498,16 @@
    "label": "Set Basic Rate Manually"
   },
   {
+   "depends_on": "eval:in_list([\"Material Transfer\", \"Material Receipt\"], parent.purpose)",
+   "fieldname": "putaway_rule",
+   "fieldtype": "Link",
+   "label": "Putaway Rule",
+   "no_copy": 1,
+   "options": "Putaway Rule",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
    "fieldname": "quantity_section",
    "fieldtype": "Section Break",
    "label": "Quantity"
@@ -526,7 +538,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-12-23 17:55:03.384138",
+ "modified": "2020-12-30 15:00:44.489442",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry Detail",
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/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 5b40292..f0a90f9 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -30,6 +30,7 @@
 		self.validate_data()
 		self.validate_expense_account()
 		self.set_total_qty_and_amount()
+		self.validate_putaway_capacity()
 
 		if self._action=="submit":
 			self.make_batches('warehouse')
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 859aea2..84af57b 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -16,6 +16,7 @@
   "action_if_quality_inspection_is_not_submitted",
   "show_barcode_field",
   "clean_description_html",
+  "disable_serial_no_and_batch_selector",
   "section_break_7",
   "auto_insert_price_list_rate_if_missing",
   "allow_negative_stock",
@@ -217,7 +218,7 @@
    "fieldname": "role_allowed_to_create_edit_back_dated_transactions",
    "fieldtype": "Link",
    "label": "Role Allowed to Create/Edit Back-dated Transactions",
-   "options": "User"
+   "options": "Role"
   },
   {
    "fieldname": "column_break_26",
@@ -227,6 +228,12 @@
    "fieldname": "control_historical_stock_transactions_section",
    "fieldtype": "Section Break",
    "label": "Control Historical Stock Transactions"
+  },
+  {
+   "default": "0",
+   "fieldname": "disable_serial_no_and_batch_selector",
+   "fieldtype": "Check",
+   "label": "Disable Serial No And Batch Selector"
   }
  ],
  "icon": "icon-cog",
@@ -234,7 +241,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-11-23 22:26:54.225608",
+ "modified": "2021-01-18 13:15:38.352796",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Settings",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 08f7a83..dfe8fea 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -74,7 +74,9 @@
 
 	update_party_blanket_order(args, out)
 
-	get_price_list_rate(args, item, out)
+	if not doc or cint(doc.get('is_return')) == 0:
+		# get price list rate only if the invoice is not a credit or debit note
+		get_price_list_rate(args, item, out)
 
 	if args.customer and cint(args.is_pos):
 		out.update(get_pos_profile_item_details(args.company, args))
@@ -672,6 +674,8 @@
 		and price_list=%(price_list)s
 		and ifnull(uom, '') in ('', %(uom)s)"""
 
+	conditions += "and ifnull(batch_no, '') in ('', %(batch_no)s)"
+
 	if not ignore_party:
 		if args.get("customer"):
 			conditions += " and customer=%(customer)s"
@@ -690,7 +694,7 @@
 
 	return frappe.db.sql(""" select name, price_list_rate, uom
 		from `tabItem Price` {conditions}
-		order by valid_from desc, uom desc """.format(conditions=conditions), args)
+		order by valid_from desc, batch_no desc, uom desc """.format(conditions=conditions), args)
 
 def get_price_list_rate_for(args, item_code):
 	"""
@@ -709,6 +713,7 @@
 			"uom": args.get('uom'),
 			"transaction_date": args.get('transaction_date'),
 			"posting_date": args.get('posting_date'),
+			"batch_no": args.get('batch_no')
 	}
 
 	item_price_data = 0
diff --git a/erpnext/stock/landed_taxes_and_charges_common.js b/erpnext/stock/landed_taxes_and_charges_common.js
new file mode 100644
index 0000000..f3f6196
--- /dev/null
+++ b/erpnext/stock/landed_taxes_and_charges_common.js
@@ -0,0 +1,62 @@
+let document_list = ['Landed Cost Voucher', 'Stock Entry'];
+
+document_list.forEach((doctype) => {
+	frappe.ui.form.on(doctype, {
+		refresh: function(frm) {
+			let tax_field = frm.doc.doctype == 'Landed Cost Voucher' ? 'taxes' : 'additional_costs';
+			frm.set_query("expense_account", tax_field, function() {
+				return {
+					filters: {
+						"account_type": ['in', ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"]],
+						"company": frm.doc.company
+					}
+				};
+			});
+		},
+
+		set_account_currency: function(frm, cdt, cdn) {
+			let row = locals[cdt][cdn];
+			if (row.expense_account) {
+				frappe.db.get_value('Account', row.expense_account, 'account_currency', function(value) {
+					frappe.model.set_value(cdt, cdn, "account_currency", value.account_currency);
+					frm.events.set_exchange_rate(frm, cdt, cdn);
+				});
+			}
+		},
+
+		set_exchange_rate: function(frm, cdt, cdn) {
+			let row = locals[cdt][cdn];
+			let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
+
+			if (row.account_currency == company_currency) {
+				row.exchange_rate = 1;
+				frm.set_df_property('taxes', 'hidden', 1, row.name, 'exchange_rate');
+			} else if (!row.exchange_rate || row.exchange_rate == 1) {
+				frm.set_df_property('taxes', 'hidden', 0, row.name, 'exchange_rate');
+				frappe.call({
+					method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_exchange_rate",
+					args: {
+						posting_date: frm.doc.posting_date,
+						account: row.expense_account,
+						account_currency: row.account_currency,
+						company: frm.doc.company
+					},
+					callback: function(r) {
+						if (r.message) {
+							frappe.model.set_value(cdt, cdn, "exchange_rate", r.message);
+						}
+					}
+				});
+			}
+
+			frm.refresh_field('taxes');
+		},
+
+		set_base_amount: function(frm, cdt, cdn) {
+			let row = locals[cdt][cdn];
+			frappe.model.set_value(cdt, cdn, "base_amount",
+				flt(flt(row.amount)*row.exchange_rate, precision("base_amount", row)));
+		}
+	});
+});
+
diff --git a/erpnext/stock/page/stock_balance/stock_balance.js b/erpnext/stock/page/stock_balance/stock_balance.js
index da21c6b..bddffd4 100644
--- a/erpnext/stock/page/stock_balance/stock_balance.js
+++ b/erpnext/stock/page/stock_balance/stock_balance.js
@@ -65,6 +65,9 @@
 	frappe.require('assets/js/item-dashboard.min.js', function() {
 		page.item_dashboard = new erpnext.stock.ItemDashboard({
 			parent: page.main,
+			page_length: 20,
+			method: 'erpnext.stock.dashboard.item_dashboard.get_data',
+			template: 'item_dashboard_list'
 		})
 
 		page.item_dashboard.before_refresh = function() {
diff --git a/erpnext/stock/page/warehouse_capacity_summary/__init__.py b/erpnext/stock/page/warehouse_capacity_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/page/warehouse_capacity_summary/__init__.py
diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html
new file mode 100644
index 0000000..90112c7
--- /dev/null
+++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html
@@ -0,0 +1,40 @@
+{% for d in data %}
+	<div class="dashboard-list-item" style="padding: 7px 15px;">
+		<div class="row">
+			<div class="col-sm-2 small" style="margin-top: 8px;">
+				<a data-type="warehouse" data-name="{{ d.warehouse }}">{{ d.warehouse }}</a>
+			</div>
+			<div class="col-sm-2 small" style="margin-top: 8px; ">
+				<a data-type="item" data-name="{{ d.item_code }}">{{ d.item_code }}</a>
+			</div>
+			<div class="col-sm-1 small" style="margin-top: 8px; ">
+				{{ d.stock_capacity }}
+			</div>
+			<div class="col-sm-2 small" style="margin-top: 8px; ">
+				{{ d.actual_qty }}
+			</div>
+			<div class="col-sm-2 small">
+				<div class="progress" title="Occupied Qty: {{ d.actual_qty }}" style="margin-bottom: 4px; height: 7px; margin-top: 14px;">
+					<div class="progress-bar" role="progressbar"
+						aria-valuenow="{{ d.percent_occupied }}"
+						aria-valuemin="0" aria-valuemax="100"
+						style="width:{{ d.percent_occupied }}%;
+						background-color: {{ d.color }}">
+					</div>
+				</div>
+			</div>
+			<div class="col-sm-1 small" style="margin-top: 8px;">
+				{{ d.percent_occupied }}%
+			</div>
+			{% if can_write %}
+			<div class="col-sm-1 text-right" style="margin-top: 2px;">
+				<button class="btn btn-default btn-xs btn-edit"
+				style="margin-top: 4px;margin-bottom: 4px;"
+				data-warehouse="{{ d.warehouse }}"
+				data-item="{{ escape(d.item_code) }}"
+				data-company="{{ escape(d.company) }}">{{ __("Edit Capacity") }}</a>
+			</div>
+			{% endif %}
+		</div>
+	</div>
+{% endfor %}
\ No newline at end of file
diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
new file mode 100644
index 0000000..b610e7d
--- /dev/null
+++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
@@ -0,0 +1,120 @@
+frappe.pages['warehouse-capacity-summary'].on_page_load = function(wrapper) {
+	var page = frappe.ui.make_app_page({
+		parent: wrapper,
+		title: 'Warehouse Capacity Summary',
+		single_column: true
+	});
+	page.set_secondary_action('Refresh', () => page.capacity_dashboard.refresh(), 'octicon octicon-sync');
+	page.start = 0;
+
+	page.company_field = page.add_field({
+		fieldname: 'company',
+		label: __('Company'),
+		fieldtype: 'Link',
+		options: 'Company',
+		reqd: 1,
+		default: frappe.defaults.get_default("company"),
+		change: function() {
+			page.capacity_dashboard.start = 0;
+			page.capacity_dashboard.refresh();
+		}
+	});
+
+	page.warehouse_field = page.add_field({
+		fieldname: 'warehouse',
+		label: __('Warehouse'),
+		fieldtype: 'Link',
+		options: 'Warehouse',
+		change: function() {
+			page.capacity_dashboard.start = 0;
+			page.capacity_dashboard.refresh();
+		}
+	});
+
+	page.item_field = page.add_field({
+		fieldname: 'item_code',
+		label: __('Item'),
+		fieldtype: 'Link',
+		options: 'Item',
+		change: function() {
+			page.capacity_dashboard.start = 0;
+			page.capacity_dashboard.refresh();
+		}
+	});
+
+	page.parent_warehouse_field = page.add_field({
+		fieldname: 'parent_warehouse',
+		label: __('Parent Warehouse'),
+		fieldtype: 'Link',
+		options: 'Warehouse',
+		get_query: function() {
+			return {
+				filters: {
+					"is_group": 1
+				}
+			};
+		},
+		change: function() {
+			page.capacity_dashboard.start = 0;
+			page.capacity_dashboard.refresh();
+		}
+	});
+
+	page.sort_selector = new frappe.ui.SortSelector({
+		parent: page.wrapper.find('.page-form'),
+		args: {
+			sort_by: 'stock_capacity',
+			sort_order: 'desc',
+			options: [
+				{fieldname: 'stock_capacity', label: __('Capacity (Stock UOM)')},
+				{fieldname: 'percent_occupied', label: __('% Occupied')},
+				{fieldname: 'actual_qty', label: __('Balance Qty (Stock ')}
+			]
+		},
+		change: function(sort_by, sort_order) {
+			page.capacity_dashboard.sort_by = sort_by;
+			page.capacity_dashboard.sort_order = sort_order;
+			page.capacity_dashboard.start = 0;
+			page.capacity_dashboard.refresh();
+		}
+	});
+
+	frappe.require('assets/js/item-dashboard.min.js', function() {
+		$(frappe.render_template('warehouse_capacity_summary_header')).appendTo(page.main);
+
+		page.capacity_dashboard = new erpnext.stock.ItemDashboard({
+			page_name: "warehouse-capacity-summary",
+			page_length: 10,
+			parent: page.main,
+			sort_by: 'stock_capacity',
+			sort_order: 'desc',
+			method: 'erpnext.stock.dashboard.warehouse_capacity_dashboard.get_data',
+			template: 'warehouse_capacity_summary'
+		});
+
+		page.capacity_dashboard.before_refresh = function() {
+			this.item_code = page.item_field.get_value();
+			this.warehouse = page.warehouse_field.get_value();
+			this.parent_warehouse = page.parent_warehouse_field.get_value();
+			this.company = page.company_field.get_value();
+		};
+
+		page.capacity_dashboard.refresh();
+
+		let setup_click = function(doctype) {
+			page.main.on('click', 'a[data-type="'+ doctype.toLowerCase() +'"]', function() {
+				var name = $(this).attr('data-name');
+				var field = page[doctype.toLowerCase() + '_field'];
+				if (field.get_value()===name) {
+					frappe.set_route('Form', doctype, name);
+				} else {
+					field.set_input(name);
+					page.capacity_dashboard.refresh();
+				}
+			});
+		};
+
+		setup_click('Item');
+		setup_click('Warehouse');
+	});
+};
\ No newline at end of file
diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.json b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.json
new file mode 100644
index 0000000..a6e5b45
--- /dev/null
+++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.json
@@ -0,0 +1,26 @@
+{
+ "content": null,
+ "creation": "2020-11-25 12:07:54.056208",
+ "docstatus": 0,
+ "doctype": "Page",
+ "idx": 0,
+ "modified": "2020-11-25 11:07:54.056208",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "warehouse-capacity-summary",
+ "owner": "Administrator",
+ "page_name": "Warehouse Capacity Summary",
+ "roles": [
+  {
+   "role": "Stock User"
+  },
+  {
+   "role": "Stock Manager"
+  }
+ ],
+ "script": null,
+ "standard": "Yes",
+ "style": null,
+ "system_page": 0,
+ "title": "Warehouse Capacity Summary"
+}
\ No newline at end of file
diff --git a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html
new file mode 100644
index 0000000..acaf180
--- /dev/null
+++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html
@@ -0,0 +1,19 @@
+<div class="dashboard-list-item" style="padding: 12px 15px;">
+	<div class="row">
+		<div class="col-sm-2 small text-muted" style="margin-top: 8px;">
+			Warehouse
+		</div>
+		<div class="col-sm-2 small text-muted" style="margin-top: 8px;">
+			Item
+		</div>
+		<div class="col-sm-1 small text-muted" style="margin-top: 8px;">
+			Stock Capacity
+		</div>
+		<div class="col-sm-2 small text-muted" style="margin-top: 8px;">
+			Balance Stock Qty
+		</div>
+		<div class="col-sm-2 small text-muted" style="margin-top: 8px;">
+			% Occupied
+		</div>
+	</div>
+</div>
\ No newline at end of file
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/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 5b9ada0..46919c8 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -41,7 +41,7 @@
 
 			if sle.get("actual_qty") or sle.get("voucher_type")=="Stock Reconciliation":
 				sle_doc = make_entry(sle, allow_negative_stock, via_landed_cost_voucher)
-			
+
 			args = sle_doc.as_dict()
 			update_bin(args, allow_negative_stock, via_landed_cost_voucher)
 
@@ -62,10 +62,10 @@
 	sle.submit()
 	return sle
 
-def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=False, via_landed_cost_voucher=False):
+def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False):
 	if not args and voucher_type and voucher_no:
 		args = get_args_for_voucher(voucher_type, voucher_no)
-	
+
 	distinct_item_warehouses = [(d.item_code, d.warehouse) for d in args]
 
 	i = 0
@@ -80,7 +80,7 @@
 		for item_wh, new_sle in iteritems(obj.new_items):
 			if item_wh not in distinct_item_warehouses:
 				args.append(new_sle)
-		
+
 		i += 1
 
 def get_args_for_voucher(voucher_type, voucher_no):
@@ -127,7 +127,7 @@
 		self.initialize_previous_data(self.args)
 
 		self.build()
-	
+
 	def get_precision(self):
 		company_base_currency = frappe.get_cached_value('Company',  self.company,  "default_currency")
 		self.precision = get_field_precision(frappe.get_meta("Stock Ledger Entry").get_field("stock_value"),
@@ -181,7 +181,7 @@
 				self.process_sle(sle)
 
 				if sle.dependant_sle_voucher_detail_no:
-					self.get_dependent_entries_to_fix(entries_to_fix, sle)
+					entries_to_fix = self.get_dependent_entries_to_fix(entries_to_fix, sle)
 
 		if self.exceptions:
 			self.raise_exceptions()
@@ -213,21 +213,23 @@
 		# includes current entry!
 		args = self.data[self.args.warehouse].previous_sle \
 			or frappe._dict({"item_code": self.item_code, "warehouse": self.args.warehouse})
-		
+
 		return list(self.get_sle_after_datetime(args))
 
 	def get_dependent_entries_to_fix(self, entries_to_fix, sle):
 		dependant_sle = get_sle_by_voucher_detail_no(sle.dependant_sle_voucher_detail_no,
 			excluded_sle=sle.name)
-		
+
 		if not dependant_sle:
-			return
+			return entries_to_fix
 		elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse:
-			return
-		elif dependant_sle.item_code != self.item_code \
-				and (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items:
-			self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle
-			return
+			return entries_to_fix
+		elif dependant_sle.item_code != self.item_code:
+			if (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items:
+				self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle
+			return entries_to_fix
+		elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data:
+			return entries_to_fix
 
 		self.initialize_previous_data(dependant_sle)
 
@@ -236,7 +238,7 @@
 		future_sle_for_dependant = list(self.get_sle_after_datetime(args))
 
 		entries_to_fix.extend(future_sle_for_dependant)
-		entries_to_fix = sorted(entries_to_fix, key=lambda k: k['timestamp'])
+		return sorted(entries_to_fix, key=lambda k: k['timestamp'])
 
 	def process_sle(self, sle):
 		# previous sle data for this warehouse
@@ -251,7 +253,7 @@
 
 		# Get dynamic incoming/outgoing rate
 		self.get_dynamic_incoming_outgoing_rate(sle)
-		
+
 		if sle.serial_no:
 			self.get_serialized_values(sle)
 			self.wh_data.qty_after_transaction += flt(sle.actual_qty)
@@ -329,7 +331,7 @@
 				rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code, voucher_detail_no=sle.voucher_detail_no)
 			else:
 				if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
-					rate_field = "valuation_rate" 
+					rate_field = "valuation_rate"
 				else:
 					rate_field = "incoming_rate"
 
@@ -344,7 +346,7 @@
 						ref_doctype = "Packed Item"
 					else:
 						ref_doctype = "Purchase Receipt Item Supplied"
-	
+
 					rate = frappe.db.get_value(ref_doctype, {"parent_detail_docname": sle.voucher_detail_no,
 						"item_code": sle.item_code}, rate_field)
 
@@ -374,7 +376,7 @@
 		stock_entry.db_update()
 		for d in stock_entry.items:
 			d.db_update()
-	
+
 	def update_rate_on_delivery_and_sales_return(self, sle, outgoing_rate):
 		# Update item's incoming rate on transaction
 		item_code = frappe.db.get_value(sle.voucher_type + " Item", sle.voucher_detail_no, "item_code")
@@ -487,7 +489,6 @@
 					self.wh_data.valuation_rate = new_stock_value / new_stock_qty
 				else:
 					self.wh_data.valuation_rate = sle.outgoing_rate
-
 		else:
 			if flt(self.wh_data.qty_after_transaction) >= 0 and sle.outgoing_rate:
 				self.wh_data.valuation_rate = sle.outgoing_rate
@@ -613,11 +614,11 @@
 				frappe.local.flags.currently_saving):
 
 				msg = _("{0} units of {1} needed in {2} to complete this transaction.").format(
-					abs(deficiency), frappe.get_desk_link('Item', self.item_code),
+					abs(deficiency), frappe.get_desk_link('Item', exceptions[0]["item_code"]),
 					frappe.get_desk_link('Warehouse', warehouse))
 			else:
 				msg = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format(
-					abs(deficiency), frappe.get_desk_link('Item', self.item_code),
+					abs(deficiency), frappe.get_desk_link('Item', exceptions[0]["item_code"]),
 					frappe.get_desk_link('Warehouse', warehouse),
 					exceptions[0]["posting_date"], exceptions[0]["posting_time"],
 					frappe.get_desk_link(exceptions[0]["voucher_type"], exceptions[0]["voucher_no"]))
@@ -631,7 +632,7 @@
 				frappe.throw(message, NegativeStockError, title='Insufficient Stock')
 			else:
 				raise NegativeStockError(message)
-	
+
 	def update_bin(self):
 		# update bin for each warehouse
 		for warehouse, data in iteritems(self.data):
@@ -762,25 +763,6 @@
 
 	return valuation_rate
 
-def update_qty_in_future_sle(args, allow_negative_stock=None):
-	frappe.db.sql("""
-		update `tabStock Ledger Entry`
-		set qty_after_transaction = qty_after_transaction + {qty}
-		where 
-			item_code = %(item_code)s
-			and warehouse = %(warehouse)s
-			and voucher_no != %(voucher_no)s
-			and is_cancelled = 0
-			and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)
-				or (
-					timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s)
-					and creation > %(creation)s
-				)
-			)
-	""".format(qty=args.actual_qty), args)
-
-	validate_negative_qty_in_future_sle(args, allow_negative_stock)
-
 def validate_negative_qty_in_future_sle(args, allow_negative_stock=None):
 	allow_negative_stock = allow_negative_stock \
 		or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
@@ -794,7 +776,7 @@
 				frappe.get_desk_link('Warehouse', args.warehouse),
 				sle[0]["posting_date"], sle[0]["posting_time"],
 				frappe.get_desk_link(sle[0]["voucher_type"], sle[0]["voucher_no"]))
-						
+
 			frappe.throw(message, NegativeStockError, title='Insufficient Stock')
 
 def get_future_sle_with_negative_qty(args):
@@ -803,12 +785,13 @@
 			qty_after_transaction, posting_date, posting_time,
 			voucher_type, voucher_no
 		from `tabStock Ledger Entry`
-		where 
+		where
 			item_code = %(item_code)s
 			and warehouse = %(warehouse)s
 			and voucher_no != %(voucher_no)s
 			and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
 			and is_cancelled = 0
-			and qty_after_transaction < 0
+			and qty_after_transaction + {0} < 0
+		order by timestamp(posting_date, posting_time) asc
 		limit 1
-	""", args, as_dict=1)
\ No newline at end of file
+	""".format(args.actual_qty), args, as_dict=1)
\ No newline at end of file
diff --git a/erpnext/support/desk_page/support/support.json b/erpnext/support/desk_page/support/support.json
index 28410f3..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]"
+   "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-08-11 15:49:34.307341",
+ "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..1ac2959 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -214,7 +214,10 @@
 
 	def before_insert(self):
 		if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
-			self.set_response_and_resolution_time()
+			if frappe.flags.in_test:
+				self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
+			else:
+				self.set_response_and_resolution_time()
 
 	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..3fdb10d
--- /dev/null
+++ b/erpnext/support/report/issue_analytics/issue_analytics.py
@@ -0,0 +1,221 @@
+# 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
+		)
+
+	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..fc6bb58
--- /dev/null
+++ b/erpnext/support/report/issue_analytics/test_issue_analytics.py
@@ -0,0 +1,214 @@
+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()
+		self.last_month = str(months[last_month_date.month - 1]).lower()
+		if current_month_date.year != last_month_date.year:
+			self.current_month += '_' + str(current_month_date.year)
+			self.last_month += '_' + 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/support/report/issue_summary/__init__.py b/erpnext/support/report/issue_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/support/report/issue_summary/__init__.py
diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js
new file mode 100644
index 0000000..684482a
--- /dev/null
+++ b/erpnext/support/report/issue_summary/issue_summary.js
@@ -0,0 +1,73 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Issue Summary"] = {
+	"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: "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"
+		}
+	]
+};
\ No newline at end of file
diff --git a/erpnext/support/report/issue_summary/issue_summary.json b/erpnext/support/report/issue_summary/issue_summary.json
new file mode 100644
index 0000000..b8a580c
--- /dev/null
+++ b/erpnext/support/report/issue_summary/issue_summary.json
@@ -0,0 +1,26 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2020-10-12 01:01:55.181777",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-10-12 14:54:55.655920",
+ "modified_by": "Administrator",
+ "module": "Support",
+ "name": "Issue Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Issue",
+ "report_name": "Issue Summary",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Support Team"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py
new file mode 100644
index 0000000..3d73531
--- /dev/null
+++ b/erpnext/support/report/issue_summary/issue_summary.py
@@ -0,0 +1,353 @@
+# 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 flt
+
+def execute(filters=None):
+	return IssueSummary(filters).run()
+
+class IssueSummary(object):
+	def __init__(self, filters=None):
+		self.filters = frappe._dict(filters or {})
+
+	def run(self):
+		self.get_columns()
+		self.get_data()
+		self.get_chart_data()
+		self.get_report_summary()
+
+		return self.columns, self.data, None, self.chart, self.report_summary
+
+	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
+			})
+
+		self.statuses = ['Open', 'Replied', 'Resolved', 'Closed']
+		for status in self.statuses:
+			self.columns.append({
+				'label': _(status),
+				'fieldname': scrub(status),
+				'fieldtype': 'Int',
+				'width': 80
+			})
+
+		self.columns.append({
+			'label': _('Total Issues'),
+			'fieldname': 'total_issues',
+			'fieldtype': 'Int',
+			'width': 100
+		})
+
+		self.sla_status_map = {
+			'SLA Failed': 'failed',
+			'SLA Fulfilled': 'fulfilled',
+			'SLA Ongoing': 'ongoing'
+		}
+
+		for label, fieldname in self.sla_status_map.items():
+			self.columns.append({
+				'label': _(label),
+				'fieldname': fieldname,
+				'fieldtype': 'Int',
+				'width': 100
+			})
+
+		self.metrics = ['Avg First Response Time', 'Avg Response Time', 'Avg Hold Time',
+			'Avg Resolution Time', 'Avg User Resolution Time']
+
+		for metric in self.metrics:
+			self.columns.append({
+				'label': _(metric),
+				'fieldname': scrub(metric),
+				'fieldtype': 'Duration',
+				'width': 170
+			})
+
+	def get_data(self):
+		self.get_issues()
+		self.get_rows()
+
+	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', 'status', 'avg_response_time',
+				'first_response_time', 'total_hold_time', 'user_resolution_time', 'resolution_time', 'agreement_status'],
+			filters=filters
+		)
+
+	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_summary_data()
+
+		for entity, data in iteritems(self.issue_summary_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}
+
+			for status in self.statuses:
+				count = flt(data.get(status, 0.0))
+				row[scrub(status)] = count
+
+			row['total_issues'] = data.get('total_issues', 0.0)
+
+			for sla_status in self.sla_status_map.values():
+				value = flt(data.get(sla_status), 0.0)
+				row[sla_status] = value
+
+			for metric in self.metrics:
+				value = flt(data.get(scrub(metric)), 0.0)
+				row[scrub(metric)] = value
+
+			self.data.append(row)
+
+	def get_summary_data(self):
+		self.issue_summary_data = frappe._dict()
+
+		for d in self.entries:
+			status = d.status
+			agreement_status = scrub(d.agreement_status)
+
+			if self.filters.based_on == 'Assigned To':
+				if d._assign:
+					for entry in json.loads(d._assign):
+						self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(status, 0.0)
+						self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(agreement_status, 0.0)
+						self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault('total_issues', 0.0)
+						self.issue_summary_data[entry][status] += 1
+						self.issue_summary_data[entry][agreement_status] += 1
+						self.issue_summary_data[entry]['total_issues'] += 1
+
+			else:
+				field = self.field_map.get(self.filters.based_on)
+				value = d.get(field)
+				if not value:
+					value = _('Not Specified')
+
+				self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(status, 0.0)
+				self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(agreement_status, 0.0)
+				self.issue_summary_data.setdefault(value, frappe._dict()).setdefault('total_issues', 0.0)
+				self.issue_summary_data[value][status] += 1
+				self.issue_summary_data[value][agreement_status] += 1
+				self.issue_summary_data[value]['total_issues'] += 1
+
+		self.get_metrics_data()
+
+	def get_metrics_data(self):
+		issues = []
+
+		metrics_list = ['avg_response_time', 'avg_first_response_time', 'avg_hold_time',
+			'avg_resolution_time', 'avg_user_resolution_time']
+
+		for entry in self.entries:
+			issues.append(entry.name)
+
+		field = self.field_map.get(self.filters.based_on)
+
+		if issues:
+			if self.filters.based_on == 'Assigned To':
+				assignment_map = frappe._dict()
+				for d in self.entries:
+					if d._assign:
+						for entry in json.loads(d._assign):
+							for metric in metrics_list:
+								self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(metric, 0.0)
+
+							self.issue_summary_data[entry]['avg_response_time'] += d.get('avg_response_time') or 0.0
+							self.issue_summary_data[entry]['avg_first_response_time'] += d.get('first_response_time') or 0.0
+							self.issue_summary_data[entry]['avg_hold_time'] += d.get('total_hold_time') or 0.0
+							self.issue_summary_data[entry]['avg_resolution_time'] += d.get('resolution_time') or 0.0
+							self.issue_summary_data[entry]['avg_user_resolution_time'] += d.get('user_resolution_time') or 0.0
+
+							if not assignment_map.get(entry):
+								assignment_map[entry] = 0
+							assignment_map[entry] += 1
+
+				for entry in assignment_map:
+					for metric in metrics_list:
+						self.issue_summary_data[entry][metric] /= flt(assignment_map.get(entry))
+
+			else:
+				data = frappe.db.sql("""
+					SELECT
+						{0}, AVG(first_response_time) as avg_frt,
+						AVG(avg_response_time) as avg_resp_time,
+						AVG(total_hold_time) as avg_hold_time,
+						AVG(resolution_time) as avg_resolution_time,
+						AVG(user_resolution_time) as avg_user_resolution_time
+					FROM `tabIssue`
+					WHERE
+						name IN %(issues)s
+					GROUP BY {0}
+				""".format(field), {'issues': issues}, as_dict=1)
+
+				for entry in data:
+					value = entry.get(field)
+					if not value:
+						value = _('Not Specified')
+
+					for metric in metrics_list:
+						self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(metric, 0.0)
+
+					self.issue_summary_data[value]['avg_response_time'] = entry.get('avg_resp_time') or 0.0
+					self.issue_summary_data[value]['avg_first_response_time'] = entry.get('avg_frt') or 0.0
+					self.issue_summary_data[value]['avg_hold_time'] = entry.get('avg_hold_time') or 0.0
+					self.issue_summary_data[value]['avg_resolution_time'] = entry.get('avg_resolution_time') or 0.0
+					self.issue_summary_data[value]['avg_user_resolution_time'] = entry.get('avg_user_resolution_time') or 0.0
+
+	def get_chart_data(self):
+		if not self.data:
+			return None
+
+		labels = []
+		open_issues = []
+		replied_issues = []
+		resolved_issues = []
+		closed_issues = []
+
+		entity = self.filters.based_on
+		entity_field = self.field_map.get(entity)
+		if entity == 'Assigned To':
+			entity_field = 'user'
+
+		for entry in self.data:
+			labels.append(entry.get(entity_field))
+			open_issues.append(entry.get('open'))
+			replied_issues.append(entry.get('replied'))
+			resolved_issues.append(entry.get('resolved'))
+			closed_issues.append(entry.get('closed'))
+
+		self.chart = {
+			'data': {
+				'labels': labels[:30],
+				'datasets': [
+					{
+						'name': 'Open',
+						'values': open_issues[:30]
+					},
+					{
+						'name': 'Replied',
+						'values': replied_issues[:30]
+					},
+					{
+						'name': 'Resolved',
+						'values': resolved_issues[:30]
+					},
+					{
+						'name': 'Closed',
+						'values': closed_issues[:30]
+					}
+				]
+			},
+			'type': 'bar',
+			'barOptions': {
+				'stacked': True
+			}
+		}
+
+	def get_report_summary(self):
+		if not self.data:
+			return None
+
+		open_issues = 0
+		replied = 0
+		resolved = 0
+		closed = 0
+
+		for entry in self.data:
+			open_issues += entry.get('open')
+			replied += entry.get('replied')
+			resolved += entry.get('resolved')
+			closed += entry.get('closed')
+
+		self.report_summary = [
+			{
+				'value': open_issues,
+				'indicator': 'Red',
+				'label': _('Open'),
+				'datatype': 'Int',
+			},
+			{
+				'value': replied,
+				'indicator': 'Grey',
+				'label': _('Replied'),
+				'datatype': 'Int',
+			},
+			{
+				'value': resolved,
+				'indicator': 'Green',
+				'label': _('Resolved'),
+				'datatype': 'Int',
+			},
+			{
+				'value': closed,
+				'indicator': 'Green',
+				'label': _('Closed'),
+				'datatype': 'Int',
+			}
+		]
+
diff --git a/erpnext/telephony/doctype/voice_call_settings/__init__.py b/erpnext/telephony/doctype/voice_call_settings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/__init__.py
diff --git a/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py b/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py
new file mode 100644
index 0000000..85d6add
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestVoiceCallSettings(unittest.TestCase):
+	pass
diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js
new file mode 100644
index 0000000..4a61b61
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Voice Call Settings', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json
new file mode 100644
index 0000000..25e55a2
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json
@@ -0,0 +1,124 @@
+{
+ "actions": [],
+ "autoname": "field:user",
+ "creation": "2020-12-08 16:52:40.590146",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "user",
+  "call_receiving_device",
+  "column_break_3",
+  "greeting_message",
+  "agent_busy_message",
+  "agent_unavailable_message"
+ ],
+ "fields": [
+  {
+   "fieldname": "user",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "User",
+   "options": "User",
+   "permlevel": 1,
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "greeting_message",
+   "fieldtype": "Data",
+   "label": "Greeting Message"
+  },
+  {
+   "fieldname": "agent_busy_message",
+   "fieldtype": "Data",
+   "label": "Agent Busy Message"
+  },
+  {
+   "fieldname": "agent_unavailable_message",
+   "fieldtype": "Data",
+   "label": "Agent Unavailable Message"
+  },
+  {
+   "default": "Computer",
+   "fieldname": "call_receiving_device",
+   "fieldtype": "Select",
+   "label": "Call Receiving Device",
+   "options": "Computer\nPhone"
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-12-14 18:49:34.600194",
+ "modified_by": "Administrator",
+ "module": "Telephony",
+ "name": "Voice Call Settings",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "All",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "permlevel": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "permlevel": 2,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "permlevel": 2,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "All",
+   "share": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.py
new file mode 100644
index 0000000..ad3bbf1
--- /dev/null
+++ b/erpnext/telephony/doctype/voice_call_settings/voice_call_settings.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 VoiceCallSettings(Document):
+	pass
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index 40a064f..74b2ae3 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -1,5 +1,9 @@
 {% extends "templates/web.html" %}
 
+{% block breadcrumbs %}
+	{% include "templates/includes/breadcrumbs.html" %}
+{% endblock %}
+
 {% block header %}<h1>{{ name }}</h1>{% endblock %}
 
 {% block page_content %}
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 %}
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index ea6b00f..5d8ee5c 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -40,7 +40,7 @@
 		<div class="col-md-{{ section.column_value }} mb-4">
 			<div class="card h-100 justify-content-between">
 				{% if card.image %}
-				<div class="website-image-lazy" data-class="card-img-top h-100" data-src="{{ card.image }}" data-alt="{{ card.title }}"></div>
+				<div class="website-image-lazy" data-class="card-img-top h-75" data-src="{{ card.image }}" data-alt="{{ card.title }}"></div>
 				{% endif %}
 				<div class="card-body">
 					<h5 class="card-title">{{ card.title }}</h5>
diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html
index 3033d15..876eaea 100644
--- a/erpnext/templates/pages/cart.html
+++ b/erpnext/templates/pages/cart.html
@@ -47,6 +47,9 @@
 
 	{% if doc.items %}
 		<div class="place-order-container">
+			<a class="btn btn-primary-light mr-2" href="/all-products">
+				{{ _("Continue Shopping") }}
+			</a>
 			{% if cart_settings.enable_checkout %}
 				<button class="btn btn-primary btn-place-order" type="button">
 					{{ _("Place Order") }}
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py
index 0394e4b..7d7793a 100644
--- a/erpnext/www/all-products/index.py
+++ b/erpnext/www/all-products/index.py
@@ -15,6 +15,9 @@
 
 	context.items = get_products_for_website(field_filters, attribute_filters, search)
 
+	# Add homepage as parent
+	context.parents = [{"name": frappe._("Home"), "route":"/"}]
+
 	product_settings = get_product_settings()
 	context.field_filters = get_field_filter_data() \
 		if product_settings.enable_field_filters else []