Merge pull request #29179 from rohitwaghchaure/fixed-incorrect-scrap-qty-in-manufacture

fix: incorrect scrap item qty
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index 8f93811..4d61f1f 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -40,6 +40,7 @@
         - HR
         - projects
         - support
+        - CRM
         - assets
         - integrations
         - quality
@@ -48,6 +49,7 @@
         - agriculture
         - education
         - non-profit
+        - other
     validations:
       required: true
 
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 0000000..fc3f06d
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,55 @@
+accounts:
+- 'erpnext/accounts/*'
+- 'erpnext/controllers/accounts_controller.py'
+- 'erpnext/controllers/taxes_and_totals.py'
+
+stock:
+- 'erpnext/stock/*'
+- 'erpnext/controllers/stock_controller.py'
+- 'erpnext/controllers/item_variant.py'
+
+assets:
+- 'erpnext/assets/*'
+
+regional:
+- 'erpnext/regional/*'
+
+selling:
+- 'erpnext/selling/*'
+- 'erpnext/controllers/selling_controller.py'
+
+buying:
+- 'erpnext/buying/*'
+- 'erpnext/controllers/buying_controller.py'
+
+support:
+- 'erpnext/support/*'
+
+POS:
+- 'pos*'
+
+ecommerce:
+- 'erpnext/e_commerce/*'
+
+maintenance:
+- 'erpnext/maintenance/*'
+
+manufacturing:
+- 'erpnext/manufacturing/*'
+
+crm:
+- 'erpnext/crm/*'
+
+HR:
+- 'erpnext/hr/*'
+
+payroll:
+- 'erpnext/payroll*'
+
+projects:
+- 'erpnext/projects/*'
+
+# Any python files modifed but no test files modified
+needs-tests:
+- any: ['erpnext/**/*.py']
+  all: ['!erpnext/**/test*.py']
diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml
index db46c56..b644568 100644
--- a/.github/workflows/docs-checker.yml
+++ b/.github/workflows/docs-checker.yml
@@ -12,7 +12,7 @@
       - name: 'Setup Environment'
         uses: actions/setup-python@v2
         with:
-          python-version: 3.6
+          python-version: 3.8
 
       - name: 'Clone repo'
         uses: actions/checkout@v2
diff --git a/.github/workflows/labeller.yml b/.github/workflows/labeller.yml
new file mode 100644
index 0000000..a774400
--- /dev/null
+++ b/.github/workflows/labeller.yml
@@ -0,0 +1,12 @@
+name: "Pull Request Labeler"
+on:
+  pull_request_target:
+    types: [opened, reopened]
+
+jobs:
+  triage:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/labeler@v3
+      with:
+        repo-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index 33a28ac..d05bbbe 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -34,7 +34,7 @@
       - name: Setup Python
         uses: actions/setup-python@v2
         with:
-          python-version: 3.7
+          python-version: 3.8
 
       - name: Setup Node
         uses: actions/setup-node@v2
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index 186f95e..7347a58 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -46,7 +46,7 @@
       - name: Setup Python
         uses: actions/setup-python@v2
         with:
-          python-version: 3.7
+          python-version: 3.8
 
       - name: Setup Node
         uses: actions/setup-node@v2
diff --git a/.github/workflows/server-tests-postgres.yml b/.github/workflows/server-tests-postgres.yml
index 3bbf6a9..77d3c1a 100644
--- a/.github/workflows/server-tests-postgres.yml
+++ b/.github/workflows/server-tests-postgres.yml
@@ -46,7 +46,7 @@
       - name: Setup Python
         uses: actions/setup-python@v2
         with:
-          python-version: 3.7
+          python-version: 3.8
 
       - name: Setup Node
         uses: actions/setup-node@v2
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index d765f04..ab6a53b 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -36,7 +36,7 @@
       - name: Setup Python
         uses: actions/setup-python@v2
         with:
-          python-version: 3.7
+          python-version: 3.8
 
       - uses: actions/setup-node@v2
         with:
diff --git a/CODEOWNERS b/CODEOWNERS
index a4a14de..bfc2601 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -23,13 +23,13 @@
 
 erpnext/crm/                    @ruchamahabal @pateljannat
 erpnext/education/              @ruchamahabal @pateljannat
-erpnext/healthcare/             @ruchamahabal @pateljannat @chillaranand
 erpnext/hr/                     @ruchamahabal @pateljannat
-erpnext/non_profit/             @ruchamahabal
 erpnext/payroll                 @ruchamahabal @pateljannat
 erpnext/projects/               @ruchamahabal @pateljannat
 
-erpnext/controllers             @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
+erpnext/controllers/            @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination @ankush
+erpnext/patches/                @deepeshgarg007 @nextchamp-saqib @marination @ankush
+erpnext/public/                 @nextchamp-saqib @marination
 
-.github/                        @surajshetty3416 @ankush
+.github/                        @ankush
 requirements.txt                @gavindsouza
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/__init__.py b/erpnext/accounts/doctype/currency_exchange_settings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings/__init__.py
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js
new file mode 100644
index 0000000..6c40f2b
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js
@@ -0,0 +1,45 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Currency Exchange Settings', {
+	service_provider: function(frm) {
+		if (frm.doc.service_provider == "exchangerate.host") {
+			let result = ['result'];
+			let params = {
+				date: '{transaction_date}',
+				from: '{from_currency}',
+				to: '{to_currency}'
+			};
+			add_param(frm, "https://api.exchangerate.host/convert", params, result);
+		} else if (frm.doc.service_provider == "frankfurter.app") {
+			let result = ['rates', '{to_currency}'];
+			let params = {
+				base: '{from_currency}',
+				symbols: '{to_currency}'
+			};
+			add_param(frm, "https://frankfurter.app/{transaction_date}", params, result);
+		}
+	}
+});
+
+
+function add_param(frm, api, params, result) {
+	var row;
+	frm.clear_table("req_params");
+	frm.clear_table("result_key");
+
+	frm.doc.api_endpoint = api;
+
+	$.each(params, function(key, value) {
+		row = frm.add_child("req_params");
+		row.key = key;
+		row.value = value;
+	});
+
+	$.each(result, function(key, value) {
+		row = frm.add_child("result_key");
+		row.key = value;
+	});
+
+	frm.refresh_fields();
+}
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
new file mode 100644
index 0000000..7921fcc
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
@@ -0,0 +1,126 @@
+{
+ "actions": [],
+ "creation": "2022-01-10 13:03:26.237081",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "api_details_section",
+  "service_provider",
+  "api_endpoint",
+  "url",
+  "column_break_3",
+  "help",
+  "section_break_2",
+  "req_params",
+  "column_break_4",
+  "result_key"
+ ],
+ "fields": [
+  {
+   "fieldname": "api_details_section",
+   "fieldtype": "Section Break",
+   "label": "API Details"
+  },
+  {
+   "fieldname": "api_endpoint",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "API Endpoint",
+   "read_only_depends_on": "eval: doc.service_provider != \"Custom\"",
+   "reqd": 1
+  },
+  {
+   "fieldname": "url",
+   "fieldtype": "Data",
+   "label": "Example URL",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "help",
+   "fieldtype": "HTML",
+   "label": "Help",
+   "options": "<h3>Currency Exchange Settings Help</h3>\n<p>There are 3 variables that could be used within the endpoint, result key and in values of the parameter.</p>\n<p>Exchange rate between {from_currency} and {to_currency} on {transaction_date} is fetched by the API.</p>\n<p>Example: If your endpoint is exchange.com/2021-08-01, then, you will have to input exchange.com/{transaction_date}</p>"
+  },
+  {
+   "fieldname": "section_break_2",
+   "fieldtype": "Section Break",
+   "label": "Request Parameters"
+  },
+  {
+   "fieldname": "req_params",
+   "fieldtype": "Table",
+   "label": "Parameters",
+   "options": "Currency Exchange Settings Details",
+   "read_only_depends_on": "eval: doc.service_provider != \"Custom\"",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "result_key",
+   "fieldtype": "Table",
+   "label": "Result Key",
+   "options": "Currency Exchange Settings Result",
+   "read_only_depends_on": "eval: doc.service_provider != \"Custom\"",
+   "reqd": 1
+  },
+  {
+   "fieldname": "service_provider",
+   "fieldtype": "Select",
+   "label": "Service Provider",
+   "options": "frankfurter.app\nexchangerate.host\nCustom",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2022-01-10 15:51:14.521174",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Currency Exchange Settings",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "Accounts Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
new file mode 100644
index 0000000..e16ff3a
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
@@ -0,0 +1,82 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+import requests
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import nowdate
+
+
+class CurrencyExchangeSettings(Document):
+	def validate(self):
+		self.set_parameters_and_result()
+		response, value = self.validate_parameters()
+		self.validate_result(response, value)
+
+	def set_parameters_and_result(self):
+		if self.service_provider == 'exchangerate.host':
+			self.set('result_key', [])
+			self.set('req_params', [])
+
+			self.api_endpoint = "https://api.exchangerate.host/convert"
+			self.append('result_key', {'key': 'result'})
+			self.append('req_params', {'key': 'date', 'value': '{transaction_date}'})
+			self.append('req_params', {'key': 'from', 'value': '{from_currency}'})
+			self.append('req_params', {'key': 'to', 'value': '{to_currency}'})
+		elif self.service_provider == 'frankfurter.app':
+			self.set('result_key', [])
+			self.set('req_params', [])
+
+			self.api_endpoint = "https://frankfurter.app/{transaction_date}"
+			self.append('result_key', {'key': 'rates'})
+			self.append('result_key', {'key': '{to_currency}'})
+			self.append('req_params', {'key': 'base', 'value': '{from_currency}'})
+			self.append('req_params', {'key': 'symbols', 'value': '{to_currency}'})
+
+	def validate_parameters(self):
+		if frappe.flags.in_test:
+			return None, None
+
+		params = {}
+		for row in self.req_params:
+			params[row.key] = row.value.format(
+				transaction_date=nowdate(),
+				to_currency='INR',
+				from_currency='USD'
+			)
+
+		api_url = self.api_endpoint.format(
+			transaction_date=nowdate(),
+			to_currency='INR',
+			from_currency='USD'
+		)
+
+		try:
+			response = requests.get(api_url, params=params)
+		except requests.exceptions.RequestException as e:
+			frappe.throw("Error: " + str(e))
+
+		response.raise_for_status()
+		value = response.json()
+
+		return response, value
+
+	def validate_result(self, response, value):
+		if frappe.flags.in_test:
+			return
+
+		try:
+			for key in self.result_key:
+				value = value[str(key.key).format(
+					transaction_date=nowdate(),
+					to_currency='INR',
+					from_currency='USD'
+				)]
+		except Exception:
+			frappe.throw("Invalid result key. Response: " + response.text)
+		if not isinstance(value, (int, float)):
+			frappe.throw(_("Returned exchange rate is neither integer not float."))
+
+		self.url = response.url
+		frappe.msgprint("Exchange rate of USD to INR is " + str(value))
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py
new file mode 100644
index 0000000..2778729
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+import unittest
+
+
+class TestCurrencyExchangeSettings(unittest.TestCase):
+	pass
diff --git a/erpnext/accounts/doctype/currency_exchange_settings_details/__init__.py b/erpnext/accounts/doctype/currency_exchange_settings_details/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings_details/__init__.py
diff --git a/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.json b/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.json
new file mode 100644
index 0000000..3093587
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.json
@@ -0,0 +1,39 @@
+{
+ "actions": [],
+ "creation": "2021-09-02 14:54:49.033512",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "key",
+  "value"
+ ],
+ "fields": [
+  {
+   "fieldname": "key",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Key",
+   "reqd": 1
+  },
+  {
+   "fieldname": "value",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Value",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-11-03 19:14:55.889037",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Currency Exchange Settings Details",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.py b/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.py
new file mode 100644
index 0000000..a6ad763
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class CurrencyExchangeSettingsDetails(Document):
+	pass
diff --git a/erpnext/accounts/doctype/currency_exchange_settings_result/__init__.py b/erpnext/accounts/doctype/currency_exchange_settings_result/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings_result/__init__.py
diff --git a/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.json b/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.json
new file mode 100644
index 0000000..fff5337
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.json
@@ -0,0 +1,31 @@
+{
+ "actions": [],
+ "creation": "2021-09-03 13:17:22.088259",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "key"
+ ],
+ "fields": [
+  {
+   "fieldname": "key",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Key",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-11-03 19:14:40.054245",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Currency Exchange Settings Result",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.py b/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.py
new file mode 100644
index 0000000..1774128
--- /dev/null
+++ b/erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class CurrencyExchangeSettingsResult(Document):
+	pass
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 957a50f..617b376 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -31,7 +31,7 @@
 		if(frm.doc.docstatus==1) {
 			frm.add_custom_button(__('Reverse Journal Entry'), function() {
 				return erpnext.journal_entry.reverse_journal_entry(frm);
-			}, __('Make'));
+			}, __('Actions'));
 		}
 
 		if (frm.doc.__islocal) {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 20678d7..335fd35 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -13,6 +13,7 @@
   "voucher_type",
   "naming_series",
   "finance_book",
+  "reversal_of",
   "tax_withholding_category",
   "column_break1",
   "from_template",
@@ -515,13 +516,21 @@
    "fieldname": "apply_tds",
    "fieldtype": "Check",
    "label": "Apply Tax Withholding Amount "
+  },
+  {
+   "depends_on": "eval:doc.docstatus",
+   "fieldname": "reversal_of",
+   "fieldtype": "Link",
+   "label": "Reversal Of",
+   "options": "Journal Entry",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 176,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-09-09 15:31:14.484029",
+ "modified": "2022-01-04 13:39:36.485954",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index ca17265..8fc4e8c 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -1157,9 +1157,8 @@
 def make_reverse_journal_entry(source_name, target_doc=None):
 	from frappe.model.mapper import get_mapped_doc
 
-	def update_accounts(source, target, source_parent):
-		target.reference_type = "Journal Entry"
-		target.reference_name = source_parent.name
+	def post_process(source, target):
+		target.reversal_of = source.name
 
 	doclist = get_mapped_doc("Journal Entry", source_name, {
 		"Journal Entry": {
@@ -1177,9 +1176,8 @@
 				"debit": "credit",
 				"credit_in_account_currency": "debit_in_account_currency",
 				"credit": "debit",
-			},
-			"postprocess": update_accounts,
+			}
 		},
-	}, target_doc)
+	}, target_doc, post_process)
 
 	return doclist
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index c1b056b..0e07abd 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1708,7 +1708,10 @@
 
 def apply_early_payment_discount(paid_amount, received_amount, doc):
 	total_discount = 0
-	if doc.doctype in ['Sales Invoice', 'Purchase Invoice'] and doc.payment_schedule:
+	eligible_for_payments = ['Sales Order', 'Sales Invoice', 'Purchase Order', 'Purchase Invoice']
+	has_payment_schedule = hasattr(doc, 'payment_schedule') and doc.payment_schedule
+
+	if doc.doctype in eligible_for_payments and has_payment_schedule:
 		for term in doc.payment_schedule:
 			if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
 				if term.discount_type == 'Percentage':
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 11d59bc..134bccf 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -353,7 +353,6 @@
 			if not for_validate and not self.customer:
 				self.customer = profile.customer
 
-			self.ignore_pricing_rule = profile.ignore_pricing_rule
 			self.account_for_change_amount = profile.get('account_for_change_amount') or self.account_for_change_amount
 			self.set_warehouse = profile.get('warehouse') or self.set_warehouse
 
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index 7d31e0a..56479a0 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -556,6 +556,37 @@
 		batch.cancel()
 		batch.delete()
 
+	def test_ignore_pricing_rule(self):
+		from erpnext.accounts.doctype.pricing_rule.test_pricing_rule import make_pricing_rule
+
+		item_price = frappe.get_doc({
+			'doctype': 'Item Price',
+			'item_code': '_Test Item',
+			'price_list': '_Test Price List',
+			'price_list_rate': '450',
+		})
+		item_price.insert()
+		pr = make_pricing_rule(selling=1, priority=5, discount_percentage=10)
+		pr.save()
+		pos_inv = create_pos_invoice(qty=1, do_not_submit=1)
+		pos_inv.items[0].rate = 300
+		pos_inv.save()
+		self.assertEquals(pos_inv.items[0].discount_percentage, 10)
+		# rate shouldn't change
+		self.assertEquals(pos_inv.items[0].rate, 405)
+
+		pos_inv.ignore_pricing_rule = 1
+		pos_inv.items[0].rate = 300
+		pos_inv.save()
+		self.assertEquals(pos_inv.ignore_pricing_rule, 1)
+		# rate should change since pricing rules are ignored
+		self.assertEquals(pos_inv.items[0].rate, 300)
+
+		item_price.delete()
+		pos_inv.delete()
+		pr.delete()
+
+
 def create_pos_invoice(**args):
 	args = frappe._dict(args)
 	pos_profile = None
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index 314c894..5746a84 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -650,7 +650,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,
+		"priority": args.priority or 1,
 		"discount_amount": args.discount_amount or 0.0,
 		"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
 	})
@@ -676,6 +676,8 @@
 	if args.get(applicable_for):
 		doc.db_set(applicable_for, args.get(applicable_for))
 
+	return doc
+
 def setup_pricing_rule_data():
 	if not frappe.db.exists('Campaign', '_Test Campaign'):
 		frappe.get_doc({
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index df957d2..b364218 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -505,11 +505,11 @@
 		# Checked both rounding_adjustment and rounded_total
 		# because rounded_total had value even before introcution of posting GLE based on rounded total
 		grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
+		base_grand_total = flt(self.base_rounded_total if (self.base_rounding_adjustment and self.base_rounded_total)
+			else self.base_grand_total, self.precision("base_grand_total"))
 
 		if grand_total and not self.is_internal_transfer():
 				# 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(
 					self.get_gl_dict({
 						"account": self.credit_to,
@@ -517,8 +517,8 @@
 						"party": self.supplier,
 						"due_date": self.due_date,
 						"against": self.against_expense_account,
-						"credit": grand_total_in_company_currency,
-						"credit_in_account_currency": grand_total_in_company_currency \
+						"credit": base_grand_total,
+						"credit_in_account_currency": base_grand_total \
 							if self.party_account_currency==self.company_currency else grand_total,
 						"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
 						"against_voucher_type": self.doctype,
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 545abf7..5062c1c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -651,7 +651,7 @@
    "hide_seconds": 1,
    "label": "Ignore Pricing Rule",
    "no_copy": 1,
-   "permlevel": 1,
+   "permlevel": 0,
    "print_hide": 1
   },
   {
@@ -2038,7 +2038,7 @@
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2021-10-21 20:19:38.667508",
+ "modified": "2021-12-23 20:19:38.667508",
  "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 321b453..98bc953 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -862,11 +862,11 @@
 		# Checked both rounding_adjustment and rounded_total
 		# because rounded_total had value even before introcution of posting GLE based on rounded total
 		grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
+		base_grand_total = flt(self.base_rounded_total if (self.base_rounding_adjustment and self.base_rounded_total)
+			else self.base_grand_total, self.precision("base_grand_total"))
+
 		if grand_total and not self.is_internal_transfer():
 			# Didnot 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(
 				self.get_gl_dict({
 					"account": self.debit_to,
@@ -874,8 +874,8 @@
 					"party": self.customer,
 					"due_date": self.due_date,
 					"against": self.against_income_account,
-					"debit": grand_total_in_company_currency,
-					"debit_in_account_currency": grand_total_in_company_currency \
+					"debit": base_grand_total,
+					"debit_in_account_currency": base_grand_total \
 						if self.party_account_currency==self.company_currency else grand_total,
 					"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
 					"against_voucher_type": self.doctype,
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
index 305cddb..715cd64 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js
@@ -117,6 +117,11 @@
 			"label": __("Show Future Payments"),
 			"fieldtype": "Check",
 		},
+		{
+			"fieldname":"show_gl_balance",
+			"label": __("Show GL Balance"),
+			"fieldtype": "Check",
+		},
 	],
 
 	onload: function(report) {
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 3c94629..4559fa9 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -4,7 +4,8 @@
 
 import frappe
 from frappe import _, scrub
-from frappe.utils import cint
+from frappe.utils import cint, flt
+from six import iteritems
 
 from erpnext.accounts.party import get_partywise_advanced_payment_amount
 from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
@@ -36,7 +37,10 @@
 		party_advance_amount = get_partywise_advanced_payment_amount(self.party_type,
 			self.filters.report_date, self.filters.show_future_payments, self.filters.company) or {}
 
-		for party, party_dict in self.party_total.items():
+		if self.filters.show_gl_balance:
+			gl_balance_map = get_gl_balance(self.filters.report_date)
+
+		for party, party_dict in iteritems(self.party_total):
 			if party_dict.outstanding == 0:
 				continue
 
@@ -55,6 +59,10 @@
 			# but in summary report advance shown in separate column
 			row.paid -= row.advance
 
+			if self.filters.show_gl_balance:
+				row.gl_balance = gl_balance_map.get(party)
+				row.diff = flt(row.outstanding) - flt(row.gl_balance)
+
 			self.data.append(row)
 
 	def get_party_total(self, args):
@@ -114,6 +122,10 @@
 		self.add_column(_(credit_debit_label), fieldname='credit_note')
 		self.add_column(_('Outstanding Amount'), fieldname='outstanding')
 
+		if self.filters.show_gl_balance:
+			self.add_column(_('GL Balance'), fieldname='gl_balance')
+			self.add_column(_('Difference'), fieldname='diff')
+
 		self.setup_ageing_columns()
 
 		if self.party_type == "Customer":
@@ -140,3 +152,7 @@
 
 		# Add column for total due amount
 		self.add_column(label="Total Amount Due", fieldname='total_due')
+
+def get_gl_balance(report_date):
+	return frappe._dict(frappe.db.get_all("GL Entry", fields=['party', 'sum(debit -  credit)'],
+		filters={'posting_date': ("<=", report_date), 'is_cancelled': 0}, group_by='party', as_list=1))
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
index a4842c1..3a51db8 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
@@ -121,20 +121,21 @@
 		"""
 		simulate future posting by creating dummy gl entries. starts from the last posting date.
 		"""
-		if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date:
-			self.estimate_for_period_list = get_period_list(
-				self.filters.from_fiscal_year,
-				self.filters.to_fiscal_year,
-				add_days(self.last_entry_date, 1),
-				self.period_list[-1].to_date,
-				"Date Range",
-				"Monthly",
-				company=self.filters.company,
-			)
-			for period in self.estimate_for_period_list:
-				amount = self.calculate_amount(period.from_date, period.to_date)
-				gle = self.make_dummy_gle(period.key, period.to_date, amount)
-				self.gle_entries.append(gle)
+		if self.service_start_date != self.service_end_date:
+			if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date:
+				self.estimate_for_period_list = get_period_list(
+					self.filters.from_fiscal_year,
+					self.filters.to_fiscal_year,
+					add_days(self.last_entry_date, 1),
+					self.period_list[-1].to_date,
+					"Date Range",
+					"Monthly",
+					company=self.filters.company,
+				)
+				for period in self.estimate_for_period_list:
+					amount = self.calculate_amount(period.from_date, period.to_date)
+					gle = self.make_dummy_gle(period.key, period.to_date, amount)
+					self.gle_entries.append(gle)
 
 	def calculate_item_revenue_expense_for_period(self):
 		"""
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index b296876..010284c 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -167,7 +167,7 @@
 			"fieldname": "include_dimensions",
 			"label": __("Consider Accounting Dimensions"),
 			"fieldtype": "Check",
-			"default": 0
+			"default": 1
 		},
 		{
 			"fieldname": "show_opening_entries",
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 385c8b2..7f27920 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -448,9 +448,11 @@
 
 			elif group_by_voucher_consolidated:
 				keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")]
-				for dim in accounting_dimensions:
-					keylist.append(gle.get(dim))
-				keylist.append(gle.get("cost_center"))
+				if filters.get("include_dimensions"):
+					for dim in accounting_dimensions:
+						keylist.append(gle.get(dim))
+					keylist.append(gle.get("cost_center"))
+
 				key = tuple(keylist)
 				if key not in consolidated_gle:
 					consolidated_gle.setdefault(key, gle)
@@ -547,10 +549,7 @@
 			"fieldname": "balance",
 			"fieldtype": "Float",
 			"width": 130
-		}
-	]
-
-	columns.extend([
+		},
 		{
 			"label": _("Voucher Type"),
 			"fieldname": "voucher_type",
@@ -584,7 +583,7 @@
 			"fieldname": "project",
 			"width": 100
 		}
-	])
+	]
 
 	if filters.get("include_dimensions"):
 		for dim in get_accounting_dimensions(as_list = False):
@@ -594,14 +593,14 @@
 				"fieldname": dim.fieldname,
 				"width": 100
 			})
-
-	columns.extend([
-		{
+		columns.append({
 			"label": _("Cost Center"),
 			"options": "Cost Center",
 			"fieldname": "cost_center",
 			"width": 100
-		},
+		})
+
+	columns.extend([
 		{
 			"label": _("Against Voucher Type"),
 			"fieldname": "against_voucher_type",
diff --git a/erpnext/accounts/test/test_reports.py b/erpnext/accounts/test/test_reports.py
new file mode 100644
index 0000000..78c109a
--- /dev/null
+++ b/erpnext/accounts/test/test_reports.py
@@ -0,0 +1,48 @@
+import unittest
+from typing import List, Tuple
+
+from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report
+
+DEFAULT_FILTERS = {
+	"company": "_Test Company",
+	"from_date": "2010-01-01",
+	"to_date": "2030-01-01",
+	"period_start_date": "2010-01-01",
+	"period_end_date": "2030-01-01"
+}
+
+
+REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
+	("General Ledger", {"group_by": "Group by Voucher (Consolidated)"} ),
+	("General Ledger", {"group_by": "Group by Voucher (Consolidated)", "include_dimensions": 1} ),
+	("Accounts Payable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}),
+	("Accounts Receivable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}),
+	("Consolidated Financial Statement", {"report": "Balance Sheet"} ),
+	("Consolidated Financial Statement", {"report": "Profit and Loss Statement"} ),
+	("Consolidated Financial Statement", {"report": "Cash Flow"} ),
+	("Gross Profit", {"group_by": "Invoice"}),
+	("Gross Profit", {"group_by": "Item Code"}),
+	("Gross Profit", {"group_by": "Item Group"}),
+	("Gross Profit", {"group_by": "Customer"}),
+	("Gross Profit", {"group_by": "Customer Group"}),
+	("Item-wise Sales Register", {}),
+	("Item-wise Purchase Register", {}),
+	("Sales Register", {}),
+	("Purchase Register", {}),
+	("Tax Detail", {"mode": "run", "report_name": "Tax Detail"},),
+]
+
+OPTIONAL_FILTERS = {}
+
+
+class TestReports(unittest.TestCase):
+	def test_execute_all_accounts_reports(self):
+		"""Test that all script report in stock modules are executable with supported filters"""
+		for report, filter in REPORT_FILTER_TEST_CASES:
+			execute_script_report(
+				report_name=report,
+				module="Accounts",
+				filters=filter,
+				default_filters=DEFAULT_FILTERS,
+				optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
+			)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index ce5d5dc..eab9e12 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -113,7 +113,7 @@
 						_('{0} is blocked so this transaction cannot proceed').format(supplier_name), raise_exception=1)
 
 	def validate(self):
-		if not self.get('is_return'):
+		if not self.get('is_return') and not self.get('is_debit_note'):
 			self.validate_qty_is_not_zero()
 
 		if self.get("_action") and self._action != "update_after_submit":
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 746c6fd..075e3e3 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -139,6 +139,8 @@
 
 				if not item.qty and self.doc.get("is_return"):
 					item.amount = flt(-1 * item.rate, item.precision("amount"))
+				elif not item.qty and self.doc.get("is_debit_note"):
+					item.amount = flt(item.rate, item.precision("amount"))
 				else:
 					item.amount = flt(item.rate * item.qty,	item.precision("amount"))
 
@@ -594,13 +596,14 @@
 
 		if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
 			grand_total = self.doc.rounded_total or self.doc.grand_total
+			base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
+
 			if self.doc.party_account_currency == self.doc.currency:
 				total_amount_to_pay = flt(grand_total - self.doc.total_advance
 					- flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
 			else:
-				total_amount_to_pay = flt(flt(grand_total *
-					self.doc.conversion_rate, self.doc.precision("grand_total")) - self.doc.total_advance
-						- flt(self.doc.base_write_off_amount), self.doc.precision("grand_total"))
+				total_amount_to_pay = flt(flt(base_grand_total, self.doc.precision("base_grand_total")) - self.doc.total_advance
+						- flt(self.doc.base_write_off_amount), self.doc.precision("base_grand_total"))
 
 			self.doc.round_floats_in(self.doc, ["paid_amount"])
 			change_amount = 0
diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py
index 05541d1..908d78c 100644
--- a/erpnext/controllers/tests/test_queries.py
+++ b/erpnext/controllers/tests/test_queries.py
@@ -1,6 +1,8 @@
 import unittest
 from functools import partial
 
+import frappe
+
 from erpnext.controllers import queries
 
 
@@ -85,3 +87,6 @@
 
 		wh = query(filters=[["Bin", "item_code", "=", "_Test Item"]])
 		self.assertGreaterEqual(len(wh), 1)
+
+	def test_default_uoms(self):
+		self.assertGreaterEqual(frappe.db.count("UOM", {"enabled": 1}), 10)
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 6479853..93ef217 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -13,8 +13,10 @@
   "column_break_3",
   "company",
   "posting_date",
-  "is_term_loan",
   "rate_of_interest",
+  "payroll_payable_account",
+  "is_term_loan",
+  "repay_from_salary",
   "payment_details_section",
   "due_date",
   "pending_principal_amount",
@@ -243,15 +245,31 @@
    "label": "Total Penalty Paid",
    "options": "Company:company:default_currency",
    "read_only": 1
+  },
+  {
+   "depends_on": "eval:doc.repay_from_salary",
+   "fieldname": "payroll_payable_account",
+   "fieldtype": "Link",
+   "label": "Payroll Payable Account",
+   "mandatory_depends_on": "eval:doc.repay_from_salary",
+   "options": "Account"
+  },
+  {
+   "default": "0",
+   "fetch_from": "against_loan.repay_from_salary",
+   "fieldname": "repay_from_salary",
+   "fieldtype": "Check",
+   "label": "Repay From Salary"
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-19 18:10:00.935364",
+ "modified": "2022-01-06 01:51:06.707782",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Repayment",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -287,5 +305,6 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 2abb395..7e997e8 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -320,74 +320,79 @@
 		else:
 			remarks = _("Repayment against Loan: ") + self.against_loan
 
-		if not loan_details.repay_from_salary:
-			if self.total_penalty_paid:
-				gle_map.append(
-					self.get_gl_dict({
-						"account": loan_details.loan_account,
-						"against": loan_details.payment_account,
-						"debit": self.total_penalty_paid,
-						"debit_in_account_currency": self.total_penalty_paid,
-						"against_voucher_type": "Loan",
-						"against_voucher": self.against_loan,
-						"remarks": _("Penalty against loan:") + self.against_loan,
-						"cost_center": self.cost_center,
-						"party_type": self.applicant_type,
-						"party": self.applicant,
-						"posting_date": getdate(self.posting_date)
-					})
-				)
+		if self.repay_from_salary:
+			payment_account = self.payroll_payable_account
+		else:
+			payment_account = loan_details.payment_account
 
-				gle_map.append(
-					self.get_gl_dict({
-						"account": loan_details.penalty_income_account,
-						"against": loan_details.payment_account,
-						"credit": self.total_penalty_paid,
-						"credit_in_account_currency": self.total_penalty_paid,
-						"against_voucher_type": "Loan",
-						"against_voucher": self.against_loan,
-						"remarks": _("Penalty against loan:") + self.against_loan,
-						"cost_center": self.cost_center,
-						"posting_date": getdate(self.posting_date)
-					})
-				)
-
-			gle_map.append(
-				self.get_gl_dict({
-					"account": loan_details.payment_account,
-					"against": loan_details.loan_account + ", " + loan_details.interest_income_account
-							+ ", " + loan_details.penalty_income_account,
-					"debit": self.amount_paid,
-					"debit_in_account_currency": self.amount_paid,
-					"against_voucher_type": "Loan",
-					"against_voucher": self.against_loan,
-					"remarks": remarks,
-					"cost_center": self.cost_center,
-					"posting_date": getdate(self.posting_date)
-				})
-			)
-
+		if self.total_penalty_paid:
 			gle_map.append(
 				self.get_gl_dict({
 					"account": loan_details.loan_account,
-					"party_type": loan_details.applicant_type,
-					"party": loan_details.applicant,
 					"against": loan_details.payment_account,
-					"credit": self.amount_paid,
-					"credit_in_account_currency": self.amount_paid,
+					"debit": self.total_penalty_paid,
+					"debit_in_account_currency": self.total_penalty_paid,
 					"against_voucher_type": "Loan",
 					"against_voucher": self.against_loan,
-					"remarks": remarks,
+					"remarks": _("Penalty against loan:") + self.against_loan,
+					"cost_center": self.cost_center,
+					"party_type": self.applicant_type,
+					"party": self.applicant,
+					"posting_date": getdate(self.posting_date)
+				})
+			)
+
+			gle_map.append(
+				self.get_gl_dict({
+					"account": loan_details.penalty_income_account,
+					"against": payment_account,
+					"credit": self.total_penalty_paid,
+					"credit_in_account_currency": self.total_penalty_paid,
+					"against_voucher_type": "Loan",
+					"against_voucher": self.against_loan,
+					"remarks": _("Penalty against loan:") + self.against_loan,
 					"cost_center": self.cost_center,
 					"posting_date": getdate(self.posting_date)
 				})
 			)
 
-			if gle_map:
-				make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
+		gle_map.append(
+			self.get_gl_dict({
+				"account": payment_account,
+				"against": loan_details.loan_account + ", " + loan_details.interest_income_account
+						+ ", " + loan_details.penalty_income_account,
+				"debit": self.amount_paid,
+				"debit_in_account_currency": self.amount_paid,
+				"against_voucher_type": "Loan",
+				"against_voucher": self.against_loan,
+				"remarks": remarks,
+				"cost_center": self.cost_center,
+				"posting_date": getdate(self.posting_date)
+			})
+		)
+
+		gle_map.append(
+			self.get_gl_dict({
+				"account": loan_details.loan_account,
+				"party_type": loan_details.applicant_type,
+				"party": loan_details.applicant,
+				"against": payment_account,
+				"credit": self.amount_paid,
+				"credit_in_account_currency": self.amount_paid,
+				"against_voucher_type": "Loan",
+				"against_voucher": self.against_loan,
+				"remarks": remarks,
+				"cost_center": self.cost_center,
+				"posting_date": getdate(self.posting_date)
+			})
+		)
+
+		if gle_map:
+			make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
 
 def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
-	payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None):
+	payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None,
+	payroll_payable_account=None):
 
 	lr = frappe.get_doc({
 		"doctype": "Loan Repayment",
@@ -400,7 +405,8 @@
 		"interest_payable": interest_payable,
 		"payable_principal_amount": payable_principal_amount,
 		"amount_paid": amount_paid,
-		"loan_type": loan_type
+		"loan_type": loan_type,
+		"payroll_payable_account": payroll_payable_account
 	}).insert()
 
 	return lr
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index f82d9a0..5a60fb7 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -530,16 +530,6 @@
 				row.hour_rate = (hour_rate / flt(self.conversion_rate)
 					if self.conversion_rate and hour_rate else hour_rate)
 
-			if self.routing:
-				time_in_mins = flt(frappe.db.get_value("BOM Operation", {
-						"workstation": row.workstation,
-						"operation": row.operation,
-						"parent": self.routing
-				}, ["time_in_mins"]))
-
-				if time_in_mins:
-					row.time_in_mins = time_in_mins
-
 		if row.hour_rate and row.time_in_mins:
 			row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate)
 			row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index e90b0a7..8bd60ea 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -46,6 +46,7 @@
 		wo_doc.delete()
 
 	def test_update_bom_operation_time(self):
+		"""Update cost shouldn't update routing times."""
 		operations = [
 			{
 				"operation": "Test Operation A",
@@ -85,8 +86,8 @@
 		routing_doc.save()
 		bom_doc.update_cost()
 		bom_doc.reload()
-		self.assertEqual(bom_doc.operations[0].time_in_mins, 90)
-		self.assertEqual(bom_doc.operations[1].time_in_mins, 42.2)
+		self.assertEqual(bom_doc.operations[0].time_in_mins, 30)
+		self.assertEqual(bom_doc.operations[1].time_in_mins, 20)
 
 
 def setup_operations(rows):
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 268db40..5190f9f 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -165,7 +165,6 @@
 erpnext.patches.v12_0.set_default_payroll_based_on
 erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse
 erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign
-erpnext.patches.v13_0.validate_options_for_data_field
 erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123
 erpnext.patches.v12_0.fix_quotation_expired_status
 erpnext.patches.v12_0.rename_pos_closing_doctype
@@ -280,6 +279,7 @@
 erpnext.patches.v13_0.update_recipient_email_digest
 erpnext.patches.v13_0.shopify_deprecation_warning
 erpnext.patches.v13_0.remove_bad_selling_defaults
+erpnext.patches.v13_0.trim_whitespace_from_serial_nos
 erpnext.patches.v13_0.migrate_stripe_api
 erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
 erpnext.patches.v13_0.einvoicing_deprecation_warning
@@ -305,6 +305,7 @@
 erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting
 erpnext.patches.v13_0.requeue_failed_reposts
 erpnext.patches.v13_0.update_job_card_status
+erpnext.patches.v13_0.enable_uoms
 erpnext.patches.v12_0.update_production_plan_status
 erpnext.patches.v13_0.healthcare_deprecation_warning
 erpnext.patches.v13_0.item_naming_series_not_mandatory
@@ -323,3 +324,4 @@
 erpnext.patches.v14_0.set_payroll_cost_centers
 erpnext.patches.v13_0.agriculture_deprecation_warning
 erpnext.patches.v14_0.delete_agriculture_doctypes
+erpnext.patches.v13_0.update_exchange_rate_settings
diff --git a/erpnext/patches/v13_0/enable_uoms.py b/erpnext/patches/v13_0/enable_uoms.py
new file mode 100644
index 0000000..4d3f637
--- /dev/null
+++ b/erpnext/patches/v13_0/enable_uoms.py
@@ -0,0 +1,13 @@
+import frappe
+
+
+def execute():
+	frappe.reload_doc('setup', 'doctype', 'uom')
+
+	uom = frappe.qb.DocType("UOM")
+
+	(frappe.qb
+		.update(uom)
+		.set(uom.enabled, 1)
+		.where(uom.creation >= "2021-10-18")  # date when this field was released
+	).run()
diff --git a/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py
new file mode 100644
index 0000000..8a9633d
--- /dev/null
+++ b/erpnext/patches/v13_0/trim_whitespace_from_serial_nos.py
@@ -0,0 +1,65 @@
+import frappe
+
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+
+def execute():
+	broken_sles = frappe.db.sql("""
+			select name, serial_no
+			from `tabStock Ledger Entry`
+			where
+				is_cancelled = 0
+				and (serial_no like %s or serial_no like %s or serial_no like %s or serial_no like %s)
+			""",
+			(
+				" %",    # leading whitespace
+				"% ",    # trailing whitespace
+				"%\n %", # leading whitespace on newline
+				"% \n%", # trailing whitespace on newline
+			),
+			as_dict=True,
+		)
+
+	frappe.db.MAX_WRITES_PER_TRANSACTION += len(broken_sles)
+
+	if not broken_sles:
+		return
+
+	broken_serial_nos = set()
+
+	# patch SLEs
+	for sle in broken_sles:
+		serial_no_list = get_serial_nos(sle.serial_no)
+		correct_sr_no = "\n".join(serial_no_list)
+
+		if correct_sr_no == sle.serial_no:
+			continue
+
+		frappe.db.set_value("Stock Ledger Entry", sle.name, "serial_no", correct_sr_no, update_modified=False)
+		broken_serial_nos.update(serial_no_list)
+
+	if not broken_serial_nos:
+		return
+
+	# Patch serial No documents if they don't have purchase info
+	# Purchase info is used for fetching incoming rate
+	broken_sr_no_records = frappe.get_list("Serial No",
+			filters={
+				"status":"Active",
+				"name": ("in", broken_serial_nos),
+				"purchase_document_type": ("is", "not set")
+			},
+			pluck="name",
+		)
+
+	frappe.db.MAX_WRITES_PER_TRANSACTION += len(broken_sr_no_records)
+
+	patch_savepoint = "serial_no_patch"
+	for serial_no in broken_sr_no_records:
+		try:
+			frappe.db.savepoint(patch_savepoint)
+			sn = frappe.get_doc("Serial No", serial_no)
+			sn.update_serial_no_reference()
+			sn.db_update()
+		except Exception:
+			frappe.db.rollback(save_point=patch_savepoint)
diff --git a/erpnext/patches/v13_0/update_exchange_rate_settings.py b/erpnext/patches/v13_0/update_exchange_rate_settings.py
new file mode 100644
index 0000000..b7ec232
--- /dev/null
+++ b/erpnext/patches/v13_0/update_exchange_rate_settings.py
@@ -0,0 +1,10 @@
+import frappe
+
+from erpnext.setup.install import setup_currency_exchange
+
+
+def execute():
+	frappe.reload_doc("accounts", "doctype", "currency_exchange_settings")
+	frappe.reload_doc("accounts", "doctype", "currency_exchange_settings_result")
+	frappe.reload_doc("accounts", "doctype", "currency_exchange_settings_details")
+	setup_currency_exchange()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/validate_options_for_data_field.py b/erpnext/patches/v13_0/validate_options_for_data_field.py
deleted file mode 100644
index ad777b8..0000000
--- a/erpnext/patches/v13_0/validate_options_for_data_field.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright (c) 2021, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-from frappe.model import data_field_options
-
-
-def execute():
-
-    for field in frappe.get_all('Custom Field',
-                            fields = ['name'],
-                            filters = {
-                                'fieldtype': 'Data',
-                                'options': ['!=', None]
-                            }):
-
-        if field not in data_field_options:
-            frappe.db.sql("""
-                UPDATE
-                    `tabCustom Field`
-                SET
-                    options=NULL
-                WHERE
-                    name=%s
-            """, (field))
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index a4e75ac..f33443d 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -1142,15 +1142,17 @@
 			})
 
 	def make_loan_repayment_entry(self):
+		payroll_payable_account = get_payroll_payable_account(self.company, self.payroll_entry)
 		for loan in self.loans:
-			repayment_entry = create_repayment_entry(loan.loan, self.employee,
-				self.company, self.posting_date, loan.loan_type, "Regular Payment", loan.interest_amount,
-				loan.principal_amount, loan.total_payment)
+			if loan.total_payment:
+				repayment_entry = create_repayment_entry(loan.loan, self.employee,
+					self.company, self.posting_date, loan.loan_type, "Regular Payment", loan.interest_amount,
+					loan.principal_amount, loan.total_payment, payroll_payable_account=payroll_payable_account)
 
-			repayment_entry.save()
-			repayment_entry.submit()
+				repayment_entry.save()
+				repayment_entry.submit()
 
-			frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name)
+				frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name)
 
 	def cancel_loan_repayment_entry(self):
 		for loan in self.loans:
@@ -1384,3 +1386,11 @@
 		],
 		as_dict=1,
 	)
+
+def get_payroll_payable_account(company, payroll_entry):
+	if payroll_entry:
+		payroll_payable_account = frappe.db.get_value('Payroll Entry', payroll_entry, 'payroll_payable_account')
+	else:
+		payroll_payable_account = frappe.db.get_value('Company', company, 'default_payroll_payable_account')
+
+	return payroll_payable_account
\ 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 6e8fae0..c0e005a 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -380,7 +380,7 @@
 		make_salary_structure("Test Loan Repayment Salary Structure", "Monthly", employee=applicant, currency='INR',
 			payroll_period=payroll_period)
 
-		frappe.db.sql("delete from tabLoan")
+		frappe.db.sql("delete from tabLoan where applicant = 'test_loan_repayment_salary_slip@salary.com'")
 		loan = create_loan(applicant, "Car Loan", 11000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
 		loan.repay_from_salary = 1
 		loan.submit()
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 9b1ea04..8fa0538 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -102,7 +102,7 @@
 			frappe.throw(_("Completed On cannot be greater than Today"))
 
 	def update_depends_on(self):
-		depends_on_tasks = self.depends_on_tasks or ""
+		depends_on_tasks = ""
 		for d in self.depends_on:
 			if d.task and d.task not in depends_on_tasks:
 				depends_on_tasks += d.task + ","
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 7c1c8c7..ae0e2a3 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -114,6 +114,8 @@
 
 				if ((!item.qty) && me.frm.doc.is_return) {
 					item.amount = flt(item.rate * -1, precision("amount", item));
+				} else if ((!item.qty) && me.frm.doc.is_debit_note) {
+					item.amount = flt(item.rate, precision("amount", item));
 				} else {
 					item.amount = flt(item.rate * item.qty, precision("amount", item));
 				}
@@ -710,14 +712,15 @@
 		frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);
 
 		if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)) {
-			var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
+			let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
+			let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
 
 			if(this.frm.doc.party_account_currency == this.frm.doc.currency) {
 				var total_amount_to_pay = flt((grand_total - this.frm.doc.total_advance
 					- this.frm.doc.write_off_amount), precision("grand_total"));
 			} else {
 				var total_amount_to_pay = flt(
-					(flt(grand_total*this.frm.doc.conversion_rate, precision("grand_total"))
+					(flt(base_grand_total, precision("base_grand_total"))
 						- this.frm.doc.total_advance - this.frm.doc.base_write_off_amount),
 					precision("base_grand_total")
 				);
@@ -748,14 +751,15 @@
 	}
 
 	set_total_amount_to_default_mop() {
-		var grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
+		let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
+		let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
 
 		if(this.frm.doc.party_account_currency == this.frm.doc.currency) {
 			var total_amount_to_pay = flt((grand_total - this.frm.doc.total_advance
 				- this.frm.doc.write_off_amount), precision("grand_total"));
 		} else {
 			var total_amount_to_pay = flt(
-				(flt(grand_total*this.frm.doc.conversion_rate, precision("grand_total"))
+				(flt(base_grand_total, precision("base_grand_total"))
 					- this.frm.doc.total_advance - this.frm.doc.base_write_off_amount),
 				precision("base_grand_total")
 			);
diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py
index b41b2b0..cc26bd7 100644
--- a/erpnext/regional/report/ksa_vat/ksa_vat.py
+++ b/erpnext/regional/report/ksa_vat/ksa_vat.py
@@ -20,25 +20,35 @@
 			"fieldname": "title",
 			"label": _("Title"),
 			"fieldtype": "Data",
-			"width": 300
+			"width": 300,
 		},
 		{
 			"fieldname": "amount",
 			"label": _("Amount (SAR)"),
 			"fieldtype": "Currency",
+			"options": "currency",
 			"width": 150,
 		},
 		{
 			"fieldname": "adjustment_amount",
 			"label": _("Adjustment (SAR)"),
 			"fieldtype": "Currency",
+			"options": "currency",
 			"width": 150,
 		},
 		{
 			"fieldname": "vat_amount",
 			"label": _("VAT Amount (SAR)"),
 			"fieldtype": "Currency",
+			"options": "currency",
 			"width": 150,
+		},
+		{
+			"fieldname": "currency",
+			"label": _("Currency"),
+			"fieldtype": "Currency",
+			"width": 150,
+			"hidden": 1
 		}
 	]
 
@@ -47,6 +57,8 @@
 
 	# Validate if vat settings exist
 	company = filters.get('company')
+	company_currency = frappe.get_cached_value('Company',  company, "default_currency")
+
 	if frappe.db.exists('KSA VAT Setting', company) is None:
 		url = get_url_to_list('KSA VAT Setting')
 		frappe.msgprint(_('Create <a href="{}">KSA VAT Setting</a> for this company').format(url))
@@ -55,7 +67,7 @@
 	ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company)
 
 	# Sales Heading
-	append_data(data, 'VAT on Sales', '', '', '')
+	append_data(data, 'VAT on Sales', '', '', '', company_currency)
 
 	grand_total_taxable_amount = 0
 	grand_total_taxable_adjustment_amount = 0
@@ -67,7 +79,7 @@
 
 		# Adding results to data
 		append_data(data, vat_setting.title, total_taxable_amount,
-			total_taxable_adjustment_amount, total_tax)
+			total_taxable_adjustment_amount, total_tax, company_currency)
 
 		grand_total_taxable_amount += total_taxable_amount
 		grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount
@@ -75,13 +87,13 @@
 
 	# Sales Grand Total
 	append_data(data, 'Grand Total', grand_total_taxable_amount,
-		grand_total_taxable_adjustment_amount, grand_total_tax)
+		grand_total_taxable_adjustment_amount, grand_total_tax, company_currency)
 
 	# Blank Line
-	append_data(data, '', '', '', '')
+	append_data(data, '', '', '', '', company_currency)
 
 	# Purchase Heading
-	append_data(data, 'VAT on Purchases', '', '', '')
+	append_data(data, 'VAT on Purchases', '', '', '', company_currency)
 
 	grand_total_taxable_amount = 0
 	grand_total_taxable_adjustment_amount = 0
@@ -93,7 +105,7 @@
 
 		# Adding results to data
 		append_data(data, vat_setting.title, total_taxable_amount,
-			total_taxable_adjustment_amount, total_tax)
+			total_taxable_adjustment_amount, total_tax, company_currency)
 
 		grand_total_taxable_amount += total_taxable_amount
 		grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount
@@ -101,7 +113,7 @@
 
 	# Purchase Grand Total
 	append_data(data, 'Grand Total', grand_total_taxable_amount,
-		grand_total_taxable_adjustment_amount, grand_total_tax)
+		grand_total_taxable_adjustment_amount, grand_total_tax, company_currency)
 
 	return data
 
@@ -147,9 +159,10 @@
 
 
 
-def append_data(data, title, amount, adjustment_amount, vat_amount):
+def append_data(data, title, amount, adjustment_amount, vat_amount, company_currency):
 	"""Returns data with appended value."""
-	data.append({"title": _(title), "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount})
+	data.append({"title": _(title), "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount,
+		"currency": company_currency})
 
 def get_tax_amount(item_code, account_head, doctype, parent):
 	if doctype == 'Sales Invoice':
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index 0c0acc7..b2bf546 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -68,7 +68,8 @@
 			(soi.billed_amt * IFNULL(so.conversion_rate, 1)) as billed_amount,
 			(soi.base_amount - (soi.billed_amt * IFNULL(so.conversion_rate, 1))) as pending_amount,
 			soi.warehouse as warehouse,
-			so.company, soi.name
+			so.company, soi.name,
+			soi.description as description
 		FROM
 			`tabSales Order` so,
 			(`tabSales Order Item` soi
@@ -184,6 +185,12 @@
 			"options": "Item",
 			"width": 100
 		})
+		columns.append({
+			"label":_("Description"),
+			"fieldname": "description",
+			"fieldtype": "Small Text",
+			"width": 100
+		})
 
 	columns.extend([
 		{
diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
index 2b007e9..06a79b4 100644
--- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
+++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
@@ -62,8 +62,13 @@
 		if kwargs['params'].get('date') and kwargs['params'].get('from') and kwargs['params'].get('to'):
 			if test_exchange_values.get(kwargs['params']['date']):
 				return PatchResponse({'result': test_exchange_values[kwargs['params']['date']]}, 200)
+	elif args[0].startswith("https://frankfurter.app") and kwargs.get('params'):
+		if kwargs['params'].get('base') and kwargs['params'].get('symbols'):
+			date = args[0].replace("https://frankfurter.app/", "")
+			if test_exchange_values.get(date):
+				return PatchResponse({'rates': {kwargs['params'].get('symbols'): test_exchange_values.get(date)}}, 200)
 
-	return PatchResponse({'result': None}, 404)
+	return PatchResponse({'rates': None}, 404)
 
 @mock.patch('requests.get', side_effect=patched_requests_get)
 class TestCurrencyExchange(unittest.TestCase):
@@ -102,6 +107,41 @@
 		self.assertFalse(exchange_rate == 60)
 		self.assertEqual(flt(exchange_rate, 3), 65.1)
 
+	def test_exchange_rate_via_exchangerate_host(self, mock_get):
+		save_new_records(test_records)
+
+		# Update Currency Exchange Rate
+		settings = frappe.get_single("Currency Exchange Settings")
+		settings.service_provider = 'exchangerate.host'
+		settings.save()
+
+		# Update exchange
+		frappe.db.set_value("Accounts Settings", None, "allow_stale", 1)
+
+		# Start with allow_stale is True
+		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying")
+		self.assertEqual(flt(exchange_rate, 3), 60.0)
+
+		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
+		self.assertEqual(exchange_rate, 65.1)
+
+		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
+		self.assertEqual(exchange_rate, 62.9)
+
+		# Exchange rate as on 15th Dec, 2015
+		self.clear_cache()
+		exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling")
+		self.assertFalse(exchange_rate == 60)
+		self.assertEqual(flt(exchange_rate, 3), 66.999)
+
+		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-20", "for_buying")
+		self.assertFalse(exchange_rate == 60)
+		self.assertEqual(flt(exchange_rate, 3), 65.1)
+
+		settings = frappe.get_single("Currency Exchange Settings")
+		settings.service_provider = 'frankfurter.app'
+		settings.save()
+
 	def test_exchange_rate_strict(self, mock_get):
 		# strict currency settings
 		frappe.db.set_value("Accounts Settings", None, "allow_stale", 0)
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 86c9b3f..bafaab8 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -60,6 +60,22 @@
 
 	frappe.db.set_default("date_format", "dd-mm-yyyy")
 
+	setup_currency_exchange()
+
+def setup_currency_exchange():
+	ces = frappe.get_single('Currency Exchange Settings')
+	try:
+		ces.set('result_key', [])
+		ces.set('req_params', [])
+
+		ces.api_endpoint = "https://frankfurter.app/{transaction_date}"
+		ces.append('result_key', {'key': 'rates'})
+		ces.append('result_key', {'key': '{to_currency}'})
+		ces.append('req_params', {'key': 'base', 'value': '{from_currency}'})
+		ces.append('req_params', {'key': 'symbols', 'value': '{to_currency}'})
+		ces.save()
+	except frappe.ValidationError:
+		pass
 
 def create_compact_item_print_custom_field():
 	create_custom_field('Print Settings', {
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index 336b51c..9dbf49e 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -353,7 +353,8 @@
 				"doctype": "UOM",
 				"uom_name": _(d.get("uom_name")),
 				"name": _(d.get("uom_name")),
-				"must_be_whole_number": d.get("must_be_whole_number")
+				"must_be_whole_number": d.get("must_be_whole_number"),
+				"enabled": 1,
 			}).db_insert()
 
 	# bootstrap uom conversion factors
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index cad4c54..4441bb9 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -100,15 +100,21 @@
 
 		if not value:
 			import requests
-			api_url = "https://api.exchangerate.host/convert"
-			response = requests.get(api_url, params={
-				"date": transaction_date,
-				"from": from_currency,
-				"to": to_currency
-			})
+			settings = frappe.get_cached_doc('Currency Exchange Settings')
+			req_params = {
+				"transaction_date": transaction_date,
+				"from_currency": from_currency,
+				"to_currency": to_currency
+			}
+			params = {}
+			for row in settings.req_params:
+				params[row.key] = format_ces_api(row.value, req_params)
+			response = requests.get(format_ces_api(settings.api_endpoint, req_params), params=params)
 			# expire in 6 hours
 			response.raise_for_status()
-			value = response.json()["result"]
+			value = response.json()
+			for res_key in settings.result_key:
+				value = value[format_ces_api(str(res_key.key), req_params)]
 			cache.setex(name=key, time=21600, value=flt(value))
 		return flt(value)
 	except Exception:
@@ -116,6 +122,13 @@
 		frappe.msgprint(_("Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually").format(from_currency, to_currency, transaction_date))
 		return 0.0
 
+def format_ces_api(data, param):
+	return data.format(
+		transaction_date=param.get("transaction_date"),
+		to_currency=param.get("to_currency"),
+		from_currency=param.get("from_currency")
+	)
+
 def enable_all_roles_and_domains():
 	""" enable all roles and domain for testing """
 	# add all roles to users
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
index d452ffd..be8597d 100644
--- a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
@@ -73,7 +73,7 @@
 	fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'serial_no as serial_nos', 'actual_qty',
 		'posting_date', 'posting_time', 'company', 'warehouse', '(stock_value_difference / actual_qty) as valuation_rate']
 
-	filters = {'serial_no': ("is", "set")}
+	filters = {'serial_no': ("is", "set"), "is_cancelled": 0}
 
 	if report_filters.get('item_code'):
 		filters['item_code'] = report_filters.get('item_code')
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index b3348f1..ea617fd 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -853,7 +853,7 @@
 @frappe.whitelist()
 def get_sla_doctypes():
 	doctypes = []
-	data = frappe.get_list('Service Level Agreement',
+	data = frappe.get_all('Service Level Agreement',
 		{'enabled': 1},
 		['document_type'],
 		distinct=1
diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py
index fbf2594..bc9f04e 100644
--- a/erpnext/tests/utils.py
+++ b/erpnext/tests/utils.py
@@ -125,17 +125,23 @@
 	if default_filters is None:
 		default_filters = {}
 
+	test_filters = []
 	report_execute_fn = frappe.get_attr(get_report_module_dotted_path(module, report_name) + ".execute")
 	report_filters = frappe._dict(default_filters).copy().update(filters)
 
-	report_data = report_execute_fn(report_filters)
+	test_filters.append(report_filters)
 
 	if optional_filters:
 		for key, value in optional_filters.items():
-			filter_with_optional_param = report_filters.copy().update({key: value})
-			report_execute_fn(filter_with_optional_param)
+			test_filters.append(report_filters.copy().update({key: value}))
 
-	return report_data
+	for test_filter in test_filters:
+		try:
+			report_execute_fn(test_filter)
+		except Exception:
+			print(f"Report failed to execute with filters: {test_filter}")
+			raise
+
 
 
 def timeout(seconds=30, error_message="Test timed out."):
diff --git a/requirements.txt b/requirements.txt
index faefb77..f447fac 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
 # frappe   # https://github.com/frappe/frappe is installed during bench-init
 gocardless-pro~=1.22.0
-googlemaps  # used in ERPNext, but dependency is defined in Frappe
+googlemaps
 pandas~=1.1.5
 plaid-python~=7.2.1
 pycountry~=20.7.3