Merge branch 'version-13' into develop
diff --git a/.github/helper/semgrep_rules/README.md b/.github/helper/semgrep_rules/README.md
new file mode 100644
index 0000000..670d8d2
--- /dev/null
+++ b/.github/helper/semgrep_rules/README.md
@@ -0,0 +1,38 @@
+# Semgrep linting
+
+## What is semgrep?
+Semgrep or "semantic grep" is language agnostic static analysis tool. In simple terms semgrep is syntax-aware `grep`, so unlike regex it doesn't get confused by different ways of writing same thing or whitespaces or code split in multiple lines etc.
+
+Example:
+
+To check if a translate function is using f-string or not the regex would be `r"_\(\s*f[\"']"` while equivalent rule in semgrep would be `_(f"...")`. As semgrep knows grammer of language it takes care of unnecessary whitespace, type of quotation marks etc.
+
+You can read more such examples in `.github/helper/semgrep_rules` directory.
+
+# Why/when to use this?
+We want to maintain quality of contributions, at the same time remembering all the good practices can be pain to deal with while evaluating contributions. Using semgrep if you can translate "best practice" into a rule then it can automate the task for us.
+
+## Running locally
+
+Install semgrep using homebrew `brew install semgrep` or pip `pip install semgrep`.
+
+To run locally use following command:
+
+`semgrep --config=.github/helper/semgrep_rules [file/folder names]`
+
+## Testing
+semgrep allows testing the tests. Refer to this page: https://semgrep.dev/docs/writing-rules/testing-rules/
+
+When writing new rules you should write few positive and few negative cases as shown in the guide and current tests.
+
+To run current tests: `semgrep --test --test-ignore-todo .github/helper/semgrep_rules`
+
+
+## Reference
+
+If you are new to Semgrep read following pages to get started on writing/modifying rules:
+
+- https://semgrep.dev/docs/getting-started/
+- https://semgrep.dev/docs/writing-rules/rule-syntax
+- https://semgrep.dev/docs/writing-rules/pattern-examples/
+- https://semgrep.dev/docs/writing-rules/rule-ideas/#common-use-cases
diff --git a/.github/helper/semgrep_rules/frappe_correctness.py b/.github/helper/semgrep_rules/frappe_correctness.py
new file mode 100644
index 0000000..4798b92
--- /dev/null
+++ b/.github/helper/semgrep_rules/frappe_correctness.py
@@ -0,0 +1,28 @@
+import frappe
+from frappe import _, flt
+
+from frappe.model.document import Document
+
+
+def on_submit(self):
+	if self.value_of_goods == 0:
+		frappe.throw(_('Value of goods cannot be 0'))
+	# ruleid: frappe-modifying-after-submit
+	self.status = 'Submitted'
+
+def on_submit(self):
+	if flt(self.per_billed) < 100:
+		self.update_billing_status()
+	else:
+		# todook: frappe-modifying-after-submit
+		self.status = "Completed"
+		self.db_set("status", "Completed")
+
+class TestDoc(Document):
+	pass
+
+	def validate(self):
+		#ruleid: frappe-modifying-child-tables-while-iterating
+		for item in self.child_table:
+			if item.value < 0:
+				self.remove(item)
diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml
new file mode 100644
index 0000000..54df062
--- /dev/null
+++ b/.github/helper/semgrep_rules/frappe_correctness.yml
@@ -0,0 +1,74 @@
+# This file specifies rules for correctness according to how frappe doctype data model works.
+
+rules:
+- id: frappe-modifying-after-submit
+  patterns:
+    - pattern: self.$ATTR = ...
+    - pattern-inside: |
+        def on_submit(self, ...):
+          ...
+    - metavariable-regex:
+        metavariable: '$ATTR'
+        # this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
+        regex: '^(?!status_updater)(.*)$'
+  message: |
+    Doctype modified after submission. Please check if modification of self.$ATTR is commited to database.
+  languages: [python]
+  severity: ERROR
+
+- id: frappe-modifying-after-cancel
+  patterns:
+    - pattern: self.$ATTR = ...
+    - pattern-inside: |
+        def on_cancel(self, ...):
+          ...
+    - metavariable-regex:
+        metavariable: '$ATTR'
+        regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
+  message: |
+    Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database.
+  languages: [python]
+  severity: ERROR
+
+- id: frappe-print-function-in-doctypes
+  pattern: print(...)
+  message: |
+      Did you mean to leave this print statement in? Consider using msgprint or logger instead of print statement.
+  languages: [python]
+  severity: WARNING
+  paths:
+      exclude:
+        - test_*.py
+      include:
+        - "*/**/doctype/*"
+
+- id: frappe-modifying-child-tables-while-iterating
+  pattern-either:
+    - pattern: |
+        for $ROW in self.$TABLE:
+            ...
+            self.remove(...)
+    - pattern: |
+        for $ROW in self.$TABLE:
+            ...
+            self.append(...)
+  message: |
+      Child table being modified while iterating on it.
+  languages: [python]
+  severity: ERROR
+  paths:
+      include:
+        - "*/**/doctype/*"
+
+- id: frappe-same-key-assigned-twice
+  pattern-either:
+    - pattern: |
+        {..., $X: $A, ..., $X: $B, ...}
+    - pattern: |
+        dict(..., ($X, $A), ..., ($X, $B), ...)
+    - pattern: |
+        _dict(..., ($X, $A), ..., ($X, $B), ...)
+  message: |
+      key `$X` is uselessly assigned twice. This could be a potential bug.
+  languages: [python]
+  severity: ERROR
diff --git a/.github/helper/semgrep_rules/security.py b/.github/helper/semgrep_rules/security.py
new file mode 100644
index 0000000..f477d7c
--- /dev/null
+++ b/.github/helper/semgrep_rules/security.py
@@ -0,0 +1,6 @@
+def function_name(input):
+	# ruleid: frappe-codeinjection-eval
+	eval(input)
+
+# ok: frappe-codeinjection-eval
+eval("1 + 1")
diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml
new file mode 100644
index 0000000..5a5098b
--- /dev/null
+++ b/.github/helper/semgrep_rules/security.yml
@@ -0,0 +1,25 @@
+rules:
+- id: frappe-codeinjection-eval
+  patterns:
+  - pattern-not: eval("...")
+  - pattern: eval(...)
+  message: |
+    Detected the use of eval(). eval() can be dangerous if used to evaluate
+    dynamic content. Avoid it or use safe_eval().
+  languages: [python]
+  severity: ERROR
+
+- id: frappe-sqli-format-strings
+  patterns:
+    - pattern-inside: |
+        @frappe.whitelist()
+        def $FUNC(...):
+            ...
+    - pattern-either:
+        - pattern: frappe.db.sql("..." % ...)
+        - pattern: frappe.db.sql(f"...", ...)
+        - pattern: frappe.db.sql("...".format(...), ...)
+  message: |
+      Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines
+  languages: [python]
+  severity: WARNING
diff --git a/.github/helper/semgrep_rules/translate.js b/.github/helper/semgrep_rules/translate.js
new file mode 100644
index 0000000..7b92fe2
--- /dev/null
+++ b/.github/helper/semgrep_rules/translate.js
@@ -0,0 +1,37 @@
+// ruleid: frappe-translation-empty-string
+__("")
+// ruleid: frappe-translation-empty-string
+__('')
+
+// ok: frappe-translation-js-formatting
+__('Welcome {0}, get started with ERPNext in just a few clicks.', [full_name]);
+
+// ruleid: frappe-translation-js-formatting
+__(`Welcome ${full_name}, get started with ERPNext in just a few clicks.`);
+
+// ok: frappe-translation-js-formatting
+__('This is fine');
+
+
+// ok: frappe-translation-trailing-spaces
+__('This is fine');
+
+// ruleid: frappe-translation-trailing-spaces
+__(' this is not ok ');
+// ruleid: frappe-translation-trailing-spaces
+__('this is not ok ');
+// ruleid: frappe-translation-trailing-spaces
+__(' this is not ok');
+
+// ok: frappe-translation-js-splitting
+__('You have {0} subscribers in your mailing list.', [subscribers.length])
+
+// todoruleid: frappe-translation-js-splitting
+__('You have') + subscribers.length + __('subscribers in your mailing list.')
+
+// ruleid: frappe-translation-js-splitting
+__('You have' + 'subscribers in your mailing list.')
+
+// ruleid: frappe-translation-js-splitting
+__('You have {0} subscribers' +
+    'in your mailing list', [subscribers.length])
diff --git a/.github/helper/semgrep_rules/translate.py b/.github/helper/semgrep_rules/translate.py
new file mode 100644
index 0000000..bd6cd91
--- /dev/null
+++ b/.github/helper/semgrep_rules/translate.py
@@ -0,0 +1,53 @@
+# Examples taken from https://frappeframework.com/docs/user/en/translations
+# This file is used for testing the tests.
+
+from frappe import _
+
+full_name = "Jon Doe"
+# ok: frappe-translation-python-formatting
+_('Welcome {0}, get started with ERPNext in just a few clicks.').format(full_name)
+
+# ruleid: frappe-translation-python-formatting
+_('Welcome %s, get started with ERPNext in just a few clicks.' % full_name)
+# ruleid: frappe-translation-python-formatting
+_('Welcome %(name)s, get started with ERPNext in just a few clicks.' % {'name': full_name})
+
+# ruleid: frappe-translation-python-formatting
+_('Welcome {0}, get started with ERPNext in just a few clicks.'.format(full_name))
+
+
+subscribers = ["Jon", "Doe"]
+# ok: frappe-translation-python-formatting
+_('You have {0} subscribers in your mailing list.').format(len(subscribers))
+
+# ruleid: frappe-translation-python-splitting
+_('You have') + len(subscribers) + _('subscribers in your mailing list.')
+
+# ruleid: frappe-translation-python-splitting
+_('You have {0} subscribers \
+    in your mailing list').format(len(subscribers))
+
+# ok: frappe-translation-python-splitting
+_('You have {0} subscribers') \
+    + 'in your mailing list'
+
+# ruleid: frappe-translation-trailing-spaces
+msg = _(" You have {0} pending invoice ")
+# ruleid: frappe-translation-trailing-spaces
+msg = _("You have {0} pending invoice ")
+# ruleid: frappe-translation-trailing-spaces
+msg = _(" You have {0} pending invoice")
+
+# ok: frappe-translation-trailing-spaces
+msg = ' ' + _("You have {0} pending invoices") + ' '
+
+# ruleid: frappe-translation-python-formatting
+_(f"can not format like this - {subscribers}")
+# ruleid: frappe-translation-python-splitting
+_(f"what" + f"this is also not cool")
+
+
+# ruleid: frappe-translation-empty-string
+_("")
+# ruleid: frappe-translation-empty-string
+_('')
diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml
new file mode 100644
index 0000000..3737da5
--- /dev/null
+++ b/.github/helper/semgrep_rules/translate.yml
@@ -0,0 +1,63 @@
+rules:
+- id: frappe-translation-empty-string
+  pattern-either:
+  - pattern: _("")
+  - pattern: __("")
+  message: |
+    Empty string is useless for translation.
+    Please refer: https://frappeframework.com/docs/user/en/translations
+  languages: [python, javascript, json]
+  severity: ERROR
+
+- id: frappe-translation-trailing-spaces
+  pattern-either:
+    - pattern: _("=~/(^[ \t]+|[ \t]+$)/")
+    - pattern: __("=~/(^[ \t]+|[ \t]+$)/")
+  message: |
+    Trailing or leading whitespace not allowed in translate strings.
+    Please refer: https://frappeframework.com/docs/user/en/translations
+  languages: [python, javascript, json]
+  severity: ERROR
+
+- id: frappe-translation-python-formatting
+  pattern-either:
+  - pattern: _("..." % ...)
+  - pattern: _("...".format(...))
+  - pattern: _(f"...")
+  message: |
+    Only positional formatters are allowed and formatting should not be done before translating.
+    Please refer: https://frappeframework.com/docs/user/en/translations
+  languages: [python]
+  severity: ERROR
+
+- id: frappe-translation-js-formatting
+  patterns:
+  - pattern: __(`...`)
+  - pattern-not: __("...")
+  message: |
+    Template strings are not allowed for text formatting.
+    Please refer: https://frappeframework.com/docs/user/en/translations
+  languages: [javascript, json]
+  severity: ERROR
+
+- id: frappe-translation-python-splitting
+  pattern-either:
+  - pattern: _(...) + ... + _(...)
+  - pattern: _("..." + "...")
+  - pattern-regex: '_\([^\)]*\\\s*'
+  message: |
+    Do not split strings inside translate function. Do not concatenate using translate functions.
+    Please refer: https://frappeframework.com/docs/user/en/translations
+  languages: [python]
+  severity: ERROR
+
+- id: frappe-translation-js-splitting
+  pattern-either:
+  - pattern-regex: '__\([^\)]*[\+\\]\s*'
+  - pattern: __('...' + '...')
+  - pattern: __('...') + __('...')
+  message: |
+    Do not split strings inside translate function. Do not concatenate using translate functions.
+    Please refer: https://frappeframework.com/docs/user/en/translations
+  languages: [javascript, json]
+  severity: ERROR
diff --git a/.github/helper/semgrep_rules/ux.py b/.github/helper/semgrep_rules/ux.py
new file mode 100644
index 0000000..4a74457
--- /dev/null
+++ b/.github/helper/semgrep_rules/ux.py
@@ -0,0 +1,31 @@
+import frappe
+from frappe import msgprint, throw, _
+
+
+# ruleid: frappe-missing-translate-function
+throw("Error Occured")
+
+# ruleid: frappe-missing-translate-function
+frappe.throw("Error Occured")
+
+# ruleid: frappe-missing-translate-function
+frappe.msgprint("Useful message")
+
+# ruleid: frappe-missing-translate-function
+msgprint("Useful message")
+
+
+# ok: frappe-missing-translate-function
+translatedmessage = _("Hello")
+
+# ok: frappe-missing-translate-function
+throw(translatedmessage)
+
+# ok: frappe-missing-translate-function
+msgprint(translatedmessage)
+
+# ok: frappe-missing-translate-function
+msgprint(_("Helpful message"))
+
+# ok: frappe-missing-translate-function
+frappe.throw(_("Error occured"))
diff --git a/.github/helper/semgrep_rules/ux.yml b/.github/helper/semgrep_rules/ux.yml
new file mode 100644
index 0000000..ed06a6a
--- /dev/null
+++ b/.github/helper/semgrep_rules/ux.yml
@@ -0,0 +1,15 @@
+rules:
+- id: frappe-missing-translate-function
+  pattern-either:
+  - patterns:
+      - pattern: frappe.msgprint("...", ...)
+      - pattern-not: frappe.msgprint(_("..."), ...)
+      - pattern-not: frappe.msgprint(__("..."), ...)
+  - patterns:
+      - pattern: frappe.throw("...", ...)
+      - pattern-not: frappe.throw(_("..."), ...)
+      - pattern-not: frappe.throw(__("..."), ...)
+  message: |
+      All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
+  languages: [python, javascript, json]
+  severity: ERROR
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 78c2f5a..824b74e 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -85,10 +85,9 @@
         run: |
           cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
           cd ${GITHUB_WORKSPACE}
-          pip install coveralls==2.2.0
-          pip install coverage==4.5.4
-          coveralls
+          pip install coveralls==3.0.1
+          pip install coverage==5.5
+          coveralls --service=github
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
-
diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml
new file mode 100644
index 0000000..df08263
--- /dev/null
+++ b/.github/workflows/semgrep.yml
@@ -0,0 +1,24 @@
+name: Semgrep
+
+on:
+  pull_request:
+    branches:
+      - develop
+jobs:
+  semgrep:
+    name: Frappe Linter
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: Setup python3
+      uses: actions/setup-python@v2
+      with:
+        python-version: 3.8
+    - name: Run semgrep
+      run: |
+        python -m pip install -q semgrep
+        git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
+        files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
+        [[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
+        semgrep --config="r/python.lang.correctness" --quiet --error $files
+        [[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index a3c29b6..e1276e7 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -12,6 +12,7 @@
   "frozen_accounts_modifier",
   "determine_address_tax_category_from",
   "over_billing_allowance",
+  "role_allowed_to_over_bill",
   "column_break_4",
   "credit_controller",
   "check_supplier_invoice_uniqueness",
@@ -226,6 +227,13 @@
    "fieldname": "delete_linked_ledger_entries",
    "fieldtype": "Check",
    "label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
+  },
+  {
+   "description": "Users with this role are allowed to over bill above the allowance percentage",
+   "fieldname": "role_allowed_to_over_bill",
+   "fieldtype": "Link",
+   "label": "Role Allowed to Over Bill ",
+   "options": "Role"
   }
  ],
  "icon": "icon-cog",
@@ -233,7 +241,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-01-05 13:04:00.118892",
+ "modified": "2021-03-11 18:52:05.601996",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
index 69ee497..88aa7ef 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.json
@@ -175,22 +175,24 @@
   },
   {
    "fieldname": "deposit",
-   "oldfieldname": "debit",
    "fieldtype": "Currency",
    "in_list_view": 1,
-   "label": "Deposit"
+   "label": "Deposit",
+   "oldfieldname": "debit",
+   "options": "currency"
   },
   {
    "fieldname": "withdrawal",
-   "oldfieldname": "credit",
    "fieldtype": "Currency",
    "in_list_view": 1,
-   "label": "Withdrawal"
+   "label": "Withdrawal",
+   "oldfieldname": "credit",
+   "options": "currency"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-30 19:40:54.221070",
+ "modified": "2021-04-14 17:31:58.963529",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Bank Transaction",
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
index e1ddeff..94ae79a 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -38,22 +38,22 @@
 					{% endif %}
 					</td>
 					<td style="text-align: right">
-						{{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}</td>
+						{{ frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}</td>
 					<td style="text-align: right">
-						{{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}</td>
+						{{ frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}</td>
 			{% else %}
 				<td></td>
 				<td></td>
 				<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or "&nbsp;" }}</b></td>
 				<td style="text-align: right">
-					{{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}
+					{{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
 				</td>
 				<td style="text-align: right">
-					{{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}
+					{{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
 				</td>
 			{% endif %}
 				<td style="text-align: right">
-					{{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }}
+					{{ frappe.utils.fmt_money(row.balance, currency=filters.presentation_currency) }}
 				</td>
 			</tr>
 		{% endfor %}
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
index 523e9ee..7d93023 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -9,7 +9,7 @@
 from frappe.model.naming import make_autoname
 from frappe.model.document import Document
 
-pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group'
+pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group',
 	'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from',
 	'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier',
 	'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules']
@@ -111,4 +111,4 @@
 	for d in pricing_rule_fields:
 		args[d] = doc.get(d)
 
-	return args
\ No newline at end of file
+	return args
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index d382386..c6c67b4 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -118,6 +118,7 @@
   "in_words",
   "total_advance",
   "outstanding_amount",
+  "disable_rounded_total",
   "advances_section",
   "allocate_advances_automatically",
   "get_advances",
@@ -1109,6 +1110,7 @@
    "reqd": 1
   },
   {
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "base_rounding_adjustment",
    "fieldtype": "Currency",
    "hide_days": 1,
@@ -1120,6 +1122,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "base_rounded_total",
    "fieldtype": "Currency",
    "hide_days": 1,
@@ -1168,6 +1171,7 @@
    "reqd": 1
   },
   {
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "rounding_adjustment",
    "fieldtype": "Currency",
    "hide_days": 1,
@@ -1180,6 +1184,7 @@
   },
   {
    "bold": 1,
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "rounded_total",
    "fieldtype": "Currency",
    "hide_days": 1,
@@ -1945,6 +1950,13 @@
    "fieldtype": "Link",
    "label": "Set Target Warehouse",
    "options": "Warehouse"
+  },
+  {
+   "default": "0",
+   "depends_on": "grand_total",
+   "fieldname": "disable_rounded_total",
+   "fieldtype": "Check",
+   "label": "Disable Rounded Total"
   }
  ],
  "icon": "fa fa-file-text",
@@ -1957,7 +1969,7 @@
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2021-03-31 15:42:26.261540",
+ "modified": "2021-04-15 23:57:58.766651",
  "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 3c91dcc..4461f29 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -46,7 +46,6 @@
 			'target_parent_dt': 'Sales Order',
 			'target_parent_field': 'per_billed',
 			'source_field': 'amount',
-			'join_field': 'so_detail',
 			'percent_join_field': 'sales_order',
 			'status_field': 'billing_status',
 			'keyword': 'Billed',
@@ -276,7 +275,7 @@
 				pluck="pos_closing_entry"
 			)
 			if pos_closing_entry:
-				msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format(
+				msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format(
 					frappe.bold("Consolidated Sales Invoice"),
 					get_link_to_form("POS Closing Entry", pos_closing_entry[0])
 				)
@@ -549,12 +548,12 @@
 			frappe.throw(_("Debit To is required"), title=_("Account Missing"))
 
 		if account.report_type != "Balance Sheet":
-			msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To"))
+			msg = _("Please ensure {} account is a Balance Sheet account.").format(frappe.bold("Debit To")) + " "
 			msg += _("You can change the parent account to a Balance Sheet account or select a different account.")
 			frappe.throw(msg, title=_("Invalid Account"))
 
 		if self.customer and account.account_type != "Receivable":
-			msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To"))
+			msg = _("Please ensure {} account is a Receivable account.").format(frappe.bold("Debit To")) + " "
 			msg += _("Change the account type to Receivable or select a different account.")
 			frappe.throw(msg, title=_("Invalid Account"))
 
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 961bdb1..09db7fe 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -251,7 +251,7 @@
 	threshold = tax_details.get('threshold', 0)
 	cumulative_threshold = tax_details.get('cumulative_threshold', 0)
 
-	if ((threshold and supp_credit_amt >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
+	if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
 		if ldc and is_valid_certificate(
 			ldc.valid_from, ldc.valid_upto,
 			inv.posting_date, tax_deducted,
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index dd3b49a..0cea761 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -87,50 +87,6 @@
 		for d in invoices:
 			d.cancel()
 
-	def test_single_threshold_tds_with_previous_vouchers(self):
-		invoices = []
-		frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS")
-		pi = create_purchase_invoice(supplier="Test TDS Supplier2")
-		pi.submit()
-		invoices.append(pi)
-
-		pi = create_purchase_invoice(supplier="Test TDS Supplier2")
-		pi.submit()
-		invoices.append(pi)
-
-		self.assertEqual(pi.taxes_and_charges_deducted, 2000)
-		self.assertEqual(pi.grand_total, 8000)
-
-		# delete invoices to avoid clashing
-		for d in invoices:
-			d.cancel()
-
-	def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self):
-		invoices = []
-		doc = create_supplier(supplier_name = "Test TDS Supplier ABC",
-			tax_withholding_category="Single Threshold TDS")
-		supplier = doc.name
-
-		pi = create_purchase_invoice(supplier=supplier)
-		pi.submit()
-		invoices.append(pi)
-
-		# TDS not applied
-		pi = create_purchase_invoice(supplier=supplier, do_not_apply_tds=True)
-		pi.submit()
-		invoices.append(pi)
-
-		pi = create_purchase_invoice(supplier=supplier)
-		pi.submit()
-		invoices.append(pi)
-
-		self.assertEqual(pi.taxes_and_charges_deducted, 2000)
-		self.assertEqual(pi.grand_total, 8000)
-
-		# delete invoices to avoid clashing
-		for d in invoices:
-			d.cancel()
-
 	def test_cumulative_threshold_tcs(self):
 		frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
 		invoices = []
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 5baf693..1dbd7c6 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -56,6 +56,8 @@
   "base_net_amount",
   "warehouse_and_reference",
   "warehouse",
+  "actual_qty",
+  "company_total_stock",
   "material_request",
   "material_request_item",
   "sales_order",
@@ -744,6 +746,22 @@
    "read_only": 1
   },
   {
+   "allow_on_submit": 1,
+   "fieldname": "actual_qty",
+   "fieldtype": "Float",
+   "label": "Available Qty at Warehouse",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "allow_on_submit": 1,
+   "fieldname": "company_total_stock",
+   "fieldtype": "Float",
+   "label": "Available Qty at Company",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
    "collapsible": 1,
    "fieldname": "discount_and_margin_section",
    "fieldtype": "Section Break",
@@ -791,7 +809,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-02-23 01:00:27.132705",
+ "modified": "2021-03-22 11:46:12.357435",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order Item",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 33fbf1c..d36e7b0 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -717,7 +717,9 @@
 						total_billed_amt = abs(total_billed_amt)
 						max_allowed_amt = abs(max_allowed_amt)
 
-					if total_billed_amt - max_allowed_amt > 0.01:
+					role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
+
+					if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
 						frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
 							.format(item.item_code, item.idx, max_allowed_amt))
 
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 219d529..b686dc0 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -6,6 +6,7 @@
 from frappe import _, msgprint
 from frappe.utils import flt,cint, cstr, getdate
 from six import iteritems
+from collections import OrderedDict
 from erpnext.accounts.party import get_party_details
 from erpnext.stock.get_item_details import get_conversion_factor
 from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
@@ -391,10 +392,12 @@
 
 					batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
 						qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
+
 					for batch_data in batches_qty:
 						qty = batch_data['qty']
 						raw_material.batch_no = batch_data['batch']
-						self.append_raw_material_to_be_backflushed(item, raw_material, qty)
+						if qty > 0:
+							self.append_raw_material_to_be_backflushed(item, raw_material, qty)
 				else:
 					self.append_raw_material_to_be_backflushed(item, raw_material, qty)
 
@@ -1056,7 +1059,7 @@
 	for batch_data in transferred_batches:
 		key = ((batch_data.item_code, fg_item)
 			if batch_data.subcontracted_item else (batch_data.item_code, purchase_order))
-		transferred_batch_qty_map.setdefault(key, {})
+		transferred_batch_qty_map.setdefault(key, OrderedDict())
 		transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty
 
 	return transferred_batch_qty_map
@@ -1109,8 +1112,14 @@
 		if available_qty >= required_qty:
 			available_batches.append({'batch': batch, 'qty': required_qty})
 			break
-		else:
+		elif available_qty != 0:
 			available_batches.append({'batch': batch, 'qty': available_qty})
 			required_qty -= available_qty
 
+	for row in available_batches:
+		if backflushed_batches.get(row.get('batch'), 0) > 0:
+			backflushed_batches[row.get('batch')] += row.get('qty')
+		else:
+			backflushed_batches[row.get('batch')] = row.get('qty')
+
 	return available_batches
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index c0c1315..bc1ac5e 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -713,7 +713,9 @@
 		return [(d,) for d in set(taxes)]
 
 
-def get_fields(doctype, fields=[]):
+def get_fields(doctype, fields=None):
+	if fields is None:
+		fields = []
 	meta = frappe.get_meta(doctype)
 	fields.extend(meta.get_search_fields())
 
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 0987d09..5276da9 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -201,10 +201,14 @@
 			get_allowance_for(item['item_code'], self.item_allowance,
 				self.global_qty_allowance, self.global_amount_allowance, qty_or_amount)
 
-		overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
-		 	item[args['target_ref_field']]) * 100
+		role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive')
+		role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
+		role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill
 
-		if overflow_percent - allowance > 0.01:
+		overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
+			item[args['target_ref_field']]) * 100
+
+		if overflow_percent - allowance > 0.01 and role not in frappe.get_roles():
 			item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
 			item['reduce_by'] = item[args['target_field']] - item['max_allowed']
 
@@ -371,10 +375,12 @@
 			ref_doc.db_set("per_billed", per_billed)
 			ref_doc.set_status(update=True)
 
-def get_allowance_for(item_code, item_allowance={}, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):
+def get_allowance_for(item_code, item_allowance=None, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):
 	"""
 		Returns the allowance for the item, if not set, returns global allowance
 	"""
+	if item_allowance is None:
+		item_allowance = {}
 	if qty_or_amount == "qty":
 		if item_allowance.get(item_code, frappe._dict()).get("qty"):
 			return item_allowance[item_code].qty, item_allowance, global_qty_allowance, global_amount_allowance
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 2049957..b14c274 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -117,7 +117,6 @@
 							"account": expense_account,
 							"against": warehouse_account[sle.warehouse]["account"],
 							"cost_center": item_row.cost_center,
-							"project": item_row.project or self.get('project'),
 							"remarks": self.get("remarks") or "Accounting Entry for Stock",
 							"credit": flt(sle.stock_value_difference, precision),
 							"project": item_row.get("project") or self.get("project"),
@@ -483,7 +482,7 @@
 			)
 		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)
+		message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
 		return message
 
 	def repost_future_sle_and_gle(self):
diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py
index f7aa6e9..2b3acf1 100644
--- a/erpnext/education/doctype/course_enrollment/course_enrollment.py
+++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py
@@ -41,7 +41,7 @@
 			frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format(
 				get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry'))
 
-	def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status):
+	def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status, time_taken):
 		result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
 		result_data = []
 		for key in answers:
@@ -66,7 +66,8 @@
 			"activity_date": frappe.utils.datetime.datetime.now(),
 			"result": result_data,
 			"score": score,
-			"status": status
+			"status": status,
+			"time_taken": time_taken
 			}).insert(ignore_permissions = True)
 
 	def add_activity(self, content_type, content):
diff --git a/erpnext/education/doctype/quiz/quiz.json b/erpnext/education/doctype/quiz/quiz.json
index 569c281..16d7d7e 100644
--- a/erpnext/education/doctype/quiz/quiz.json
+++ b/erpnext/education/doctype/quiz/quiz.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "field:title",
@@ -12,7 +13,10 @@
   "quiz_configuration_section",
   "passing_score",
   "max_attempts",
-  "grading_basis"
+  "grading_basis",
+  "column_break_7",
+  "is_time_bound",
+  "duration"
  ],
  "fields": [
   {
@@ -58,9 +62,26 @@
    "fieldtype": "Select",
    "label": "Grading Basis",
    "options": "Latest Highest Score\nLatest Attempt"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_time_bound",
+   "fieldtype": "Check",
+   "label": "Is Time-Bound"
+  },
+  {
+   "depends_on": "is_time_bound",
+   "fieldname": "duration",
+   "fieldtype": "Duration",
+   "label": "Duration"
+  },
+  {
+   "fieldname": "column_break_7",
+   "fieldtype": "Column Break"
   }
  ],
- "modified": "2019-06-12 12:23:57.020508",
+ "links": [],
+ "modified": "2020-12-24 15:41:35.043262",
  "modified_by": "Administrator",
  "module": "Education",
  "name": "Quiz",
diff --git a/erpnext/education/doctype/quiz_activity/quiz_activity.json b/erpnext/education/doctype/quiz_activity/quiz_activity.json
index e78db42..742c887 100644
--- a/erpnext/education/doctype/quiz_activity/quiz_activity.json
+++ b/erpnext/education/doctype/quiz_activity/quiz_activity.json
@@ -1,490 +1,163 @@
 {
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
+ "actions": [],
  "autoname": "format:EDU-QA-{YYYY}-{#####}",
  "beta": 1,
  "creation": "2018-10-15 15:48:40.482821",
- "custom": 0,
- "docstatus": 0,
  "doctype": "DocType",
- "document_type": "",
  "editable_grid": 1,
  "engine": "InnoDB",
+ "field_order": [
+  "enrollment",
+  "student",
+  "column_break_3",
+  "course",
+  "section_break_5",
+  "quiz",
+  "column_break_7",
+  "status",
+  "section_break_9",
+  "result",
+  "section_break_11",
+  "activity_date",
+  "score",
+  "column_break_14",
+  "time_taken"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "enrollment",
    "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": "Enrollment",
-   "length": 0,
-   "no_copy": 0,
    "options": "Course Enrollment",
-   "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": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fetch_from": "enrollment.student",
    "fieldname": "student",
    "fieldtype": "Link",
-   "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": "Student",
-   "length": 0,
-   "no_copy": 0,
    "options": "Student",
-   "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
+   "read_only": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "column_break_3",
-   "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
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fetch_from": "enrollment.course",
    "fieldname": "course",
    "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": "Course",
-   "length": 0,
-   "no_copy": 0,
    "options": "Course",
-   "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": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "section_break_5",
-   "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
+   "fieldtype": "Section Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "quiz",
    "fieldtype": "Link",
-   "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": "Quiz",
-   "length": 0,
-   "no_copy": 0,
    "options": "Quiz",
-   "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": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "column_break_7",
-   "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
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 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": 0,
-   "in_standard_filter": 0,
    "label": "Status",
-   "length": 0,
-   "no_copy": 0,
    "options": "\nPass\nFail",
-   "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
+   "read_only": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "section_break_9",
-   "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
+   "fieldtype": "Section Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "result",
    "fieldtype": "Table",
-   "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": "Result",
-   "length": 0,
-   "no_copy": 0,
    "options": "Quiz Result",
-   "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": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "activity_date",
    "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": "Activity Date",
-   "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": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "score",
    "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": "Score",
-   "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": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
+  },
+  {
+   "fieldname": "time_taken",
+   "fieldtype": "Duration",
+   "label": "Time Taken",
+   "set_only_once": 1
+  },
+  {
+   "fieldname": "section_break_11",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_14",
+   "fieldtype": "Column Break"
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-11-25 19:05:52.434437",
+ "links": [],
+ "modified": "2020-12-24 15:41:20.085380",
  "modified_by": "Administrator",
  "module": "Education",
  "name": "Quiz Activity",
- "name_case": "",
  "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "Academics User",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "LMS User",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
-   "create": 0,
-   "delete": 0,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "Instructor",
-   "set_user_permissions": 0,
-   "share": 1,
-   "submit": 0,
-   "write": 0
+   "share": 1
   }
  ],
  "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/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index 81626f1..2dc0f63 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -114,7 +114,7 @@
 					status = check_content_completion(content.name, content.doctype, course_enrollment_name)
 					progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status})
 				elif content.doctype == 'Quiz':
-					status, score, result = check_quiz_completion(content, course_enrollment_name)
+					status, score, result, time_taken = check_quiz_completion(content, course_enrollment_name)
 					progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result})
 		return progress
 
diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py
index cffc396..8f51fef 100644
--- a/erpnext/education/utils.py
+++ b/erpnext/education/utils.py
@@ -194,7 +194,7 @@
 		return enrollment.add_activity(content_type, content)
 
 @frappe.whitelist()
-def evaluate_quiz(quiz_response, quiz_name, course, program):
+def evaluate_quiz(quiz_response, quiz_name, course, program, time_taken):
 	import json
 
 	student = get_current_student()
@@ -209,7 +209,7 @@
 	if student:
 		enrollment = get_or_create_course_enrollment(course, program)
 		if quiz.allowed_attempt(enrollment, quiz_name):
-			enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status)
+			enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status, time_taken)
 			return {'result': result, 'score': score, 'status': status}
 		else:
 			return None
@@ -219,8 +219,9 @@
 	try:
 		quiz = frappe.get_doc("Quiz", quiz_name)
 		questions = quiz.get_questions()
+		duration = quiz.duration
 	except:
-		frappe.throw(_("Quiz {0} does not exist").format(quiz_name))
+		frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError)
 		return None
 
 	questions = [{
@@ -232,12 +233,20 @@
 		} for question in questions]
 
 	if has_super_access():
-		return {'questions': questions, 'activity': None}
+		return {
+			'questions': questions,
+			'activity': None,
+			'duration':duration
+		}
 
 	student = get_current_student()
 	course_enrollment = get_enrollment("course", course, student.name)
-	status, score, result = check_quiz_completion(quiz, course_enrollment)
-	return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}}
+	status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment)
+	return {
+		'questions': questions, 
+		'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken},
+		'duration': quiz.duration
+	}
 
 def get_topic_progress(topic, course_name, program):
 	"""
@@ -361,15 +370,23 @@
 		return False
 
 def check_quiz_completion(quiz, enrollment_name):
-	attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"])
+	attempts = frappe.get_all("Quiz Activity",
+		filters={
+			'enrollment': enrollment_name, 
+			'quiz': quiz.name
+		}, 
+		fields=["name", "activity_date", "score", "status", "time_taken"]
+	)
 	status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts)
 	score = None
 	result = None
+	time_taken = None
 	if attempts:
 		if quiz.grading_basis == 'Last Highest Score':
 			attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True)
 		score = attempts[0]['score']
 		result = attempts[0]['status']
+		time_taken = attempts[0]['time_taken']
 		if result == 'Pass':
 			status = True
-	return status, score, result
\ No newline at end of file
+	return status, score, result, time_taken
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
index 818f125..3fa98b6 100644
--- a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
+++ b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json
@@ -1,206 +1,64 @@
 {
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2018-07-12 12:07:36.932333", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2018-07-12 12:07:36.932333",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "service_unit",
+  "check_in",
+  "left",
+  "check_out",
+  "invoiced"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "service_unit", 
-   "fieldtype": "Link", 
-   "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": "Healthcare Service Unit", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Healthcare Service Unit", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "service_unit",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Healthcare Service Unit",
+   "options": "Healthcare Service Unit",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "check_in", 
-   "fieldtype": "Datetime", 
-   "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": "Check In", 
-   "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
-  }, 
+   "fieldname": "check_in",
+   "fieldtype": "Datetime",
+   "in_list_view": 1,
+   "label": "Check In"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "left", 
-   "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": "Left", 
-   "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": 1, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "default": "0",
+   "fieldname": "left",
+   "fieldtype": "Check",
+   "label": "Left",
+   "read_only": 1,
+   "search_index": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "check_out", 
-   "fieldtype": "Datetime", 
-   "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": "Check Out", 
-   "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
-  }, 
+   "fieldname": "check_out",
+   "fieldtype": "Datetime",
+   "label": "Check Out"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "0", 
-   "fieldname": "invoiced", 
-   "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": "Invoiced", 
-   "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
+   "default": "0",
+   "fieldname": "invoiced",
+   "fieldtype": "Check",
+   "label": "Invoiced",
+   "read_only": 1
   }
- ], 
- "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": "2018-11-04 03:33:26.958713", 
- "modified_by": "Administrator", 
- "module": "Healthcare", 
- "name": "Inpatient Occupancy", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Healthcare", 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-03-18 15:08:54.634132",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Inpatient Occupancy",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Healthcare",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
index aaf0e85..0e1c2ba 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
@@ -185,7 +185,7 @@
    "fieldtype": "Datetime",
    "in_list_view": 1,
    "label": "Admitted Datetime",
-   "read_only": 1
+   "permlevel": 2
   },
   {
    "depends_on": "eval:(doc.expected_length_of_stay > 0)",
@@ -312,7 +312,7 @@
    "fieldname": "inpatient_occupancies",
    "fieldtype": "Table",
    "options": "Inpatient Occupancy",
-   "read_only": 1
+   "permlevel": 2
   },
   {
    "fieldname": "btn_transfer",
@@ -407,12 +407,12 @@
    "fieldname": "discharge_datetime",
    "fieldtype": "Datetime",
    "label": "Discharge Date",
-   "read_only": 1
+   "permlevel": 2
   }
  ],
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2021-03-18 14:44:11.689956",
+ "modified": "2021-03-18 15:59:17.318988",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Inpatient Record",
@@ -465,6 +465,37 @@
    "read": 1,
    "report": 1,
    "role": "Nursing User"
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "permlevel": 2,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Healthcare Administrator",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "permlevel": 2,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Physician",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "permlevel": 2,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Nursing User",
+   "share": 1
   }
  ],
  "restrict_to_domain": "Healthcare",
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 3a300c0..ae02c51 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -218,8 +218,7 @@
    "fieldname": "leave_policy_assignment",
    "fieldtype": "Link",
    "label": "Leave Policy Assignment",
-   "options": "Leave Policy Assignment",
-   "read_only": 1
+   "options": "Leave Policy Assignment"
   },
   {
    "fetch_from": "employee.company",
@@ -236,7 +235,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-01-04 18:46:13.184104",
+ "modified": "2021-04-14 15:28:26.335104",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Allocation",
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index 4f8ceb0..c9f23ca 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -360,13 +360,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-10 09:28:21.946972",
+ "modified": "2021-04-19 18:10:32.360818",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.json b/erpnext/loan_management/doctype/loan_application/loan_application.json
index a353a77..f91fa07 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.json
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.json
@@ -212,15 +212,17 @@
    "read_only": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-03-01 10:21:44.413353",
+ "modified": "2021-04-19 18:24:40.119647",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Application",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -235,6 +237,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "create": 1,
    "delete": 1,
    "email": 1,
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
index 662c626..7811d56 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
@@ -154,13 +154,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-10 10:03:41.502210",
+ "modified": "2021-04-19 18:09:32.175355",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Disbursement",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -175,6 +176,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
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 185bf7a..30e2328 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
@@ -185,13 +185,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-01-10 00:15:21.544140",
+ "modified": "2021-04-19 18:26:38.871889",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Interest Accrual",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -206,6 +207,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 8fbf233..6479853 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -248,13 +248,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-10 10:00:31.859076",
+ "modified": "2021-04-19 18:10:00.935364",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Repayment",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -269,6 +270,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
index 7dd5725..18bd4ae 100644
--- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
+++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
@@ -160,13 +160,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-09-04 22:38:19.894488",
+ "modified": "2021-04-19 18:23:16.953305",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Security Pledge",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -181,6 +182,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
index 2e2b251..92923bb 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.json
@@ -126,13 +126,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-09-04 22:39:57.756146",
+ "modified": "2021-04-19 18:12:01.401744",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Security Unpledge",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -147,6 +148,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json
index 3ef5304..c0a5d2c 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.json
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.json
@@ -154,13 +154,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-01-17 06:51:26.082879",
+ "modified": "2021-04-19 18:10:57.368490",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Type",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json
index 4617a62..4ca9ef1 100644
--- a/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json
+++ b/erpnext/loan_management/doctype/loan_write_off/loan_write_off.json
@@ -116,13 +116,14 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-26 07:13:43.663924",
+ "modified": "2021-04-19 18:11:27.759862",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Write Off",
  "owner": "Administrator",
  "permissions": [
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
@@ -137,6 +138,7 @@
    "write": 1
   },
   {
+   "amend": 1,
    "cancel": 1,
    "create": 1,
    "delete": 1,
diff --git a/erpnext/manufacturing/dashboard_fixtures.py b/erpnext/manufacturing/dashboard_fixtures.py
index 0e9a21c..7ba43d6 100644
--- a/erpnext/manufacturing/dashboard_fixtures.py
+++ b/erpnext/manufacturing/dashboard_fixtures.py
@@ -43,7 +43,6 @@
 	return [{
 		"doctype": "Dashboard Chart",
 		"based_on": "modified",
-		"time_interval": "Yearly",
 		"chart_type": "Sum",
 		"chart_name": _("Produced Quantity"),
 		"name": "Produced Quantity",
@@ -60,7 +59,6 @@
 	}, {
 		"doctype": "Dashboard Chart",
 		"based_on": "creation",
-		"time_interval": "Yearly",
 		"chart_type": "Sum",
 		"chart_name": _("Completed Operation"),
 		"name": "Completed Operation",
@@ -238,4 +236,4 @@
 		"label": _("Monthly Quality Inspections"),
 		"show_percentage_stats": 1,
 		"stats_time_interval": "Weekly"
-	}]
\ No newline at end of file
+	}]
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index 742d18c..8fbcd4e 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -53,7 +53,9 @@
 			rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2 and parenttype='BOM'""",
 			(self.new_bom, unit_cost, unit_cost, self.current_bom))
 
-	def get_parent_boms(self, bom, bom_list=[]):
+	def get_parent_boms(self, bom, bom_list=None):
+		if bom_list is None:
+			bom_list = []
 		data = frappe.db.sql("""SELECT DISTINCT parent FROM `tabBOM Item`
 			WHERE bom_no = %s AND docstatus < 2 AND parenttype='BOM'""", bom)
 
@@ -106,4 +108,4 @@
 	for bom in bom_list:
 		frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True)
 
-	frappe.db.auto_commit_on_many_writes = 0
\ No newline at end of file
+	frappe.db.auto_commit_on_many_writes = 0
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index cef2d8b..a3e23a6 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -561,7 +561,6 @@
 			'item_name': row.item_name,
 			'quantity': required_qty,
 			'required_bom_qty': total_qty,
-			'description': row.description,
 			'stock_uom': row.get("stock_uom"),
 			'warehouse': warehouse or row.get('source_warehouse') \
 				or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"),
@@ -766,7 +765,7 @@
 		to_enable = frappe.bold(_("Ignore Existing Projected Quantity"))
 		warehouse = frappe.bold(doc.get('for_warehouse'))
 		message = _("As there are sufficient raw materials, Material Request is not required for Warehouse {0}.").format(warehouse) + "<br><br>"
-		message += _(" If you still want to proceed, please enable {0}.").format(to_enable)
+		message += _("If you still want to proceed, please enable {0}.").format(to_enable)
 
 		frappe.msgprint(message, title=_("Note"))
 
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 1f80088..620cc5b 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -693,7 +693,7 @@
 execute:frappe.reload_doc('desk', 'doctype', 'number_card_link')
 execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')
 erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo
-erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2020-05-25
+erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2021-04-16
 erpnext.patches.v12_0.update_bom_in_so_mr
 execute:frappe.delete_doc("Report", "Department Analytics")
 execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True)
@@ -772,3 +772,4 @@
 erpnext.patches.v13_0.fix_non_unique_represents_company
 erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
 erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
+erpnext.patches.v13_0.update_shipment_status
diff --git a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
index be5e30f..a5b93f6 100644
--- a/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
+++ b/erpnext/patches/v13_0/set_company_field_in_healthcare_doctypes.py
@@ -3,7 +3,7 @@
 
 def execute():
 	company = frappe.db.get_single_value('Global Defaults', 'default_company')
-	doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection' 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment']
+	doctypes = ['Clinical Procedure', 'Inpatient Record', 'Lab Test', 'Sample Collection', 'Patient Appointment', 'Patient Encounter', 'Vital Signs', 'Therapy Session', 'Therapy Plan', 'Patient Assessment']
 	for entry in doctypes:
 		if frappe.db.exists('DocType', entry):
 			frappe.reload_doc('Healthcare', 'doctype', entry)
diff --git a/erpnext/patches/v13_0/update_shipment_status.py b/erpnext/patches/v13_0/update_shipment_status.py
new file mode 100644
index 0000000..c425599
--- /dev/null
+++ b/erpnext/patches/v13_0/update_shipment_status.py
@@ -0,0 +1,14 @@
+import frappe
+
+def execute():
+	frappe.reload_doc("stock", "doctype", "shipment")
+
+	# update submitted status
+	frappe.db.sql("""UPDATE `tabShipment`
+					SET status = "Submitted"
+					WHERE status = "Draft" AND docstatus = 1""")
+
+	# update cancelled status
+	frappe.db.sql("""UPDATE `tabShipment`
+					SET status = "Cancelled"
+					WHERE status = "Draft" AND docstatus = 2""")
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js
index b539b1b..e00bd87 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -133,8 +133,6 @@
 			title: __("Assign to Employees"),
 			fields: [
 				{fieldname: "sec_break", fieldtype: "Section Break", label: __("Filter Employees By (Optional)")},
-				{fieldname: "company", fieldtype: "Link", options: "Company", label: __("Company"), default: frm.doc.company, read_only:1},
-				{fieldname: "currency", fieldtype: "Link", options: "Currency", label: __("Currency"), default: frm.doc.currency, read_only:1},
 				{fieldname: "grade", fieldtype: "Link", options: "Employee Grade", label: __("Employee Grade")},
 				{fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
 				{fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py
index 352c180..58c445f 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py
@@ -88,7 +88,7 @@
 		return employees
 
 	@frappe.whitelist()
-	def assign_salary_structure(self, grade=None, department=None, designation=None,employee=None,
+	def assign_salary_structure(self, grade=None, department=None, designation=None, employee=None,
 			payroll_payable_account=None, from_date=None, base=None, variable=None, income_tax_slab=None):
 		employees = self.get_employees(company= self.company, grade= grade,department= department,designation= designation,name=employee)
 
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index 160cc58..ef4740d 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -11,15 +11,16 @@
   "project",
   "issue",
   "type",
+  "color",
   "is_group",
   "is_template",
   "column_break0",
   "status",
   "priority",
   "task_weight",
-  "completed_by",
-  "color",
   "parent_task",
+  "completed_by",
+  "completed_on",
   "sb_timeline",
   "exp_start_date",
   "expected_time",
@@ -358,6 +359,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval: doc.status == \"Completed\"",
    "fieldname": "completed_by",
    "fieldtype": "Link",
    "label": "Completed By",
@@ -381,6 +383,13 @@
    "fieldname": "duration",
    "fieldtype": "Int",
    "label": "Duration (Days)"
+  },
+  {
+   "depends_on": "eval: doc.status == \"Completed\"",
+   "fieldname": "completed_on",
+   "fieldtype": "Date",
+   "label": "Completed On",
+   "mandatory_depends_on": "eval: doc.status == \"Completed\""
   }
  ],
  "icon": "fa fa-check",
@@ -388,7 +397,7 @@
  "is_tree": 1,
  "links": [],
  "max_attachments": 5,
- "modified": "2020-12-28 11:32:58.714991",
+ "modified": "2021-04-16 12:46:51.556741",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Task",
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index 855ff5f..d1583f1 100755
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -36,6 +36,7 @@
 		self.validate_status()
 		self.update_depends_on()
 		self.validate_dependencies_for_template_task()
+		self.validate_completed_on()
 
 	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):
@@ -100,6 +101,10 @@
 					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 validate_completed_on(self):
+		if self.completed_on and getdate(self.completed_on) > getdate():
+			frappe.throw(_("Completed On cannot be greater than Today"))
+
 	def update_depends_on(self):
 		depends_on_tasks = self.depends_on_tasks or ""
 		for d in self.depends_on:
diff --git a/erpnext/projects/report/delayed_tasks_summary/__init__.py b/erpnext/projects/report/delayed_tasks_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/projects/report/delayed_tasks_summary/__init__.py
diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js
new file mode 100644
index 0000000..5aa44c0
--- /dev/null
+++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.js
@@ -0,0 +1,41 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Delayed Tasks Summary"] = {
+	"filters": [
+		{
+			"fieldname": "from_date",
+			"label": __("From Date"),
+			"fieldtype": "Date"
+		},
+		{
+			"fieldname": "to_date",
+			"label": __("To Date"),
+			"fieldtype": "Date"
+		},
+		{
+			"fieldname": "priority",
+			"label": __("Priority"),
+			"fieldtype": "Select",
+			"options": ["", "Low", "Medium", "High", "Urgent"]
+		},
+		{
+			"fieldname": "status",
+			"label": __("Status"),
+			"fieldtype": "Select",
+			"options": ["", "Open", "Working","Pending Review","Overdue","Completed"]
+		},
+	],
+	"formatter": function(value, row, column, data, default_formatter) {
+		value = default_formatter(value, row, column, data);
+		if (column.id == "delay") {
+			if (data["delay"] > 0) {
+				value = `<p style="color: red; font-weight: bold">${value}</p>`;
+			} else {
+				value = `<p style="color: green; font-weight: bold">${value}</p>`;
+			}
+		}
+		return value
+	}
+};
diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json
new file mode 100644
index 0000000..100c422
--- /dev/null
+++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.json
@@ -0,0 +1,29 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-03-25 15:03:19.857418",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-04-15 15:49:35.432486",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Delayed Tasks Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Task",
+ "report_name": "Delayed Tasks Summary",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Projects User"
+  },
+  {
+   "role": "Projects Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
new file mode 100644
index 0000000..cdabe64
--- /dev/null
+++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
@@ -0,0 +1,133 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import date_diff, nowdate
+
+def execute(filters=None):
+	columns, data = [], []
+	data = get_data(filters)
+	columns = get_columns()
+	charts = get_chart_data(data)
+	return columns, data, None, charts
+
+def get_data(filters):
+	conditions = get_conditions(filters)
+	tasks = frappe.get_all("Task",
+			filters = conditions,
+			fields = ["name", "subject", "exp_start_date", "exp_end_date",
+					"status", "priority", "completed_on", "progress"],
+			order_by="creation"
+		)
+	for task in tasks:
+		if task.exp_end_date:
+			if task.completed_on:
+				task.delay = date_diff(task.completed_on, task.exp_end_date)
+			elif task.status == "Completed":
+				# task is completed but completed on is not set (for older tasks)
+				task.delay = 0
+			else:
+				# task not completed
+				task.delay = date_diff(nowdate(), task.exp_end_date)
+		else:
+			# task has no end date, hence no delay
+			task.delay = 0
+
+	# Sort by descending order of delay
+	tasks.sort(key=lambda x: x["delay"], reverse=True)
+	return tasks
+
+def get_conditions(filters):
+	conditions = frappe._dict()
+	keys = ["priority", "status"]
+	for key in keys:
+		if filters.get(key):
+			conditions[key] = filters.get(key)
+	if filters.get("from_date"):
+		conditions.exp_end_date = [">=", filters.get("from_date")]
+	if filters.get("to_date"):
+		conditions.exp_start_date = ["<=", filters.get("to_date")]
+	return conditions
+
+def get_chart_data(data):
+	delay, on_track = 0, 0
+	for entry in data:
+		if entry.get("delay") > 0:
+			delay = delay + 1
+		else:
+			on_track = on_track + 1
+	charts = {
+		"data": {
+			"labels": ["On Track", "Delayed"],
+			"datasets": [
+				{
+					"name": "Delayed",
+					"values": [on_track, delay]
+				}
+			]
+		},
+		"type": "percentage",
+		"colors": ["#84D5BA", "#CB4B5F"]
+	}
+	return charts
+
+def get_columns():
+	columns = [
+		{
+			"fieldname": "name",
+			"fieldtype": "Link",
+			"label": "Task",
+			"options": "Task",
+			"width": 150
+		},
+		{
+			"fieldname": "subject",
+			"fieldtype": "Data",
+			"label": "Subject",
+			"width": 200
+		},
+		{
+			"fieldname": "status",
+			"fieldtype": "Data",
+			"label": "Status",
+			"width": 100
+		},
+		{
+			"fieldname": "priority",
+			"fieldtype": "Data",
+			"label": "Priority",
+			"width": 80
+		},
+		{
+			"fieldname": "progress",
+			"fieldtype": "Data",
+			"label": "Progress (%)",
+			"width": 120
+		},
+		{
+			"fieldname": "exp_start_date",
+			"fieldtype": "Date",
+			"label": "Expected Start Date",
+			"width": 150
+		},
+		{
+			"fieldname": "exp_end_date",
+			"fieldtype": "Date",
+			"label": "Expected End Date",
+			"width": 150
+		},
+		{
+			"fieldname": "completed_on",
+			"fieldtype": "Date",
+			"label": "Actual End Date",
+			"width": 130
+		},
+		{
+			"fieldname": "delay",
+			"fieldtype": "Data",
+			"label": "Delay (In Days)",
+			"width": 120
+		}
+	]
+	return columns
diff --git a/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py
new file mode 100644
index 0000000..dbeedb4
--- /dev/null
+++ b/erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py
@@ -0,0 +1,54 @@
+from __future__ import unicode_literals
+import unittest
+import frappe
+from frappe.utils import nowdate, add_days, add_months
+from erpnext.projects.doctype.task.test_task import create_task
+from erpnext.projects.report.delayed_tasks_summary.delayed_tasks_summary import execute
+
+class TestDelayedTasksSummary(unittest.TestCase):
+	@classmethod
+	def setUp(self):
+		task1 = create_task("_Test Task 98", add_days(nowdate(), -10), nowdate())
+		create_task("_Test Task 99", add_days(nowdate(), -10), add_days(nowdate(), -1))
+		
+		task1.status = "Completed"
+		task1.completed_on = add_days(nowdate(), -1)
+		task1.save()
+
+	def test_delayed_tasks_summary(self):
+		filters = frappe._dict({
+			"from_date": add_months(nowdate(), -1),
+			"to_date": nowdate(),
+			"priority": "Low",
+			"status": "Open"
+		})
+		expected_data = [
+			{
+				"subject": "_Test Task 99",
+				"status": "Open",
+				"priority": "Low",
+				"delay": 1
+			},
+			{
+				"subject": "_Test Task 98",
+				"status": "Completed",
+				"priority": "Low",
+				"delay": -1
+			}
+		]
+		report = execute(filters)
+		data = list(filter(lambda x: x.subject == "_Test Task 99", report[1]))[0]
+		
+		for key in ["subject", "status", "priority", "delay"]:
+			self.assertEqual(expected_data[0].get(key), data.get(key))
+
+		filters.status = "Completed"
+		report = execute(filters)
+		data = list(filter(lambda x: x.subject == "_Test Task 98", report[1]))[0]
+
+		for key in ["subject", "status", "priority", "delay"]:
+			self.assertEqual(expected_data[1].get(key), data.get(key))
+
+	def tearDown(self):
+		for task in ["_Test Task 98", "_Test Task 99"]:
+			frappe.get_doc("Task", {"subject": task}).delete()
\ No newline at end of file
diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json
index dbbd7e1..0ec1702 100644
--- a/erpnext/projects/workspace/projects/projects.json
+++ b/erpnext/projects/workspace/projects/projects.json
@@ -15,6 +15,7 @@
  "hide_custom": 0,
  "icon": "project",
  "idx": 0,
+ "is_default": 0,
  "is_standard": 1,
  "label": "Projects",
  "links": [
@@ -148,9 +149,19 @@
    "link_type": "Report",
    "onboard": 0,
    "type": "Link"
+  },
+  {
+   "dependencies": "Task",
+   "hidden": 0,
+   "is_query_report": 1,
+   "label": "Delayed Tasks Summary",
+   "link_to": "Delayed Tasks Summary",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
   }
  ],
- "modified": "2020-12-01 13:38:37.856224",
+ "modified": "2021-03-26 16:32:00.628561",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Projects",
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 67b12fb..cdfd909 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -216,7 +216,8 @@
 				child: item,
 				args: {
 					item_code: item.item_code,
-					warehouse: item.warehouse
+					warehouse: item.warehouse,
+					company: doc.company
 				}
 			});
 		}
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 6c2144d..a0398e7 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1103,6 +1103,8 @@
 				to_currency: to_currency,
 				args: args
 			},
+			freeze: true,
+			freeze_message: __("Fetching exchange rates ..."),
 			callback: function(r) {
 				callback(flt(r.message));
 			}
diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js
index 4a9d1e3..32fa4ab 100644
--- a/erpnext/public/js/education/lms/quiz.js
+++ b/erpnext/public/js/education/lms/quiz.js
@@ -20,6 +20,16 @@
 	}
 
 	make(data) {
+		if (data.duration) {
+			const timer_display = document.createElement("div");
+			timer_display.classList.add("lms-timer", "float-right", "font-weight-bold");
+			document.getElementsByClassName("lms-title")[0].appendChild(timer_display);
+			if (!data.activity || (data.activity && !data.activity.is_complete)) {
+				this.initialiseTimer(data.duration);
+				this.is_time_bound = true;
+				this.time_taken = 0;
+			}
+		}
 		data.questions.forEach(question_data => {
 			let question_wrapper = document.createElement('div');
 			let question = new Question({
@@ -37,12 +47,51 @@
 				indicator = 'green'
 				message = 'You have already cleared the quiz.'
 			}
-
+			if (data.activity.time_taken) {
+				this.calculate_and_display_time(data.activity.time_taken, "Time Taken - ");
+			}
 			this.set_quiz_footer(message, indicator, data.activity.score)
 		}
 		else {
 			this.make_actions();
 		}
+		window.addEventListener('beforeunload', (event) => {
+			event.preventDefault();
+			event.returnValue = '';
+		});
+	}
+
+	initialiseTimer(duration) {
+		this.time_left = duration;
+		var self = this;
+		var old_diff;
+		this.calculate_and_display_time(this.time_left, "Time Left - ");
+		this.start_time = new Date().getTime();
+		this.timer = setInterval(function () {
+			var diff = (new Date().getTime() - self.start_time)/1000;
+			var variation = old_diff ? diff - old_diff : diff;
+			old_diff = diff;
+			self.time_left -= variation;
+			self.time_taken += variation;
+			self.calculate_and_display_time(self.time_left, "Time Left - ");
+			if (self.time_left <= 0) {
+				clearInterval(self.timer);
+				self.time_taken -= 1;
+				self.submit();
+			}
+		}, 1000);
+	}
+
+	calculate_and_display_time(second, text) {
+		var timer_display = document.getElementsByClassName("lms-timer")[0];
+		var hours = this.append_zero(Math.floor(second / 3600));
+		var minutes = this.append_zero(Math.floor(second % 3600 / 60));
+		var seconds = this.append_zero(Math.ceil(second % 3600 % 60));
+		timer_display.innerText = text + hours + ":" + minutes + ":" + seconds;
+	}
+
+	append_zero(time) {
+		return time > 9 ? time : "0" + time;
 	}
 
 	make_actions() {
@@ -57,6 +106,10 @@
 	}
 
 	submit() {
+		if (this.is_time_bound) {
+			clearInterval(this.timer);
+			$(".lms-timer").text("");
+		}
 		this.submit_btn.innerText = 'Evaluating..'
 		this.submit_btn.disabled = true
 		this.disable()
@@ -64,7 +117,8 @@
 			quiz_name: this.name,
 			quiz_response: this.get_selected(),
 			course: this.course,
-			program: this.program
+			program: this.program,
+			time_taken: this.is_time_bound ? this.time_taken : ""
 		}).then(res => {
 			this.submit_btn.remove()
 			if (!res.message) {
@@ -157,7 +211,7 @@
 			return input;
 		}
 
-		let make_label = function(name, value) {
+		let make_label = function (name, value) {
 			let label = document.createElement('label');
 			label.classList.add('form-check-label');
 			label.htmlFor = name;
@@ -166,14 +220,14 @@
 		}
 
 		let make_option = function (wrapper, option) {
-			let option_div = document.createElement('div')
-			option_div.classList.add('form-check', 'pb-1')
+			let option_div = document.createElement('div');
+			option_div.classList.add('form-check', 'pb-1');
 			let input = make_input(option.name, option.option);
 			let label = make_label(option.name, option.option);
-			option_div.appendChild(input)
-			option_div.appendChild(label)
-			wrapper.appendChild(option_div)
-			return {input: input, ...option}
+			option_div.appendChild(input);
+			option_div.appendChild(label);
+			wrapper.appendChild(option_div);
+			return { input: input, ...option };
 		}
 
 		let options_wrapper = document.createElement('div')
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index e5b50d8..fd98f17 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -291,17 +291,15 @@
 			return options[0];
 		}
 	},
-	copy_parent_value_in_all_row: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) {
-		var d = locals[dt][dn];
-		if(d[fieldname]){
-			var cl = doc[table_fieldname] || [];
-			for(var i = 0; i < cl.length; i++) {
+	overrides_parent_value_in_all_rows: function(doc, dt, dn, table_fieldname, fieldname, parent_fieldname) {
+		if (doc[parent_fieldname]) {
+			let cl = doc[table_fieldname] || [];
+			for (let i = 0; i < cl.length; i++) {
 				cl[i][fieldname] = doc[parent_fieldname];
 			}
+			frappe.refresh_field(table_fieldname);
 		}
-		refresh_field(table_fieldname);
 	},
-
 	create_new_doc: function (doctype, update_fields) {
 		frappe.model.with_doctype(doctype, function() {
 			var new_doc = frappe.model.get_new_doc(doctype);
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index d49a813..3333d56 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -353,9 +353,9 @@
 									return row.on_grid_fields_dict.batch_no.get_value();
 								}
 							});
-							if (selected_batches.includes(val)) {
+							if (selected_batches.includes(batch_no)) {
 								this.set_value("");
-								frappe.throw(__('Batch {0} already selected.', [val]));
+								frappe.throw(__('Batch {0} already selected.', [batch_no]));
 							}
 
 							if (me.warehouse_details.name) {
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 7507623..b637fb4 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -561,7 +561,7 @@
 
 	fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
 
-	gst_json = {"gstin": "", "version": "GST2.2.9",
+	gst_json = {"version": "GST2.2.9",
 		"hash": "hash", "gstin": gstin, "fp": fp}
 
 	res = {}
diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py
index d9ac6cb..9b3677d 100644
--- a/erpnext/regional/saudi_arabia/setup.py
+++ b/erpnext/regional/saudi_arabia/setup.py
@@ -4,11 +4,8 @@
 from __future__ import unicode_literals
 
 from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats
-from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax
+
 
 def setup(company=None, patch=True):
 	make_custom_fields()
 	add_print_formats()
-
-	if company:
-		create_sales_tax(company)
\ No newline at end of file
diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py
index 68208ab..bd12d66 100644
--- a/erpnext/regional/united_arab_emirates/setup.py
+++ b/erpnext/regional/united_arab_emirates/setup.py
@@ -6,7 +6,6 @@
 import frappe, os, json
 from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
 from frappe.permissions import add_permission, update_permission_property
-from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax
 from erpnext.payroll.doctype.gratuity_rule.gratuity_rule import get_gratuity_rule
 
 def setup(company=None, patch=True):
@@ -16,9 +15,6 @@
 	add_permissions()
 	create_gratuity_rule()
 
-	if company:
-		create_sales_tax(company)
-
 def make_custom_fields():
 	is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
 		fieldtype='Check', fetch_from='item_code.is_zero_rated', insert_after='description',
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 96b3fa4..49ca942 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -38,11 +38,19 @@
 			set_name_by_naming_series(self)
 
 	def get_customer_name(self):
-		if frappe.db.get_value("Customer", self.customer_name):
+
+		if frappe.db.get_value("Customer", self.customer_name) and not frappe.flags.in_import:
 			count = frappe.db.sql("""select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabCustomer
 				 where name like %s""", "%{0} - %".format(self.customer_name), as_list=1)[0][0]
 			count = cint(count) + 1
-			return "{0} - {1}".format(self.customer_name, cstr(count))
+
+			new_customer_name = "{0} - {1}".format(self.customer_name, cstr(count))
+
+			msgprint(_("Changed customer name to '{}' as '{}' already exists.")
+					.format(new_customer_name, self.customer_name),
+					title=_("Note"), indicator="yellow")
+
+			return new_customer_name
 
 		return self.customer_name
 
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 0a5c665..762b6f1 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -98,6 +98,7 @@
   "rounded_total",
   "in_words",
   "advance_paid",
+  "disable_rounded_total",
   "packing_list",
   "packed_items",
   "payment_schedule_section",
@@ -901,6 +902,7 @@
    "width": "150px"
   },
   {
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "base_rounding_adjustment",
    "fieldtype": "Currency",
    "hide_days": 1,
@@ -912,6 +914,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "base_rounded_total",
    "fieldtype": "Currency",
    "hide_days": 1,
@@ -961,6 +964,7 @@
    "width": "150px"
   },
   {
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "rounding_adjustment",
    "fieldtype": "Currency",
    "hide_days": 1,
@@ -973,6 +977,7 @@
   },
   {
    "bold": 1,
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "rounded_total",
    "fieldtype": "Currency",
    "hide_days": 1,
@@ -1474,13 +1479,20 @@
    "label": "Represents Company",
    "options": "Company",
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "grand_total",
+   "fieldname": "disable_rounded_total",
+   "fieldtype": "Check",
+   "label": "Disable Rounded Total"
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-01-20 23:40:39.929296",
+ "modified": "2021-04-15 23:55:13.439068",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order",
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index e0d5b73..9fb3943 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -159,6 +159,31 @@
 	bind_events() {
 		const me = this;
 		window.onScan = onScan;
+
+		onScan.decodeKeyEvent = function (oEvent) {
+			var iCode = this._getNormalizedKeyNum(oEvent);
+			switch (true) {
+				case iCode >= 48 && iCode <= 90: // numbers and letters
+				case iCode >= 106 && iCode <= 111: // operations on numeric keypad (+, -, etc.)
+				case (iCode >= 160 && iCode <= 164) || iCode == 170: // ^ ! # $ *
+				case iCode >= 186 && iCode <= 194: // (; = , - . / `)
+				case iCode >= 219 && iCode <= 222: // ([ \ ] ')
+					if (oEvent.key !== undefined && oEvent.key !== '') {
+						return oEvent.key;
+					}
+
+					var sDecoded = String.fromCharCode(iCode);
+					switch (oEvent.shiftKey) {
+						case false: sDecoded = sDecoded.toLowerCase(); break;
+						case true: sDecoded = sDecoded.toUpperCase(); break;
+					}
+					return sDecoded;
+				case iCode >= 96 && iCode <= 105: // numbers on numeric keypad
+					return 0 + (iCode - 96);
+			}
+			return '';
+		};
+
 		onScan.attachTo(document, {
 			onScan: (sScancode) => {
 				if (this.search_field && this.$component.is(':visible')) {
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 0922171..64e027d 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -17,6 +17,7 @@
 from past.builtins import cmp
 import functools
 from erpnext.accounts.doctype.account.account import get_account_currency
+from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges
 
 class Company(NestedSet):
 	nsm_parent_field = 'parent_company'
@@ -68,11 +69,7 @@
 
 	@frappe.whitelist()
 	def create_default_tax_template(self):
-		from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax
-		create_sales_tax({
-			'country': self.country,
-			'company_name': self.name
-		})
+		setup_taxes_and_charges(self.name, self.country)
 
 	def validate_default_accounts(self):
 		accounts = [
diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py
index 933ed3c..8367a25 100644
--- a/erpnext/setup/doctype/company/delete_company_transactions.py
+++ b/erpnext/setup/doctype/company/delete_company_transactions.py
@@ -15,7 +15,7 @@
 	frappe.only_for("System Manager")
 	doc = frappe.get_doc("Company", company_name)
 
-	if frappe.session.user != doc.owner:
+	if frappe.session.user != doc.owner and frappe.session.user != 'Administrator':
 		frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
 			frappe.PermissionError)
 
diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json
index beddaee..5876488 100644
--- a/erpnext/setup/setup_wizard/data/country_wise_tax.json
+++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json
@@ -481,14 +481,250 @@
 	},
 
 	"Germany": {
-		"Germany VAT 19%": {
-			"account_name": "VAT 19%",
-			"tax_rate": 19.00,
-			"default": 1
-		},
-		"Germany VAT 7%": {
-			"account_name": "VAT 7%",
-			"tax_rate": 7.00
+		"chart_of_accounts": {
+			"SKR04 mit Kontonummern": {
+				"sales_tax_templates": [
+					{
+						"title": "Umsatzsteuer 19%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Umsatzsteuer 19%",
+									"account_number": "3806",
+									"tax_rate": 19.00
+								}
+							}
+						]
+					},
+					{
+						"title": "Umsatzsteuer 7%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Umsatzsteuer 7%",
+									"account_number": "3801",
+									"tax_rate": 7.00
+								}
+							}
+						]
+					}
+				],
+				"purchase_tax_templates": [
+					{
+						"title": "Abziehbare Vorsteuer 19%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Abziehbare Vorsteuer 19%",
+									"account_number": "1406",
+									"root_type": "Asset",
+									"tax_rate": 19.00
+								}
+							}
+						]
+					},
+					{
+						"title": "Abziehbare Vorsteuer 7%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Abziehbare Vorsteuer 7%",
+									"account_number": "1401",
+									"root_type": "Asset",
+									"tax_rate": 7.00
+								}
+							}
+						]
+					},
+					{
+						"title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%",
+									"account_number": "1407",
+									"root_type": "Asset",
+									"tax_rate": 19.00
+								},
+								"add_deduct_tax": "Add"
+							},
+							{
+								"account_head": {
+									"account_name": "Umsatzsteuer nach § 13b UStG 19%",
+									"account_number": "3837",
+									"root_type": "Liability",
+									"tax_rate": 19.00
+								},
+								"add_deduct_tax": "Deduct"
+							}
+						]
+					}
+				]
+			},
+			"SKR03 mit Kontonummern": {
+				"sales_tax_templates": [
+					{
+						"title": "Umsatzsteuer 19%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Umsatzsteuer 19%",
+									"account_number": "1776",
+									"tax_rate": 19.00
+								}
+							}
+						]
+					},
+					{
+						"title": "Umsatzsteuer 7%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Umsatzsteuer 7%",
+									"account_number": "1771",
+									"tax_rate": 7.00
+								}
+							}
+						]
+					}
+				],
+				"purchase_tax_templates": [
+					{
+						"title": "Abziehbare Vorsteuer 19%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Abziehbare Vorsteuer 19%",
+									"account_number": "1576",
+									"root_type": "Asset",
+									"tax_rate": 19.00
+								}
+							}
+						]
+					},
+					{
+						"title": "Abziehbare Vorsteuer 7%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Abziehbare Vorsteuer 7%",
+									"account_number": "1571",
+									"root_type": "Asset",
+									"tax_rate": 7.00
+								}
+							}
+						]
+					}
+				]
+			},
+			"Standard with Numbers": {
+				"sales_tax_templates": [
+					{
+						"title": "Umsatzsteuer 19%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Umsatzsteuer 19%",
+									"account_number": "2301",
+									"tax_rate": 19.00
+								}
+							}
+						]
+					},
+					{
+						"title": "Umsatzsteuer 7%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Umsatzsteuer 7%",
+									"account_number": "2302",
+									"tax_rate": 7.00
+								}
+							}
+						]
+					}
+				],
+				"purchase_tax_templates": [
+					{
+						"title": "Abziehbare Vorsteuer 19%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Abziehbare Vorsteuer 19%",
+									"account_number": "1501",
+									"root_type": "Asset",
+									"tax_rate": 19.00
+								}
+							}
+						]
+					},
+					{
+						"title": "Abziehbare Vorsteuer 7%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Abziehbare Vorsteuer 7%",
+									"account_number": "1502",
+									"root_type": "Asset",
+									"tax_rate": 7.00
+								}
+							}
+						]
+					}
+				]
+			},
+			"*": {
+				"sales_tax_templates": [
+					{
+						"title": "Umsatzsteuer 19%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Umsatzsteuer 19%",
+									"tax_rate": 19.00
+								}
+							}
+						]
+					},
+					{
+						"title": "Umsatzsteuer 7%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Umsatzsteuer 7%",
+									"tax_rate": 7.00
+								}
+							}
+						]
+					}
+				],
+				"purchase_tax_templates": [
+					{
+						"title": "Abziehbare Vorsteuer 19%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Abziehbare Vorsteuer 19%",
+									"tax_rate": 19.00,
+									"root_type": "Asset"
+								}
+							}
+						]
+					},
+					{
+						"title": "Abziehbare Vorsteuer 7%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "Abziehbare Vorsteuer 7%",
+									"root_type": "Asset",
+									"tax_rate": 7.00
+								}
+							}
+						]
+					}
+				]
+			}
 		}
 	},
 
@@ -580,26 +816,135 @@
 	},
 
 	"India": {
-		"In State GST": {
-			"account_name": ["SGST", "CGST"],
-			"tax_rate": [9.00, 9.00],
-			"default": 1
-		},
-		"Out of State GST": {
-			"account_name": "IGST",
-			"tax_rate": 18.00
-		},
-		"VAT 5%": {
-			"account_name": "VAT 5%",
-			"tax_rate": 5.00
-		},
-		"VAT 4%": {
-			"account_name": "VAT 4%",
-			"tax_rate": 4.00
-		},
-		"VAT 14%": {
-			"account_name": "VAT 14%",
-			"tax_rate": 14.00
+		"chart_of_accounts": {
+			"*": {
+				"item_tax_templates": [
+					{
+						"title": "In State GST",
+						"taxes": [
+							{
+								"tax_type": {
+									"account_name": "SGST",
+									"tax_rate": 9.00
+								}
+							},
+							{
+								"tax_type": {
+									"account_name": "CGST",
+									"tax_rate": 9.00
+								}
+							}
+						]
+					},
+					{
+						"title": "Out of State GST",
+						"taxes": [
+							{
+								"tax_type": {
+									"account_name": "IGST",
+									"tax_rate": 18.00
+								}
+							}
+						]
+					},
+					{
+						"title": "VAT 5%",
+						"taxes": [
+							{
+								"tax_type": {
+									"account_name": "VAT 5%",
+									"tax_rate": 5.00
+								}
+							}
+						]
+					},
+					{
+						"title": "VAT 4%",
+						"taxes": [
+							{
+								"tax_type": {
+									"account_name": "VAT 4%",
+									"tax_rate": 4.00
+								}
+							}
+						]
+					},
+					{
+						"title": "VAT 14%",
+						"taxes": [
+							{
+								"tax_type": {
+									"account_name": "VAT 14%",
+									"tax_rate": 14.00
+								}
+							}
+						]
+					}
+				],
+				"*": [
+					{
+						"title": "In State GST",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "SGST",
+									"tax_rate": 9.00
+								}
+							},
+							{
+								"account_head": {
+									"account_name": "CGST",
+									"tax_rate": 9.00
+								}
+							}
+						]
+					},
+					{
+						"title": "Out of State GST",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "IGST",
+									"tax_rate": 18.00
+								}
+							}
+						]
+					},
+					{
+						"title": "VAT 5%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "VAT 5%",
+									"tax_rate": 5.00
+								}
+							}
+						]
+					},
+					{
+						"title": "VAT 4%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "VAT 4%",
+									"tax_rate": 4.00
+								}
+							}
+						]
+					},
+					{
+						"title": "VAT 14%",
+						"taxes": [
+							{
+								"account_head": {
+									"account_name": "VAT 14%",
+									"tax_rate": 14.00
+								}
+							}
+						]
+					}
+				]
+			}
 		}
 	},
 
diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py
index c3c1593..429a558 100644
--- a/erpnext/setup/setup_wizard/operations/taxes_setup.py
+++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py
@@ -1,123 +1,232 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
 from __future__ import unicode_literals
-import frappe, copy, os, json
-from frappe.utils import flt
-from erpnext.accounts.doctype.account.account import RootNotEditable
 
-def create_sales_tax(args):
-	country_wise_tax = get_country_wise_tax(args.get("country"))
-	if country_wise_tax and len(country_wise_tax) > 0:
-		for sales_tax, tax_data in country_wise_tax.items():
-			make_tax_account_and_template(
-				args.get("company_name"),
-				tax_data.get('account_name'),
-				tax_data.get('tax_rate'), sales_tax)
+import os
+import json
 
-def make_tax_account_and_template(company, account_name, tax_rate, template_name=None):
-	if not isinstance(account_name, (list, tuple)):
-		account_name = [account_name]
-		tax_rate = [tax_rate]
+import frappe
+from frappe import _
 
-	accounts = []
-	for i, name in enumerate(account_name):
-		tax_account = make_tax_account(company, account_name[i], tax_rate[i])
-		if tax_account:
-			accounts.append(tax_account)
 
-	try:
-		if accounts:
-			make_sales_and_purchase_tax_templates(accounts, template_name)
-			make_item_tax_templates(accounts, template_name)
-	except frappe.NameError:
-		if frappe.message_log: frappe.message_log.pop()
-	except RootNotEditable:
-		pass
+def setup_taxes_and_charges(company_name: str, country: str):
+	file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'country_wise_tax.json')
+	with open(file_path, 'r') as json_file:
+		tax_data = json.load(json_file)
 
-def make_tax_account(company, account_name, tax_rate):
-	tax_group = get_tax_account_group(company)
-	if tax_group:
-		try:
-			return frappe.get_doc({
-				"doctype":"Account",
-				"company": company,
-				"parent_account": tax_group,
-				"account_name": account_name,
-				"is_group": 0,
-				"report_type": "Balance Sheet",
-				"root_type": "Liability",
-				"account_type": "Tax",
-				"tax_rate": flt(tax_rate) if tax_rate else None
-			}).insert(ignore_permissions=True, ignore_mandatory=True)
-		except frappe.NameError:
-			if frappe.message_log: frappe.message_log.pop()
-			abbr = frappe.get_cached_value('Company',  company,  'abbr')
-			account = '{0} - {1}'.format(account_name, abbr)
-			return frappe.get_doc('Account', account)
+	country_wise_tax = tax_data.get(country)
 
-def make_sales_and_purchase_tax_templates(accounts, template_name=None):
-	if not template_name:
-		template_name = accounts[0].name
+	if not country_wise_tax:
+		return
 
-	sales_tax_template = {
-		"doctype": "Sales Taxes and Charges Template",
-		"title": template_name,
-		"company": accounts[0].company,
-		'taxes': []
+	if 'chart_of_accounts' not in country_wise_tax:
+		country_wise_tax = simple_to_detailed(country_wise_tax)
+
+	from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts'))
+
+
+def simple_to_detailed(templates):
+	"""
+	Convert a simple taxes object into a more detailed data structure.
+
+	Example input:
+
+	{
+		"France VAT 20%": {
+			"account_name": "VAT 20%",
+			"tax_rate": 20,
+			"default": 1
+		},
+		"France VAT 10%": {
+			"account_name": "VAT 10%",
+			"tax_rate": 10
+		}
 	}
-
-	for account in accounts:
-		sales_tax_template['taxes'].append({
-			"category": "Total",
-			"charge_type": "On Net Total",
-			"account_head": account.name,
-			"description": "{0} @ {1}".format(account.account_name, account.tax_rate),
-			"rate": account.tax_rate
-		})
-	# Sales
-	frappe.get_doc(copy.deepcopy(sales_tax_template)).insert(ignore_permissions=True)
-
-	# Purchase
-	purchase_tax_template = copy.deepcopy(sales_tax_template)
-	purchase_tax_template["doctype"] = "Purchase Taxes and Charges Template"
-
-	doc = frappe.get_doc(purchase_tax_template)
-	doc.insert(ignore_permissions=True)
-
-def make_item_tax_templates(accounts, template_name=None):
-	if not template_name:
-		template_name = accounts[0].name
-
-	item_tax_template = {
-		"doctype": "Item Tax Template",
-		"title": template_name,
-		"company": accounts[0].company,
-		'taxes': []
+	"""
+	return {
+		'chart_of_accounts': {
+			'*': {
+				'item_tax_templates': [{
+					'title': title,
+					'taxes': [{
+						'tax_type': {
+							'account_name': data.get('account_name'),
+							'tax_rate': data.get('tax_rate')
+						}
+					}]
+				} for title, data in templates.items()],
+				'*': [{
+					'title': title,
+					'is_default': data.get('default', 0),
+					'taxes': [{
+						'account_head': {
+							'account_name': data.get('account_name'),
+							'tax_rate': data.get('tax_rate')
+						}
+					}]
+				} for title, data in templates.items()]
+			}
+		}
 	}
 
 
-	for account in accounts:
-		item_tax_template['taxes'].append({
-			"tax_type": account.name,
-			"tax_rate": account.tax_rate
-		})
+def from_detailed_data(company_name, data):
+	"""Create Taxes and Charges Templates from detailed data."""
+	coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts')
+	tax_templates = data.get(coa_name) or data.get('*')
+	sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*')
+	purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*')
+	item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*')
 
-	# Items
-	frappe.get_doc(copy.deepcopy(item_tax_template)).insert(ignore_permissions=True)
+	if sales_tax_templates:
+		for template in sales_tax_templates:
+			make_taxes_and_charges_template(company_name, 'Sales Taxes and Charges Template', template)
 
-def get_tax_account_group(company):
-	tax_group = frappe.db.get_value("Account",
-		{"account_name": "Duties and Taxes", "is_group": 1, "company": company})
-	if not tax_group:
-		tax_group = frappe.db.get_value("Account", {"is_group": 1, "root_type": "Liability",
-				"account_type": "Tax", "company": company})
+	if purchase_tax_templates:
+		for template in purchase_tax_templates:
+			make_taxes_and_charges_template(company_name, 'Purchase Taxes and Charges Template', template)
 
-	return tax_group
+	if item_tax_templates:
+		for template in item_tax_templates:
+			make_item_tax_template(company_name, template)
 
-def get_country_wise_tax(country):
-	data = {}
-	with open (os.path.join(os.path.dirname(__file__), "..", "data", "country_wise_tax.json")) as countrywise_tax:
-		data = json.load(countrywise_tax).get(country)
 
-	return data
+def make_taxes_and_charges_template(company_name, doctype, template):
+	template['company'] = company_name
+	template['doctype'] = doctype
+
+	if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}):
+		return
+
+	for tax_row in template.get('taxes'):
+		account_data = tax_row.get('account_head')
+		tax_row_defaults = {
+			'category': 'Total',
+			'charge_type': 'On Net Total'
+		}
+
+		# if account_head is a dict, search or create the account and get it's name
+		if isinstance(account_data, dict):
+			tax_row_defaults['description'] = '{0} @ {1}'.format(account_data.get('account_name'), account_data.get('tax_rate'))
+			tax_row_defaults['rate'] = account_data.get('tax_rate')
+			account = get_or_create_account(company_name, account_data)
+			tax_row['account_head'] = account.name
+
+		# use the default value if nothing other is specified
+		for fieldname, default_value in tax_row_defaults.items():
+			if fieldname not in tax_row:
+				tax_row[fieldname] = default_value
+
+	return frappe.get_doc(template).insert(ignore_permissions=True)
+
+
+def make_item_tax_template(company_name, template):
+	"""Create an Item Tax Template.
+
+	This requires a separate method because Item Tax Template is structured
+	differently from Sales and Purchase Tax Templates.
+	"""
+	doctype = 'Item Tax Template'
+	template['company'] = company_name
+	template['doctype'] = doctype
+
+	if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}):
+		return
+
+	for tax_row in template.get('taxes'):
+		account_data = tax_row.get('tax_type')
+
+		# if tax_type is a dict, search or create the account and get it's name
+		if isinstance(account_data, dict):
+			account = get_or_create_account(company_name, account_data)
+			tax_row['tax_type'] = account.name
+			if 'tax_rate' not in tax_row:
+				tax_row['tax_rate'] = account_data.get('tax_rate')
+
+	return frappe.get_doc(template).insert(ignore_permissions=True)
+
+
+def get_or_create_account(company_name, account):
+	"""
+	Check if account already exists. If not, create it.
+	Return a tax account or None.
+	"""
+	default_root_type = 'Liability'
+	root_type = account.get('root_type', default_root_type)
+
+	existing_accounts = frappe.get_list('Account',
+		filters={
+			'company': company_name,
+			'root_type': root_type
+		},
+		or_filters={
+			'account_name': account.get('account_name'),
+			'account_number': account.get('account_number')
+		}
+	)
+
+	if existing_accounts:
+		return frappe.get_doc('Account', existing_accounts[0].name)
+
+	tax_group = get_or_create_tax_group(company_name, root_type)
+
+	account['doctype'] = 'Account'
+	account['company'] = company_name
+	account['parent_account'] = tax_group
+	account['report_type'] = 'Balance Sheet'
+	account['account_type'] = 'Tax'
+	account['root_type'] = root_type
+	account['is_group'] = 0
+
+	return frappe.get_doc(account).insert(ignore_permissions=True, ignore_mandatory=True)
+
+
+def get_or_create_tax_group(company_name, root_type):
+	# Look for a group account of type 'Tax'
+	tax_group_name = frappe.db.get_value('Account', {
+		'is_group': 1,
+		'root_type': root_type,
+		'account_type': 'Tax',
+		'company': company_name
+	})
+
+	if tax_group_name:
+		return tax_group_name
+
+	# Look for a group account named 'Duties and Taxes' or 'Tax Assets'
+	account_name = _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets')
+	tax_group_name = frappe.db.get_value('Account', {
+		'is_group': 1,
+		'root_type': root_type,
+		'account_name': account_name,
+		'company': company_name
+	})
+
+	if tax_group_name:
+		return tax_group_name
+
+	# Create a new group account named 'Duties and Taxes' or 'Tax Assets' just
+	# below the root account
+	root_account = frappe.get_list('Account', {
+		'is_group': 1,
+		'root_type': root_type,
+		'company': company_name,
+		'report_type': 'Balance Sheet',
+		'parent_account': ('is', 'not set')
+	}, limit=1)[0]
+
+	tax_group_account = frappe.get_doc({
+		'doctype': 'Account',
+		'company': company_name,
+		'is_group': 1,
+		'report_type': 'Balance Sheet',
+		'root_type': root_type,
+		'account_type': 'Tax',
+		'account_name': account_name,
+		'parent_account': root_account.name
+	}).insert(ignore_permissions=True)
+
+	tax_group_name = tax_group_account.name
+
+	return tax_group_name
diff --git a/erpnext/setup/setup_wizard/utils.py b/erpnext/setup/setup_wizard/utils.py
index e82bc96..4223f00 100644
--- a/erpnext/setup/setup_wizard/utils.py
+++ b/erpnext/setup/setup_wizard/utils.py
@@ -9,5 +9,4 @@
 		'data', 'test_mfg.json'), 'r') as f:
 		data = json.loads(f.read())
 
-	#setup_wizard.create_sales_tax(data)
 	setup_complete(data)
diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json
index 305456b..1576d5a 100644
--- a/erpnext/setup/workspace/home/home.json
+++ b/erpnext/setup/workspace/home/home.json
@@ -248,177 +248,9 @@
    "link_type": "DocType",
    "onboard": 1,
    "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Healthcare",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Patient",
-   "link_to": "Patient",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Diagnosis",
-   "link_to": "Diagnosis",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Education",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Student",
-   "link_to": "Student",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Instructor",
-   "link_to": "Instructor",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Course",
-   "link_to": "Course",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Room",
-   "link_to": "Room",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Non Profit",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Donor",
-   "link_to": "Donor",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Member",
-   "link_to": "Member",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Volunteer",
-   "link_to": "Volunteer",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Chapter",
-   "link_to": "Chapter",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Agriculture",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Location",
-   "link_to": "Location",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Crop",
-   "link_to": "Crop",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Crop Cycle",
-   "link_to": "Crop Cycle",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Fertilizer",
-   "link_to": "Fertilizer",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
   }
  ],
- "modified": "2021-03-16 15:59:58.416154",
+ "modified": "2021-04-19 15:48:44.089927",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Home",
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index 8515db3..56afe95 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -230,12 +230,12 @@
 	if address_type.lower() == "billing":
 		quotation.customer_address = address_name
 		quotation.address_display = address_display
-		quotation.shipping_address_name == quotation.shipping_address_name or address_name
+		quotation.shipping_address_name = quotation.shipping_address_name or address_name
 		address_doc = next((doc for doc in get_billing_addresses() if doc["name"] == address_name), None)
 	elif address_type.lower() == "shipping":
 		quotation.shipping_address_name = address_name
 		quotation.shipping_address = address_display
-		quotation.customer_address == quotation.customer_address or address_name
+		quotation.customer_address = quotation.customer_address or address_name
 		address_doc = next((doc for doc in get_shipping_addresses() if doc["name"] == address_name), None)
 	apply_cart_settings(quotation=quotation)
 
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index f595aad..280fde1 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -99,6 +99,7 @@
   "rounding_adjustment",
   "rounded_total",
   "in_words",
+  "disable_rounded_total",
   "terms_section_break",
   "tc_name",
   "terms",
@@ -768,6 +769,7 @@
    "width": "150px"
   },
   {
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "base_rounding_adjustment",
    "fieldtype": "Currency",
    "label": "Rounding Adjustment (Company Currency)",
@@ -777,6 +779,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "base_rounded_total",
    "fieldtype": "Currency",
    "label": "Rounded Total (Company Currency)",
@@ -819,6 +822,7 @@
    "width": "150px"
   },
   {
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "rounding_adjustment",
    "fieldtype": "Currency",
    "label": "Rounding Adjustment",
@@ -829,6 +833,7 @@
   },
   {
    "bold": 1,
+   "depends_on": "eval:!doc.disable_rounded_total",
    "fieldname": "rounded_total",
    "fieldtype": "Currency",
    "label": "Rounded Total",
@@ -1271,13 +1276,20 @@
    "label": "Represents Company",
    "options": "Company",
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "grand_total",
+   "fieldname": "disable_rounded_total",
+   "fieldtype": "Check",
+   "label": "Disable Rounded Total"
   }
  ],
  "icon": "fa fa-truck",
  "idx": 146,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-26 17:07:59.194403",
+ "modified": "2021-04-15 23:55:49.620641",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note",
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 7f0c3fa..16eea24 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -582,6 +582,7 @@
 			serial_no=serial_no, basic_rate=100, do_not_submit=True)
 		se.submit()
 
+		se.cancel()
 		dn.cancel()
 		pr1.cancel()
 
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index c8d8ca9..c02dd2e 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -14,6 +14,7 @@
 from erpnext.controllers.stock_controller import StockController
 from six import string_types
 from six.moves import map
+
 class SerialNoCannotCreateDirectError(ValidationError): pass
 class SerialNoCannotCannotChangeError(ValidationError): pass
 class SerialNoNotRequiredError(ValidationError): pass
@@ -322,11 +323,35 @@
 			frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code),
 				SerialNoRequiredError)
 	elif serial_nos:
+		# SLE is being cancelled and has serial nos
 		for serial_no in serial_nos:
-			sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1)
-			if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse:
-				frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
-					.format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse))
+			check_serial_no_validity_on_cancel(serial_no, sle)
+
+def check_serial_no_validity_on_cancel(serial_no, sle):
+	sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse", "company", "status"], as_dict=1)
+	sr_link = frappe.utils.get_link_to_form("Serial No", serial_no)
+	doc_link = frappe.utils.get_link_to_form(sle.voucher_type, sle.voucher_no)
+	actual_qty = cint(sle.actual_qty)
+	is_stock_reco = sle.voucher_type == "Stock Reconciliation"
+	msg = None
+
+	if sr and (actual_qty < 0 or is_stock_reco) and sr.warehouse != sle.warehouse:
+		# receipt(inward) is being cancelled
+		msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the warehouse {3}").format(
+			sle.voucher_type, doc_link, sr_link, frappe.bold(sle.warehouse))
+	elif sr and actual_qty > 0 and not is_stock_reco:
+		# delivery is being cancelled, check for warehouse.
+		if sr.warehouse:
+			# serial no is active in another warehouse/company.
+			msg = _("Cannot cancel {0} {1} as Serial No {2} is active in warehouse {3}").format(
+				sle.voucher_type, doc_link, sr_link, frappe.bold(sr.warehouse))
+		elif sr.company != sle.company and sr.status == "Delivered":
+			# serial no is inactive (allowed) or delivered from another company (block).
+			msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the company {3}").format(
+				sle.voucher_type, doc_link, sr_link, frappe.bold(sle.company))
+
+	if msg:
+		frappe.throw(msg, title=_("Cannot cancel"))
 
 def validate_material_transfer_entry(sle_doc):
 	sle_doc.update({
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index ed70790..cde7fe0 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -40,16 +40,139 @@
 		se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
 		serial_nos = get_serial_nos(se.get("items")[0].serial_no)
 
-		create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0])
+		dn = create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0])
+
+		serial_no = frappe.get_doc("Serial No", serial_nos[0])
+
+		# check Serial No details after delivery
+		self.assertEqual(serial_no.status, "Delivered")
+		self.assertEqual(serial_no.warehouse, None)
+		self.assertEqual(serial_no.company, "_Test Company")
+		self.assertEqual(serial_no.delivery_document_type, "Delivery Note")
+		self.assertEqual(serial_no.delivery_document_no, dn.name)
 
 		wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
-		make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0],
+		pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0],
 			company="_Test Company 1", warehouse=wh)
 
-		serial_no = frappe.db.get_value("Serial No", serial_nos[0], ["warehouse", "company"], as_dict=1)
+		serial_no.reload()
 
+		# check Serial No details after purchase in second company
+		self.assertEqual(serial_no.status, "Active")
 		self.assertEqual(serial_no.warehouse, wh)
 		self.assertEqual(serial_no.company, "_Test Company 1")
+		self.assertEqual(serial_no.purchase_document_type, "Purchase Receipt")
+		self.assertEqual(serial_no.purchase_document_no, pr.name)
+
+	def test_inter_company_transfer_intermediate_cancellation(self):
+		"""
+			Receive into and Deliver Serial No from one company.
+			Then Receive into and Deliver from second company.
+			Try to cancel intermediate receipts/deliveries to test if it is blocked.
+		"""
+		se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
+		serial_nos = get_serial_nos(se.get("items")[0].serial_no)
+
+		sn_doc = frappe.get_doc("Serial No", serial_nos[0])
+
+		# check Serial No details after purchase in first company
+		self.assertEqual(sn_doc.status, "Active")
+		self.assertEqual(sn_doc.company, "_Test Company")
+		self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
+		self.assertEqual(sn_doc.purchase_document_no, se.name)
+
+		dn = create_delivery_note(item_code="_Test Serialized Item With Series",
+			qty=1, serial_no=serial_nos[0])
+		sn_doc.reload()
+		# check Serial No details after delivery from **first** company
+		self.assertEqual(sn_doc.status, "Delivered")
+		self.assertEqual(sn_doc.company, "_Test Company")
+		self.assertEqual(sn_doc.warehouse, None)
+		self.assertEqual(sn_doc.delivery_document_no, dn.name)
+
+		# try cancelling the first Serial No Receipt, even though it is delivered
+		# block cancellation is Serial No is out of the warehouse
+		self.assertRaises(frappe.ValidationError, se.cancel)
+
+		# receive serial no in second company
+		wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
+		pr = make_purchase_receipt(item_code="_Test Serialized Item With Series",
+			qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+		sn_doc.reload()
+
+		self.assertEqual(sn_doc.warehouse, wh)
+		# try cancelling the delivery from the first company
+		# block cancellation as Serial No belongs to different company
+		self.assertRaises(frappe.ValidationError, dn.cancel)
+
+		# deliver from second company
+		dn_2 = create_delivery_note(item_code="_Test Serialized Item With Series",
+			qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+		sn_doc.reload()
+
+		# check Serial No details after delivery from **second** company
+		self.assertEqual(sn_doc.status, "Delivered")
+		self.assertEqual(sn_doc.company, "_Test Company 1")
+		self.assertEqual(sn_doc.warehouse, None)
+		self.assertEqual(sn_doc.delivery_document_no, dn_2.name)
+
+		# cannot cancel any intermediate document before last Delivery Note
+		self.assertRaises(frappe.ValidationError, se.cancel)
+		self.assertRaises(frappe.ValidationError, dn.cancel)
+		self.assertRaises(frappe.ValidationError, pr.cancel)
+
+	def test_inter_company_transfer_fallback_on_cancel(self):
+		"""
+			Test Serial No state changes on cancellation.
+			If Delivery cancelled, it should fall back on last Receipt in the same company.
+			If Receipt is cancelled, it should be Inactive in the same company.
+		"""
+		# Receipt in **first** company
+		se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
+		serial_nos = get_serial_nos(se.get("items")[0].serial_no)
+		sn_doc = frappe.get_doc("Serial No", serial_nos[0])
+
+		# Delivery from first company
+		dn = create_delivery_note(item_code="_Test Serialized Item With Series",
+			qty=1, serial_no=serial_nos[0])
+
+		# Receipt in **second** company
+		wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
+		pr = make_purchase_receipt(item_code="_Test Serialized Item With Series",
+			qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+
+		# Delivery from second company
+		dn_2 = create_delivery_note(item_code="_Test Serialized Item With Series",
+			qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+		sn_doc.reload()
+
+		self.assertEqual(sn_doc.status, "Delivered")
+		self.assertEqual(sn_doc.company, "_Test Company 1")
+		self.assertEqual(sn_doc.delivery_document_no, dn_2.name)
+
+		dn_2.cancel()
+		sn_doc.reload()
+		# Fallback on Purchase Receipt if Delivery is cancelled
+		self.assertEqual(sn_doc.status, "Active")
+		self.assertEqual(sn_doc.company, "_Test Company 1")
+		self.assertEqual(sn_doc.warehouse, wh)
+		self.assertEqual(sn_doc.purchase_document_no, pr.name)
+
+		pr.cancel()
+		sn_doc.reload()
+		# Inactive in same company if Receipt cancelled
+		self.assertEqual(sn_doc.status, "Inactive")
+		self.assertEqual(sn_doc.company, "_Test Company 1")
+		self.assertEqual(sn_doc.warehouse, None)
+
+		dn.cancel()
+		sn_doc.reload()
+		# Fallback on Purchase Receipt in FIRST company if
+		# Delivery from FIRST company is cancelled
+		self.assertEqual(sn_doc.status, "Active")
+		self.assertEqual(sn_doc.company, "_Test Company")
+		self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
+		self.assertEqual(sn_doc.purchase_document_no, se.name)
 
 	def tearDown(self):
 		frappe.db.rollback()
\ No newline at end of file
diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js
index 7af16af..ce2906e 100644
--- a/erpnext/stock/doctype/shipment/shipment.js
+++ b/erpnext/stock/doctype/shipment/shipment.js
@@ -363,43 +363,6 @@
 		if (frm.doc.pickup_date < frappe.datetime.get_today()) {
 			frappe.throw(__("Pickup Date cannot be before this day"));
 		}
-		if (frm.doc.pickup_date == frappe.datetime.get_today()) {
-			var pickup_time = frm.events.get_pickup_time(frm);
-			frm.set_value("pickup_from", pickup_time);
-			frm.trigger('set_pickup_to_time');
-		}
-	},
-	pickup_from: function(frm) {
-		var pickup_time = frm.events.get_pickup_time(frm);
-		if (frm.doc.pickup_from && frm.doc.pickup_date == frappe.datetime.get_today()) {
-			let current_hour = pickup_time.split(':')[0];
-			let current_min = pickup_time.split(':')[1];
-			let pickup_hour = frm.doc.pickup_from.split(':')[0];
-			let pickup_min = frm.doc.pickup_from.split(':')[1];
-			if (pickup_hour < current_hour || (pickup_hour == current_hour && pickup_min < current_min)) {
-				frm.set_value("pickup_from", pickup_time);
-				frappe.throw(__("Pickup Time cannot be in the past"));
-			}
-		}
-		frm.trigger('set_pickup_to_time');
-	},
-	get_pickup_time: function() {
-		let current_hour = new Date().getHours();
-		let current_min = new Date().toLocaleString('en-US', {minute: 'numeric'});
-		if (current_min < 30) {
-			current_min = '30';
-		} else {
-			current_min = '00';
-			current_hour = Number(current_hour)+1;
-		}
-		let pickup_time = current_hour +':'+ current_min;
-		return pickup_time;
-	},
-	set_pickup_to_time: function(frm) {
-		let pickup_to_hour = Number(frm.doc.pickup_from.split(':')[0])+5;
-		let pickup_to_min = frm.doc.pickup_from.split(':')[1];
-		let pickup_to = pickup_to_hour +':'+ pickup_to_min;
-		frm.set_value("pickup_to", pickup_to);
 	},
 	clear_pickup_fields: function(frm) {
 		let fields = ["pickup_address_name", "pickup_contact_name", "pickup_address", "pickup_contact", "pickup_contact_email", "pickup_contact_person"];
diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json
index 76c331c..a33cbc2 100644
--- a/erpnext/stock/doctype/shipment/shipment.json
+++ b/erpnext/stock/doctype/shipment/shipment.json
@@ -275,14 +275,16 @@
    "default": "09:00",
    "fieldname": "pickup_from",
    "fieldtype": "Time",
-   "label": "Pickup from"
+   "label": "Pickup from",
+   "reqd": 1
   },
   {
    "allow_on_submit": 1,
    "default": "17:00",
    "fieldname": "pickup_to",
    "fieldtype": "Time",
-   "label": "Pickup to"
+   "label": "Pickup to",
+   "reqd": 1
   },
   {
    "fieldname": "column_break_36",
@@ -431,7 +433,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-25 15:02:34.891976",
+ "modified": "2021-04-13 17:14:18.181818",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Shipment",
@@ -469,4 +471,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py
index 4697a7b..01fcee4 100644
--- a/erpnext/stock/doctype/shipment/shipment.py
+++ b/erpnext/stock/doctype/shipment/shipment.py
@@ -23,10 +23,10 @@
 			frappe.throw(_('Please enter Shipment Parcel information'))
 		if self.value_of_goods == 0:
 			frappe.throw(_('Value of goods cannot be 0'))
-		self.status = 'Submitted'
+		self.db_set('status', 'Submitted')
 
 	def on_cancel(self):
-		self.status = 'Cancelled'
+		self.db_set('status', 'Cancelled')
 
 	def validate_weight(self):
 		for parcel in self.shipment_parcel:
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index b452e96..1396f19 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -398,7 +398,7 @@
 		merge_similar_entries = {}
 
 		for d in sl_entries:
-			if not d.serial_no or d.actual_qty < 0:
+			if not d.serial_no or flt(d.get("actual_qty")) < 0:
 				new_sl_entries.append(d)
 				continue
 
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 6690c6a..36380b8 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -32,7 +32,7 @@
 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
 		# [[qty, valuation_rate, posting_date,
 		#		posting_time, expected_stock_value, bin_qty, bin_valuation]]
-		
+
 		input_data = [
 			[50, 1000, "2012-12-26", "12:00"],
 			[25, 900, "2012-12-26", "12:00"],
@@ -86,7 +86,7 @@
 		se1.cancel()
 
 	def test_get_items(self):
-		create_warehouse("_Test Warehouse Group 1", 
+		create_warehouse("_Test Warehouse Group 1",
 			{"is_group": 1, "company": "_Test Company", "parent_warehouse": "All Warehouses - _TC"})
 		create_warehouse("_Test Warehouse Ledger 1",
 			{"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC", "company": "_Test Company"})
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 84af57b..f18eabc 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -13,6 +13,7 @@
   "column_break_4",
   "valuation_method",
   "over_delivery_receipt_allowance",
+  "role_allowed_to_over_deliver_receive",
   "action_if_quality_inspection_is_not_submitted",
   "show_barcode_field",
   "clean_description_html",
@@ -234,6 +235,13 @@
    "fieldname": "disable_serial_no_and_batch_selector",
    "fieldtype": "Check",
    "label": "Disable Serial No And Batch Selector"
+  },
+  {
+   "description": "Users with this role are allowed to over deliver/receive against orders above the allowance percentage",
+   "fieldname": "role_allowed_to_over_deliver_receive",
+   "fieldtype": "Link",
+   "label": "Role Allowed to Over Deliver/Receive",
+   "options": "Role"
   }
  ],
  "icon": "icon-cog",
@@ -241,7 +249,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-01-18 13:15:38.352796",
+ "modified": "2021-03-11 18:48:14.513055",
  "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 e23f7d4..1a61f30 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -309,8 +309,6 @@
 		"update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0,
 		"delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0,
 		"is_fixed_asset": item.is_fixed_asset,
-		"weight_per_unit":item.weight_per_unit,
-		"weight_uom":item.weight_uom,
 		"last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0,
 		"transaction_date": args.get("transaction_date"),
 		"against_blanket_order": args.get("against_blanket_order"),
@@ -611,8 +609,12 @@
 	meta = frappe.get_meta(args.parenttype or args.doctype)
 
 	if meta.get_field("currency") or args.get('currency'):
-		pl_details = get_price_list_currency_and_exchange_rate(args)
-		args.update(pl_details)
+		if not args.get("price_list_currency") or not args.get("plc_conversion_rate"):
+			# if currency and plc_conversion_rate exist then
+			# `get_price_list_currency_and_exchange_rate` has already been called
+			pl_details = get_price_list_currency_and_exchange_rate(args)
+			args.update(pl_details)
+
 		if meta.get_field("currency"):
 			validate_conversion_rate(args, meta)
 
@@ -922,10 +924,19 @@
 		{"item_code": item_code, "warehouse": warehouse}, "projected_qty")}
 
 @frappe.whitelist()
-def get_bin_details(item_code, warehouse):
-	return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
+def get_bin_details(item_code, warehouse, company=None):
+	bin_details = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
 		["projected_qty", "actual_qty", "reserved_qty"], as_dict=True, cache=True) \
 			or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0}
+	if company:
+		bin_details['company_total_stock'] = get_company_total_stock(item_code, company)
+	return bin_details
+
+def get_company_total_stock(item_code, company):
+	return frappe.db.sql("""SELECT sum(actual_qty) from 
+		(`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name) 
+		WHERE `tabWarehouse`.company = '{0}' and `tabBin`.item_code = '{1}'"""
+		.format(company, item_code))[0][0]
 
 @frappe.whitelist()
 def get_serial_no_details(item_code, warehouse, stock_qty, serial_no):
@@ -993,6 +1004,8 @@
 	args = process_args(args)
 
 	parent = get_price_list_currency_and_exchange_rate(args)
+	args.update(parent)
+
 	children = []
 
 	if "items" in args:
@@ -1057,7 +1070,7 @@
 	return frappe._dict({
 		"price_list_currency": price_list_currency,
 		"price_list_uom_dependant": price_list_uom_dependant,
-		"plc_conversion_rate": plc_conversion_rate
+		"plc_conversion_rate": plc_conversion_rate or 1
 	})
 
 @frappe.whitelist()
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
index 5df3fa8..2f70523 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
@@ -55,19 +55,31 @@
 
 
 def get_consumed_items(condition):
+	purpose_to_exclude = [
+		"Material Transfer for Manufacture",
+		"Material Transfer",
+		"Send to Subcontractor"
+	]
+
+	condition += """
+		and (
+			purpose is NULL
+			or purpose not in ({})
+		)
+	""".format(', '.join([f"'{p}'" for p in purpose_to_exclude]))
+	condition = condition.replace("posting_date", "sle.posting_date")
+
 	consumed_items = frappe.db.sql("""
 		select item_code, abs(sum(actual_qty)) as consumed_qty
-		from `tabStock Ledger Entry`
-		where actual_qty < 0
+		from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se
+			on sle.voucher_no = se.name
+		where
+			actual_qty < 0
 			and voucher_type not in ('Delivery Note', 'Sales Invoice')
 			%s
-		group by item_code
-	""" % condition, as_dict=1)
+		group by item_code""" % condition, as_dict=1)
 
-	consumed_items_map = {}
-	for item in consumed_items:
-		consumed_items_map.setdefault(item.item_code, item.consumed_qty)
-
+	consumed_items_map = {item.item_code : item.consumed_qty for item in consumed_items}
 	return consumed_items_map
 
 def get_delivered_items(condition):
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index df5f16f..985901f 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -604,7 +604,7 @@
 				batch = self.wh_data.stock_queue[index]
 				if qty_to_pop >= batch[0]:
 					# consume current batch
-					qty_to_pop = qty_to_pop - batch[0]
+					qty_to_pop = _round_off_if_near_zero(qty_to_pop - batch[0])
 					self.wh_data.stock_queue.pop(index)
 					if not self.wh_data.stock_queue and qty_to_pop:
 						# stock finished, qty still remains to be withdrawn
@@ -618,8 +618,8 @@
 					batch[0] = batch[0] - qty_to_pop
 					qty_to_pop = 0
 
-		stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue))
-		stock_qty = sum((flt(batch[0]) for batch in self.wh_data.stock_queue))
+		stock_value = _round_off_if_near_zero(sum((flt(batch[0]) * flt(batch[1]) for batch in self.wh_data.stock_queue)))
+		stock_qty = _round_off_if_near_zero(sum((flt(batch[0]) for batch in self.wh_data.stock_queue)))
 
 		if stock_qty:
 			self.wh_data.valuation_rate = stock_value / flt(stock_qty)
@@ -858,3 +858,12 @@
 		order by timestamp(posting_date, posting_time) asc
 		limit 1
 	""", args, as_dict=1)
+
+def _round_off_if_near_zero(number: float, precision: int = 6) -> float:
+	""" Rounds off the number to zero only if number is close to zero for decimal
+		specified in precision. Precision defaults to 6.
+	"""
+	if flt(number) < (1.0 / (10**precision)):
+		return 0
+
+	return flt(number)
diff --git a/erpnext/utilities/activation.py b/erpnext/utilities/activation.py
index 7b17c8c..50c4b25 100644
--- a/erpnext/utilities/activation.py
+++ b/erpnext/utilities/activation.py
@@ -18,7 +18,6 @@
 		"Delivery Note": 5,
 		"Employee": 3, 
 		"Instructor": 5, 
-		"Instructor": 5, 
 		"Issue": 5,
 		"Item": 5, 
 		"Journal Entry": 3, 
diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html
index dc9b6d8..15afb09 100644
--- a/erpnext/www/lms/content.html
+++ b/erpnext/www/lms/content.html
@@ -62,7 +62,7 @@
 			{{_('Back to Course')}}
 		</a>
 	</div>
-	<div>
+	<div class="lms-title">
 		<h2>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h2>
 	</div>
 {% endmacro %}
@@ -169,14 +169,51 @@
 			const next_url = '/lms/course?name={{ course }}&program={{ program }}'
 			{% endif %}
 			frappe.ready(() => {
-				const quiz = new Quiz(document.getElementById('quiz-wrapper'), {
-					name: '{{ content.name }}',
-					course: '{{ course }}',
-					program: '{{ program }}',
-					quiz_exit_button: quiz_exit_button,
-					next_url: next_url
-				})
-				window.quiz = quiz;
+				{% if content.is_time_bound %}
+					var duration = get_duration("{{content.duration}}")
+					var d = frappe.msgprint({
+						title: __('Important Notice'),
+						indicator: "red",
+						message: __(`This is a Time-Bound Quiz. <br><br>
+						A timer for <b>${duration}</b> will start, once you click on <b>Proceed</b>. <br><br>
+						If you fail to submit before the time is up, the Quiz will be submitted automatically.`),
+						primary_action: {
+							label: __("Proceed"),
+							action: () => {
+								create_quiz();
+								d.hide();
+        					}
+						},
+						secondary_action: {
+							action: () => {
+								d.hide();
+								window.location.href = "/lms/course?name={{ course }}&program={{ program }}";
+							},
+							label: __("Go Back"),
+						}
+					});
+				{% else %}
+					create_quiz();
+				{% endif %}
+				function create_quiz() {
+					const quiz = new Quiz(document.getElementById('quiz-wrapper'), {
+						name: '{{ content.name }}',
+						course: '{{ course }}',
+						program: '{{ program }}',
+						quiz_exit_button: quiz_exit_button,
+						next_url: next_url
+					})
+					window.quiz = quiz;
+				}
+				function get_duration(seconds){
+					var hours = append_zero(Math.floor(seconds / 3600));
+					var minutes = append_zero(Math.floor(seconds % 3600 / 60));
+					var seconds = append_zero(Math.floor(seconds % 3600 % 60));
+					return `${hours}:${minutes}:${seconds}`;
+				}
+				function append_zero(time) {
+					return time > 9 ? time : "0" + time;
+				}
 			})
 		{% endif  %}
 
diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html
index 7b239ac..c1e9620 100644
--- a/erpnext/www/lms/index.html
+++ b/erpnext/www/lms/index.html
@@ -42,7 +42,9 @@
 <section class="top-section" style="padding: 6rem 0rem;">
 	<div class='container pb-5'>
 		<h1>{{ education_settings.portal_title }}</h1>
-		<p class='lead'>{{ education_settings.description }}</p>
+		{% if education_settings.description %}
+			<p class='lead'>{{ education_settings.description }}</p>
+		{% endif %}
 		<p class="mt-4">
 			{% if frappe.session.user == 'Guest' %}
 				<a class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a>
@@ -51,13 +53,15 @@
 	</div>
 	<div class='container'>
 		<div class="row mt-5">
-			{% for program in featured_programs %}
-				{{ program_card(program.program, program.has_access) }}
-			{% endfor %}
 			{% if featured_programs %}
+				{% for program in featured_programs %}
+					{{ program_card(program.program, program.has_access) }}
+				{% endfor %}
 				{% for n in range( (3 - (featured_programs|length)) %3) %}
 					{{ null_card() }}
 				{% endfor %}
+			{% else %}
+				<p class="lead">You have not enrolled in any program. Contact your Instructor.</p>
 			{% endif %}
 		</div>
 	</div>
diff --git a/erpnext/www/lms/topic.py b/erpnext/www/lms/topic.py
index f75ae8e..8abbc72 100644
--- a/erpnext/www/lms/topic.py
+++ b/erpnext/www/lms/topic.py
@@ -35,7 +35,7 @@
 				progress.append({'content': content, 'content_type': content.doctype, 'completed': status})
 			elif content.doctype == 'Quiz':
 				if student:
-					status, score, result = utils.check_quiz_completion(content, course_enrollment.name)
+					status, score, result, time_taken = utils.check_quiz_completion(content, course_enrollment.name)
 				else:
 					status = False
 					score = None
diff --git a/requirements.txt b/requirements.txt
index 5a35236..377fd7d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,15 +1,13 @@
-braintree==3.57.1
 frappe
-gocardless-pro==1.11.0
-googlemaps==3.1.1
-pandas>=1.0.5
-plaid-python>=7.0.0
-pycountry==19.8.18
-PyGithub==1.44.1
-python-stdnum==1.12
-python-youtube==0.6.0
-taxjar==1.9.0
-tweepy==3.8.0
-Unidecode==1.1.1
-WooCommerce==2.1.1
-pycryptodome==3.9.8
+gocardless-pro~=1.22.0
+googlemaps  # used in ERPNext, but dependency is defined in Frappe
+pandas~=1.1.5
+plaid-python~=7.2.1
+pycountry~=20.7.3
+PyGithub~=1.54.1
+python-stdnum~=1.16
+python-youtube~=0.8.0
+taxjar~=1.9.2
+tweepy~=3.10.0
+Unidecode~=1.2.0
+WooCommerce~=3.0.0